File Coverage

blib/lib/Perl/Tidy/VerticalAligner.pm
Criterion Covered Total %
statement 2389 2617 91.2
branch 877 1110 79.0
condition 522 692 75.4
subroutine 109 119 91.6
pod 0 79 0.0
total 3897 4617 84.4


line stmt bran cond sub pod time code
1             package Perl::Tidy::VerticalAligner;
2 44     44   295 use strict;
  44         99  
  44         1486  
3 44     44   171 use warnings;
  44         65  
  44         2360  
4 44     44   185 use Carp;
  44         69  
  44         3798  
5              
6             { #<<< A non-indenting brace to contain all lexical variables
7              
8             our $VERSION = '20260705';
9 44     44   252 use English qw( -no_match_vars );
  44         71  
  44         290  
10 44     44   12791 use Scalar::Util 'refaddr'; # perl 5.8.1 and later
  44         91  
  44         1779  
11 44     44   15636 use Perl::Tidy::VerticalAligner::Alignment;
  44         452  
  44         1762  
12 44     44   15356 use Perl::Tidy::VerticalAligner::Line;
  44         111  
  44         1387  
13              
14 44     44   228 use constant DEVEL_MODE => 0;
  44         90  
  44         2435  
15 44     44   172 use constant EMPTY_STRING => q{};
  44         73  
  44         1486  
16 44     44   155 use constant SPACE => q{ };
  44         63  
  44         1223  
17 44     44   138 use constant COMMA => q{,};
  44         61  
  44         17358  
18              
19             # The Perl::Tidy::VerticalAligner package collects output lines and
20             # attempts to line up certain common tokens, such as => and #, which are
21             # identified by the calling routine.
22             #
23             # Usage:
24             # - Initiate an object with a call to new().
25             # - Write lines one-by-one with calls to valign_input().
26             # - Make a final call to flush() to empty the pipeline.
27             #
28             # The sub valign_input collects lines into groups. When a group reaches
29             # the maximum possible size it is processed for alignment and output.
30             # The maximum group size is reached whenever there is a change in indentation
31             # level, a blank line, a block comment, or an external flush call. The calling
32             # routine may also force a break in alignment at any time.
33             #
34             # If the calling routine needs to interrupt the output and send other text to
35             # the output, it must first call flush() to empty the output pipeline. This
36             # might occur for example if a block of pod text needs to be sent to the output
37             # between blocks of code.
38              
39             # It is essential that a final call to flush() be made. Otherwise some
40             # final lines of text will be lost.
41              
42             # Index...
43             # CODE SECTION 1: Preliminary code, global definitions and sub new
44             # sub new
45             # CODE SECTION 2: Some Basic Utilities
46             # CODE SECTION 3: Code to accept input and form groups
47             # sub valign_input
48             # CODE SECTION 4: Code to process comment lines
49             # sub _flush_comment_lines
50             # CODE SECTION 5: Code to process groups of code lines
51             # sub _flush_group_lines
52             # CODE SECTION 6: Pad Signed Number Columns
53             # sub pad_signed_number_columns
54             # CODE SECTION 7: Pad Wide Equals Columns
55             # sub pad_wide_equals_columns
56             # CODE SECTION 8: Output Step A
57             # sub valign_output_step_A
58             # CODE SECTION 9: Output Step B
59             # sub valign_output_step_B
60             # CODE SECTION 10: Output Step C
61             # sub valign_output_step_C
62             # CODE SECTION 11: Output Step D
63             # sub valign_output_step_D
64             # CODE SECTION 12: Summary
65             # sub report_anything_unusual
66              
67             ##################################################################
68             # CODE SECTION 1: Preliminary code, global definitions and sub new
69             ##################################################################
70              
71             sub AUTOLOAD {
72              
73             # Catch any undefined sub calls so that we are sure to get
74             # some diagnostic information. This sub should never be called
75             # except for a programming error.
76 0     0   0 our $AUTOLOAD;
77 0 0       0 return if ( $AUTOLOAD =~ /\bDESTROY$/ );
78 0         0 my ( $pkg, $fname, $lno ) = caller();
79 0         0 my $my_package = __PACKAGE__;
80 0         0 print {*STDERR} <<EOM;
  0         0  
81             ======================================================================
82             Error detected in package '$my_package', version $VERSION
83             Received unexpected AUTOLOAD call for sub '$AUTOLOAD'
84             Called from package: '$pkg'
85             Called from File '$fname' at line '$lno'
86             This error is probably due to a recent programming change
87             ======================================================================
88             EOM
89 0         0 exit 1;
90             } ## end sub AUTOLOAD
91              
92       0     sub DESTROY {
93              
94             # required to avoid call to AUTOLOAD in some versions of perl
95             }
96              
97             sub Die {
98 0     0 0 0 my ($msg) = @_;
99 0         0 Perl::Tidy::Die($msg);
100 0         0 croak "unexpected return from Perl::Tidy::Die";
101             }
102              
103             sub Warn {
104 0     0 0 0 my ($msg) = @_;
105 0         0 Perl::Tidy::Warn($msg);
106 0         0 return;
107             }
108              
109             sub Fault {
110              
111 0     0 0 0 my ($msg) = @_;
112              
113             # This routine is called for errors that really should not occur
114             # except if there has been a bug introduced by a recent program change.
115             # Please add comments at calls to Fault to explain why the call
116             # should not occur, and where to look to fix it.
117 0         0 my ( $package0_uu, $filename0_uu, $line0, $subroutine0_uu ) = caller(0);
118 0         0 my ( $package1_uu, $filename1, $line1, $subroutine1 ) = caller(1);
119 0         0 my ( $package2_uu, $filename2_uu, $line2_uu, $subroutine2 ) = caller(2);
120 0         0 my $pkg = __PACKAGE__;
121 0         0 my $input_stream_name = Perl::Tidy::get_input_stream_name();
122              
123 0         0 Die(<<EOM);
124             ==============================================================================
125             While operating on input stream with name: '$input_stream_name'
126             A fault was detected at line $line0 of sub '$subroutine1'
127             in file '$filename1'
128             which was called from line $line1 of sub '$subroutine2'
129             Message: '$msg'
130             This is probably an error introduced by a recent programming change.
131             $pkg reports VERSION='$VERSION'.
132             ==============================================================================
133             EOM
134 0         0 croak "unexpected return from sub Die";
135             } ## end sub Fault
136              
137             my %valid_LINE_keys;
138              
139             BEGIN {
140              
141             # define valid keys in a line object
142 44     44   271 my @q = qw(
143             jmax
144             rtokens
145             rfields
146             rfield_lengths
147             rpatterns
148             indentation
149             leading_space_count
150             outdent_long_lines
151             list_type
152             list_seqno
153             is_hanging_side_comment
154             maximum_line_length
155             rvertical_tightness_flags
156             is_terminal_ternary
157             j_terminal_match
158             end_group
159             Kend
160             ci_level
161             level
162             level_end
163             imax_pair
164              
165             ralignments
166             );
167              
168 44         4521 $valid_LINE_keys{$_} = 1 for @q;
169              
170             } ## end BEGIN
171              
172             BEGIN {
173              
174             # Define the fixed indexes for variables in $self, which is an array
175             # reference. Note the convention of leading and trailing underscores to
176             # keep them unique.
177             # Do not combine with other BEGIN blocks (c101).
178 44     44   132 my $i = 0;
179             use constant {
180 44         7345 _file_writer_object_ => $i++,
181             _logger_object_ => $i++,
182             _diagnostics_object_ => $i++,
183              
184             _rOpts_ => $i++,
185              
186             _last_level_written_ => $i++,
187             _last_side_comment_column_ => $i++,
188             _last_side_comment_line_number_ => $i++,
189             _last_side_comment_length_ => $i++,
190             _last_side_comment_level_ => $i++,
191             _outdented_line_count_ => $i++,
192             _first_outdented_line_at_ => $i++,
193             _last_outdented_line_at_ => $i++,
194             _consecutive_block_comments_ => $i++,
195              
196             _rgroup_lines_ => $i++,
197             _group_level_ => $i++,
198             _group_type_ => $i++,
199             _group_maximum_line_length_ => $i++,
200             _zero_count_ => $i++,
201             _last_leading_space_count_ => $i++,
202             _comment_leading_space_count_ => $i++,
203 44     44   297 };
  44         70  
204              
205             # Debug flag. This is a relic from the original program development
206             # looking for problems with tab characters. Caution: this debug flag can
207             # produce a lot of output It should be 0 except when debugging small
208             # scripts.
209              
210 44     44   234 use constant DEBUG_TABS => 0;
  44         80  
  44         4229  
211              
212             my $debug_warning = sub {
213 0         0 my $msg = shift;
214 0         0 print {*STDOUT} "VALIGN_DEBUGGING with key $msg\n";
  0         0  
215 0         0 return;
216 44         162 };
217              
218 44         56732 DEBUG_TABS && $debug_warning->('TABS');
219             } ## end BEGIN
220              
221             # GLOBAL variables
222             my (
223              
224             %valign_control_hash,
225             $valign_control_default,
226              
227             $rOpts_indent_columns,
228             $rOpts_tabs,
229             $rOpts_entab_leading_whitespace,
230             $rOpts_fixed_position_side_comment,
231             $rOpts_maximum_line_length,
232             $rOpts_minimum_space_to_comment,
233             $rOpts_valign_code,
234             $rOpts_valign_block_comments,
235             $rOpts_valign_side_comments,
236             $rOpts_valign_signed_numbers,
237             $rOpts_valign_signed_numbers_limit,
238             $rOpts_valign_wide_equals,
239              
240             $require_tabs,
241              
242             );
243              
244             sub check_valign_list_items {
245 4     4 0 11 my ( $rlist, ( $option_name, $die_on_error ) ) = @_;
246              
247             # Warn if obviously invalid token types or keywords occur in one of the
248             # --valign lists. This is a crude check for valid token types and valid
249             # keywords.
250              
251             # Given:
252             # $rlist = ref to list of input items
253             # $option_name = (optional) name of option for a warning message
254             # Return:
255             # nothing if no errors, or
256             # ref to list of unknown token types
257 4 50       12 return if ( !defined($rlist) );
258              
259 4         9 my @unknown_items;
260 4         6 foreach my $item ( @{$rlist} ) {
  4         9  
261 5 50       18 next if ( Perl::Tidy::Tokenizer::is_valid_token_type($item) );
262 0 0       0 next if ( Perl::Tidy::Tokenizer::is_keyword($item) );
263 0         0 push @unknown_items, $item;
264             }
265 4 50       12 return if ( !@unknown_items );
266              
267 0 0       0 if ($option_name) {
268 0         0 my $num = @unknown_items;
269 0         0 local $LIST_SEPARATOR = SPACE;
270 0         0 my $msg = <<EOM;
271             $num unrecognized items input with $option_name :
272             @unknown_items
273             EOM
274 0 0       0 Die($msg) if ($die_on_error);
275 0         0 Warn($msg);
276             }
277 0         0 return \@unknown_items;
278             } ## end sub check_valign_list_items
279              
280             sub check_options {
281              
282 657     657 0 1607 my ($rOpts) = @_;
283              
284             # This routine is called to check the user-supplied run parameters
285             # in $rOpts and to configure the control hashes to them.
286              
287             # All alignments are done by default
288 657         1382 %valign_control_hash = ();
289 657         1185 $valign_control_default = 1;
290              
291             # If -vil=s is entered without -vxl, assume -vxl='*'
292 657 50 66     4314 if ( !$rOpts->{'valign-exclusion-list'}
293             && $rOpts->{'valign-inclusion-list'} )
294             {
295 0         0 $rOpts->{'valign-exclusion-list'} = '*';
296             }
297              
298             # See if the user wants to exclude any alignment types ...
299 657 100       2091 if ( $rOpts->{'valign-exclusion-list'} ) {
300              
301             # The inclusion list is only relevant if there is an exclusion list
302 3 100       11 if ( $rOpts->{'valign-inclusion-list'} ) {
303 1         4 my @vil = split /\s+/, $rOpts->{'valign-inclusion-list'};
304 1         4 check_valign_list_items( \@vil, 'valign-inclusion-list', 1 );
305 1         3 $valign_control_hash{$_} = 1 for @vil;
306             }
307              
308             # Note that the -vxl list is done after -vil, so -vxl has priority
309             # in the event of duplicate entries.
310 3         11 my @vxl = split /\s+/, $rOpts->{'valign-exclusion-list'};
311 3         14 check_valign_list_items( \@vxl, 'valign-exclusion-list', 1 );
312 3         10 $valign_control_hash{$_} = 0 for @vxl;
313              
314             # Optimization: revert to defaults if no exclusions.
315             # This could happen with -vxl=' ' and any -vil list
316 3 50       11 if ( !@vxl ) {
317 0         0 %valign_control_hash = ();
318             }
319              
320             # '$valign_control_default' applies to types not in the hash:
321             # - If a '*' was entered then set it to be that default type
322             # - Otherwise, leave it set it to 1
323 3 100       12 if ( defined( $valign_control_hash{'*'} ) ) {
324 1         3 $valign_control_default = $valign_control_hash{'*'};
325             }
326              
327             # Side comments are controlled separately and must be removed
328             # if given in a list.
329 3 50       10 if (%valign_control_hash) {
330 3         11 $valign_control_hash{'#'} = 1;
331             }
332             }
333              
334             # Initialize some global options
335 657         1507 $rOpts_indent_columns = $rOpts->{'indent-columns'};
336 657         1343 $rOpts_tabs = $rOpts->{'tabs'};
337 657         1330 $rOpts_entab_leading_whitespace = $rOpts->{'entab-leading-whitespace'};
338 657   66     2943 $require_tabs = ( $rOpts_tabs || $rOpts_entab_leading_whitespace )
339             && $rOpts_indent_columns > 0;
340              
341             $rOpts_fixed_position_side_comment =
342 657         1355 $rOpts->{'fixed-position-side-comment'};
343              
344 657         1303 $rOpts_maximum_line_length = $rOpts->{'maximum-line-length'};
345 657         1318 $rOpts_minimum_space_to_comment = $rOpts->{'minimum-space-to-comment'};
346 657         1164 $rOpts_valign_code = $rOpts->{'valign-code'};
347 657         1264 $rOpts_valign_block_comments = $rOpts->{'valign-block-comments'};
348 657         1111 $rOpts_valign_side_comments = $rOpts->{'valign-side-comments'};
349 657         1232 $rOpts_valign_signed_numbers = $rOpts->{'valign-signed-numbers'};
350             $rOpts_valign_signed_numbers_limit =
351 657         1346 $rOpts->{'valign-signed-numbers-limit'};
352 657         1132 $rOpts_valign_wide_equals = $rOpts->{'valign-wide-equals'};
353              
354 657         1291 return;
355             } ## end sub check_options
356              
357             sub check_keys {
358              
359 0     0 0 0 my ( $rtest, $rvalid, $msg, $exact_match ) = @_;
360              
361             # Check the keys of a hash:
362             # $rtest = ref to hash to test
363             # $rvalid = ref to hash with valid keys
364              
365             # $msg = a message to write in case of error
366             # $exact_match defines the type of check:
367             # = false: test hash must not have unknown key
368             # = true: test hash must have exactly same keys as known hash
369             my @unknown_keys =
370 0         0 grep { !exists $rvalid->{$_} } keys %{$rtest};
  0         0  
  0         0  
371             my @missing_keys =
372 0         0 grep { !exists $rtest->{$_} } keys %{$rvalid};
  0         0  
  0         0  
373 0         0 my $error = @unknown_keys;
374 0 0 0     0 if ($exact_match) { $error ||= @missing_keys }
  0         0  
375 0 0       0 if ($error) {
376 0         0 local $LIST_SEPARATOR = ')(';
377 0         0 my @expected_keys = sort keys %{$rvalid};
  0         0  
378 0         0 @unknown_keys = sort @unknown_keys;
379 0         0 Fault(<<EOM);
380             ------------------------------------------------------------------------
381             Program error detected checking hash keys
382             Message is: '$msg'
383             Expected keys: (@expected_keys)
384             Unknown key(s): (@unknown_keys)
385             Missing key(s): (@missing_keys)
386             ------------------------------------------------------------------------
387             EOM
388             }
389 0         0 return;
390             } ## end sub check_keys
391              
392             sub vertical_alignment_ok {
393 1316     1316 0 2168 my ($tok) = @_;
394              
395             # Return 1 if alignment ok
396             # Return 0 if do not align
397 1316 100       3966 return 1 if ( !%valign_control_hash );
398 6         13 my $align_ok = $valign_control_hash{$tok};
399 6 50       16 $align_ok = $valign_control_default unless ( defined($align_ok) );
400 6         14 return $align_ok;
401             } ## end sub vertical_alignment_ok
402              
403             sub new {
404              
405 658     658 0 2404 my ( $class, @arglist ) = @_;
406              
407             # Create a VerticalAligner object
408             # Given:
409             # @arglist is the hash of values shown below in %defaults
410              
411 658 50       1877 if ( @arglist % 2 ) { croak "Odd number of items in arg hash list\n" }
  0         0  
412              
413 658         3131 my %defaults = (
414             rOpts => undef,
415             file_writer_object => undef,
416             logger_object => undef,
417             diagnostics_object => undef,
418             );
419 658         2756 my %args = ( %defaults, @arglist );
420              
421             # Initialize other caches and buffers
422 658         3170 initialize_step_B_cache();
423 658         2358 initialize_valign_buffer();
424 658         2654 initialize_decode();
425              
426             # Initialize all variables in $self.
427             # To add an item to $self, first define a new constant index in the BEGIN
428             # section.
429 658         1014 my $self = [];
430              
431             # objects
432 658         1686 $self->[_file_writer_object_] = $args{file_writer_object};
433 658         1393 $self->[_logger_object_] = $args{logger_object};
434 658         1433 $self->[_diagnostics_object_] = $args{diagnostics_object};
435              
436             # shortcut to user options
437 658         1150 my $rOpts = $args{rOpts};
438 658         1181 $self->[_rOpts_] = $rOpts;
439              
440             # Batch of lines being collected
441 658         1366 $self->[_rgroup_lines_] = [];
442 658         1403 $self->[_group_level_] = 0;
443 658         1224 $self->[_group_type_] = EMPTY_STRING;
444 658         1138 $self->[_group_maximum_line_length_] = undef;
445 658         1386 $self->[_zero_count_] = 0;
446 658         1138 $self->[_comment_leading_space_count_] = 0;
447 658         1049 $self->[_last_leading_space_count_] = 0;
448              
449             # Memory of what has been processed
450 658         1003 $self->[_last_level_written_] = -1;
451 658         1058 $self->[_last_side_comment_column_] = 0;
452 658         993 $self->[_last_side_comment_line_number_] = 0;
453 658         989 $self->[_last_side_comment_length_] = 0;
454 658         1069 $self->[_last_side_comment_level_] = -1;
455 658         941 $self->[_outdented_line_count_] = 0;
456 658         977 $self->[_first_outdented_line_at_] = 0;
457 658         1027 $self->[_last_outdented_line_at_] = 0;
458 658         928 $self->[_consecutive_block_comments_] = 0;
459              
460 658         1123 bless $self, $class;
461 658         2383 return $self;
462             } ## end sub new
463              
464             #################################
465             # CODE SECTION 2: Basic Utilities
466             #################################
467              
468             sub flush {
469              
470 2311     2311 0 3406 my ($self) = @_;
471              
472             # flush() is the external call to completely empty the pipeline.
473              
474             # push out any current group lines
475             $self->_flush_group_lines()
476 2311 100       2628 if ( @{ $self->[_rgroup_lines_] } );
  2311         5829  
477              
478             # then anything left in the cache of step_B
479 2311         6569 $self->_flush_step_B_cache();
480              
481             # then anything left in the buffer of step_C
482 2311         5569 $self->dump_valign_buffer();
483              
484 2311         3417 return;
485             } ## end sub flush
486              
487             sub initialize_for_new_group {
488              
489 2649     2649 0 4241 my ($self) = @_;
490              
491             # initialize for a new group of lines to be aligned vertically
492              
493 2649         4240 $self->[_rgroup_lines_] = [];
494 2649         4139 $self->[_group_type_] = EMPTY_STRING;
495 2649         3347 $self->[_zero_count_] = 0;
496 2649         3428 $self->[_comment_leading_space_count_] = 0;
497 2649         3069 $self->[_last_leading_space_count_] = 0;
498 2649         3592 $self->[_group_maximum_line_length_] = undef;
499              
500             # Note that the value for _group_level_ is
501             # handled separately in sub valign_input
502 2649         3452 return;
503             } ## end sub initialize_for_new_group
504              
505             sub group_line_count {
506 94     94 0 141 my $self = shift;
507 94         116 return +@{ $self->[_rgroup_lines_] };
  94         407  
508             }
509              
510             sub write_diagnostics {
511              
512 0     0 0 0 my ( $self, $msg ) = @_;
513              
514             # Interface to Perl::Tidy::Diagnostics routines
515             # For debugging; not currently used
516 0         0 my $diagnostics_object = $self->[_diagnostics_object_];
517 0 0       0 if ($diagnostics_object) {
518 0         0 $diagnostics_object->write_diagnostics($msg);
519             }
520 0         0 return;
521             } ## end sub write_diagnostics
522              
523             sub warning {
524 0     0 0 0 my ( $self, $msg ) = @_;
525 0         0 my $logger_object = $self->[_logger_object_];
526 0 0       0 if ($logger_object) {
527 0         0 $logger_object->warning($msg);
528             }
529 0         0 return;
530             } ## end sub warning
531              
532             sub get_cached_line_count {
533 1     1 0 2 my $self = shift;
534 1 50       4 return $self->group_line_count() + ( get_cached_line_type() ? 1 : 0 );
535             }
536              
537             sub get_recoverable_spaces {
538              
539 4648     4648 0 5767 my $indentation = shift;
540              
541             # Return the number of spaces (+ means shift right, - means shift left)
542             # that we would like to shift a group of lines with the same indentation
543             # to get them to line up with their opening parens
544              
545 4648 100       11778 return ref($indentation) ? $indentation->get_recoverable_spaces() : 0;
546             } ## end sub get_recoverable_spaces
547              
548             ######################################################
549             # CODE SECTION 3: Code to accept input and form groups
550             ######################################################
551              
552 44     44   365 use constant DEBUG_VALIGN => 0;
  44         87  
  44         2881  
553 44     44   199 use constant SC_LONG_LINE_DIFF => 12;
  44         90  
  44         7941  
554              
555             my %is_opening_token;
556             my %is_closing_token;
557             my %is_digit_char;
558             my %is_plus_or_minus;
559             my %is_if_or;
560             my %is_comma_token;
561             my %is_assignment;
562             my %is_good_marginal_alignment;
563              
564             BEGIN {
565              
566 44     44   363 $is_opening_token{$_} = 1 for qw# { ( [ #;
567 44         228 $is_closing_token{$_} = 1 for qw# } ) ] #;
568 44         577 $is_digit_char{$_} = 1 for qw# 0 1 2 3 4 5 6 7 8 9 #;
569 44         176 $is_plus_or_minus{$_} = 1 for qw# + - #;
570 44         172 $is_if_or{$_} = 1 for qw# if unless or || #;
571 44         107 $is_comma_token{$_} = 1 for ( '=>', COMMA );
572             $is_assignment{$_} = 1
573 44         397 for qw# = **= += *= &= <<= &&= -= /= |= >>= ||= //= .= %= ^= x= ^^= #;
574              
575             # We can be less restrictive in marginal cases at certain "good" alignments
576 44         73497 $is_good_marginal_alignment{$_} = 1 for ( COMMA, qw# { ? => = # );
577             }
578              
579             #--------------------------------------------
580             # VTFLAGS: Vertical tightness types and flags
581             #--------------------------------------------
582             # Vertical tightness is controlled by a 'type' and associated 'flags' for each
583             # line. These values are set by sub Formatter::set_vertical_tightness_flags.
584             # These are defined as follows:
585              
586             # Vertical Tightness Line Type Codes:
587             # Type 0, no vertical tightness condition
588             # Type 1, last token of this line is a non-block opening token
589             # Type 2, first token of next line is a non-block closing
590             # Type 3, isolated opening block brace
591             # type 4, isolated closing block brace
592              
593             # Opening token flag values are the vertical tightness flags
594             # 0 do not join with next line
595             # 1 just one join per line
596             # 2 any number of joins
597              
598             # Closing token flag values indicate spacing:
599             # 0 = no space added before closing token
600             # 1 = single space added before closing token
601              
602             sub valign_input {
603              
604 8510     8510 0 12790 my ( $self, $rcall_hash ) = @_;
605              
606             #---------------------------------------------------------------------
607             # This is the front door of the vertical aligner. On each call
608             # we receive one line of specially marked text for vertical alignment.
609             # We compare the line with the current group, and either:
610             # - the line joins the current group if alignments match, or
611             # - the current group is flushed and a new group is started
612             #---------------------------------------------------------------------
613             #
614             # The key input parameters describing each line are:
615             # $level = indentation level of this line
616             # $rfields = ref to array of fields
617             # $rpatterns = ref to array of patterns, one per field
618             # $rtokens = ref to array of tokens starting fields 1,2,..
619             # $rfield_lengths = ref to array of field display widths
620             #
621             # Here is an example of what this package does. In this example,
622             # we are trying to line up both the '=>' and the '#'.
623             #
624             # '18' => 'grave', # \`
625             # '19' => 'acute', # `'
626             # '20' => 'caron', # \v
627             # <-tabs-><f1-><--field 2 ---><-f3->
628             # | | | |
629             # | | | |
630             # col1 col2 col3 col4
631             #
632             # The calling routine has already broken the entire line into 3 fields as
633             # indicated. (So the work of identifying promising common tokens has
634             # already been done).
635             #
636             # In this example, there will be 2 tokens being matched: '=>' and '#'.
637             # They are the leading parts of fields 2 and 3, but we do need to know
638             # what they are so that we can dump a group of lines when these tokens
639             # change.
640             #
641             # The fields contain the actual characters of each field. The patterns
642             # are like the fields, but they contain mainly token types instead
643             # of tokens, so they have fewer characters. They are used to be
644             # sure we are matching fields of similar type.
645             #
646             # In this example, there will be 4 column indexes being adjusted. The
647             # first one is always at zero. The interior columns are at the start of
648             # the matching tokens, and the last one tracks the maximum line length.
649             #
650             # Each time a new line comes in, it joins the current vertical
651             # group if possible. Otherwise it causes the current group to be flushed
652             # and a new group is started.
653             #
654             # For each new group member, the column locations are increased, as
655             # necessary, to make room for the new fields. When the group is finally
656             # output, these column numbers are used to compute the amount of spaces of
657             # padding needed for each field.
658             #
659             # Programming note: the fields are assumed not to have any tab characters.
660             # Tabs have been previously removed except for tabs in quoted strings and
661             # side comments. Tabs in these fields can mess up the column counting.
662             # The log file warns the user if there are any such tabs.
663              
664             # Unpack the call args. This form is significantly faster than getting them
665             # one-by-one.
666             my (
667              
668             $Kend,
669             $break_alignment_after,
670             $break_alignment_before,
671             $ci_level,
672             $forget_side_comment,
673             $indentation,
674             $is_terminal_ternary,
675             $level,
676             $level_end,
677             $list_seqno,
678             $maximum_line_length,
679             $outdent_long_lines,
680             $rline_alignment,
681             $rvertical_tightness_flags,
682              
683             ) =
684              
685 8510         29135 @{$rcall_hash}{
686 8510         12463 qw(
687             Kend
688             break_alignment_after
689             break_alignment_before
690             ci_level
691             forget_side_comment
692             indentation
693             is_terminal_ternary
694             level
695             level_end
696             list_seqno
697             maximum_line_length
698             outdent_long_lines
699             rline_alignment
700             rvertical_tightness_flags
701             )
702             };
703              
704             my ( $rtokens, $rfields, $rpatterns, $rfield_lengths ) =
705 8510         9959 @{$rline_alignment};
  8510         13956  
706              
707             # The index '$Kend' is a value which passed along with the line text to sub
708             # 'write_code_line' for a convergence check.
709              
710             # number of fields is $jmax
711             # number of tokens between fields is $jmax-1
712 8510         9240 my $jmax = @{$rfields} - 1;
  8510         10596  
713              
714 8510 100       13559 my $leading_space_count =
715             ref($indentation) ? $indentation->get_spaces() : $indentation;
716              
717             # set outdented flag to be sure we either align within statements or
718             # across statement boundaries, but not both.
719 8510         12235 my $is_outdented =
720             $self->[_last_leading_space_count_] > $leading_space_count;
721 8510         10718 $self->[_last_leading_space_count_] = $leading_space_count;
722              
723             # Identify a hanging side comment. Hanging side comments have an empty
724             # initial field.
725 8510   100     19933 my $is_hanging_side_comment =
726             ( $jmax == 1 && $rtokens->[0] eq '#' && $rfields->[0] =~ /^\s*$/ );
727              
728             # Undo outdented flag for a hanging side comment
729 8510 100       13086 $is_outdented = 0 if ($is_hanging_side_comment);
730              
731             # Identify a block comment.
732 8510   100     20805 my $is_block_comment = $jmax == 0 && substr( $rfields->[0], 0, 1 ) eq '#';
733              
734             # Block comment .. update count
735 8510 100       12480 if ($is_block_comment) {
736 717         1129 $self->[_consecutive_block_comments_]++;
737             }
738              
739             # Not a block comment ..
740             # Forget side comment column if we saw 2 or more block comments,
741             # and reset the count
742             else {
743              
744 7793 100       12760 if ( $self->[_consecutive_block_comments_] > 1 ) {
745 78         308 $self->forget_side_comment();
746             }
747 7793         10153 $self->[_consecutive_block_comments_] = 0;
748             }
749              
750             # Reset side comment location if we are entering a new block from level 0.
751             # This is intended to keep them from drifting too far to the right.
752 8510 100       12599 if ($forget_side_comment) {
753 46         156 $self->forget_side_comment();
754             }
755              
756 8510         10148 my $is_balanced_line = $level_end == $level;
757              
758 8510         10857 my $group_level = $self->[_group_level_];
759 8510         9903 my $group_maximum_line_length = $self->[_group_maximum_line_length_];
760              
761 8510         8694 DEBUG_VALIGN && do {
762             my $nlines = $self->group_line_count();
763             print {*STDOUT}
764             "Entering valign_input: lines=$nlines new #fields= $jmax, leading_count=$leading_space_count, level=$level, group_level=$group_level, level_end=$level_end\n";
765             };
766              
767             # Validate cached line if necessary: If we can produce a container
768             # with just 2 lines total by combining an existing cached opening
769             # token with the closing token to follow, then we will mark both
770             # cached flags as valid.
771 8510         18024 my $cached_line_type = get_cached_line_type();
772 8510 100       13518 if ($cached_line_type) {
773 227         423 my $cached_line_opening_flag = get_cached_line_opening_flag();
774 227 100       473 if ($rvertical_tightness_flags) {
775 157         304 my $cached_seqno = get_cached_seqno();
776 157 100 100     594 if ( $cached_seqno
      100        
777             && $rvertical_tightness_flags->{_vt_seqno}
778             && $rvertical_tightness_flags->{_vt_seqno} == $cached_seqno )
779             {
780              
781             # Fix for b1187 and b1188: Normally this step is only done
782             # if the number of existing lines is 0 or 1. But to prevent
783             # blinking, this range can be controlled by the caller.
784             # If zero values are given we fall back on the range 0 to 1.
785 4         22 my $line_count = $self->group_line_count();
786 4         10 my $min_lines = $rvertical_tightness_flags->{_vt_min_lines};
787 4         9 my $max_lines = $rvertical_tightness_flags->{_vt_max_lines};
788 4 50       16 $min_lines = 0 if ( !$min_lines );
789 4 50       13 $max_lines = 1 if ( !$max_lines );
790 4 100 66     25 if ( ( $line_count >= $min_lines )
791             && ( $line_count <= $max_lines ) )
792             {
793 3   50     19 $rvertical_tightness_flags->{_vt_valid_flag} ||= 1;
794 3         10 set_cached_line_valid(1);
795             }
796             }
797             }
798              
799             # do not join an opening block brace (type 3, see VTFLAGS)
800             # with an unbalanced line unless requested with a flag value of 2
801 227 50 100     555 if ( $cached_line_type == 3
      66        
      66        
802             && !$self->group_line_count()
803             && $cached_line_opening_flag < 2
804             && !$is_balanced_line )
805             {
806 0         0 set_cached_line_valid(0);
807             }
808             }
809              
810             # shouldn't happen:
811 8510 50       13579 if ( $level < 0 ) { $level = 0 }
  0         0  
812              
813             # do not align code across indentation level changes
814             # or changes in the maximum line length
815             # or if vertical alignment is turned off
816 8510 100 66     50213 if (
      66        
      66        
      100        
      100        
      100        
      100        
817             $level != $group_level
818             || ( $group_maximum_line_length
819             && $maximum_line_length != $group_maximum_line_length )
820             || $is_outdented
821             || ( $is_block_comment && !$rOpts_valign_block_comments )
822             || ( !$is_block_comment
823             && !$rOpts_valign_side_comments
824             && !$rOpts_valign_code )
825             )
826             {
827              
828             $self->_flush_group_lines( $level - $group_level )
829 3269 100       3770 if ( @{ $self->[_rgroup_lines_] } );
  3269         8734  
830              
831 3269         4263 $group_level = $level;
832 3269         4081 $self->[_group_level_] = $group_level;
833 3269         4010 $self->[_group_maximum_line_length_] = $maximum_line_length;
834              
835             # Update leading spaces after the above flush because the leading space
836             # count may have been changed if the -icp flag is in effect
837 3269 100       5267 $leading_space_count =
838             ref($indentation) ? $indentation->get_spaces() : $indentation;
839             }
840              
841             # --------------------------------------------------------------------
842             # Collect outdentable block COMMENTS
843             # --------------------------------------------------------------------
844 8510 100       15781 if ( $self->[_group_type_] eq 'COMMENT' ) {
845 599 100 66     2208 if ( $is_block_comment
      66        
846             && $outdent_long_lines
847             && $leading_space_count == $self->[_comment_leading_space_count_] )
848             {
849              
850             # Note that for a comment group we are not storing a line
851             # but rather just the text and its length.
852 90         129 push @{ $self->[_rgroup_lines_] },
  90         289  
853             [ $rfields->[0], $rfield_lengths->[0], $Kend ];
854 90         261 return;
855             }
856             else {
857             $self->_flush_group_lines()
858 509 50       666 if ( @{ $self->[_rgroup_lines_] } );
  509         2538  
859             }
860             }
861              
862 8420         10934 my $rgroup_lines = $self->[_rgroup_lines_];
863 8420 100 100     14469 if ( $break_alignment_before && @{$rgroup_lines} ) {
  129         331  
864 30         81 $rgroup_lines->[-1]->{'end_group'} = 1;
865             }
866              
867             # --------------------------------------------------------------------
868             # add dummy fields for terminal ternary
869             # --------------------------------------------------------------------
870 8420         9055 my $j_terminal_match;
871              
872 8420 100 100     14077 if ( $is_terminal_ternary && @{$rgroup_lines} ) {
  16         47  
873 13         118 $j_terminal_match = fix_terminal_ternary(
874             {
875             old_line => $rgroup_lines->[-1],
876             rfields => $rfields,
877             rtokens => $rtokens,
878             rpatterns => $rpatterns,
879             rfield_lengths => $rfield_lengths,
880             group_level => $group_level,
881             }
882             );
883 13         34 $jmax = @{$rfields} - 1;
  13         21  
884             }
885              
886             # --------------------------------------------------------------------
887             # add dummy fields for else statement
888             # --------------------------------------------------------------------
889              
890             # Note the trailing space after 'else' here. If there were no space between
891             # the else and the next '{' then we would not be able to do vertical
892             # alignment of the '{'.
893 8420 100 100     16543 if ( $rfields->[0] eq 'else '
      66        
894 12         60 && @{$rgroup_lines}
895             && $is_balanced_line )
896             {
897 9         79 $j_terminal_match = fix_terminal_else(
898             {
899             old_line => $rgroup_lines->[-1],
900             rfields => $rfields,
901             rtokens => $rtokens,
902             rpatterns => $rpatterns,
903             rfield_lengths => $rfield_lengths,
904             }
905             );
906 9         29 $jmax = @{$rfields} - 1;
  9         13  
907             }
908              
909             # --------------------------------------------------------------------
910             # Handle simple line of code with no fields to match.
911             # --------------------------------------------------------------------
912 8420 100       12366 if ( $jmax <= 0 ) {
913 4874         6289 $self->[_zero_count_]++;
914              
915             # VSN PATCH for a single number, part 1.
916 4874   100     12957 my $is_numeric =
917             $rOpts_valign_signed_numbers && $rpatterns->[0] eq 'n,';
918              
919 4874 100 100     8484 if ( !$is_numeric
      100        
920 4833         11882 && @{$rgroup_lines}
921             && !get_recoverable_spaces( $rgroup_lines->[0]->{'indentation'} ) )
922             {
923              
924             # flush the current group if it has some aligned columns..
925             # or we haven't seen a comment lately
926 388 100 100     1295 if ( $rgroup_lines->[0]->{'jmax'} > 1
927             || $self->[_zero_count_] > 3 )
928             {
929             $self->_flush_group_lines()
930 352 50       466 if ( @{ $self->[_rgroup_lines_] } );
  352         1711  
931              
932             # Update '$rgroup_lines' - it will become a ref to empty array.
933             # This allows avoiding a call to get_group_line_count below.
934 352         631 $rgroup_lines = $self->[_rgroup_lines_];
935             }
936             }
937              
938             # start new COMMENT group if this comment may be outdented
939 4874 100 100     10206 if ( $is_block_comment
      66        
940             && $outdent_long_lines
941 596         1417 && !@{$rgroup_lines} )
942             {
943 596         1027 $self->[_group_type_] = 'COMMENT';
944 596         886 $self->[_comment_leading_space_count_] = $leading_space_count;
945 596         845 $self->[_group_maximum_line_length_] = $maximum_line_length;
946 596         813 push @{$rgroup_lines},
  596         1684  
947             [ $rfields->[0], $rfield_lengths->[0], $Kend ];
948 596         1897 return;
949             }
950              
951             # just write this line directly if no current group, no side comment,
952             # and no space recovery is needed,
953             # and not numeric - VSN PATCH for a single number, part 4.
954 4278 100 100     4743 if ( !@{$rgroup_lines}
  4278   100     15066  
955             && !$is_numeric
956             && !get_recoverable_spaces($indentation) )
957             {
958              
959 4187         29642 $self->valign_output_step_B(
960             {
961             leading_space_count => $leading_space_count,
962             line => $rfields->[0],
963             line_length => $rfield_lengths->[0],
964             side_comment_length => 0,
965             outdent_long_lines => $outdent_long_lines,
966             rvertical_tightness_flags => $rvertical_tightness_flags,
967             level => $level,
968             level_end => $level_end,
969             Kend => $Kend,
970             maximum_line_length => $maximum_line_length,
971             }
972             );
973 4187         16969 return;
974             }
975             }
976             else {
977 3546         4929 $self->[_zero_count_] = 0;
978             }
979              
980             # --------------------------------------------------------------------
981             # It simplifies things to create a zero length side comment
982             # if none exists.
983             # --------------------------------------------------------------------
984 3637 100 100     11419 if ( ( $jmax == 0 ) || ( $rtokens->[ $jmax - 1 ] ne '#' ) ) {
985 3289         4037 $jmax += 1;
986 3289         5519 $rtokens->[ $jmax - 1 ] = '#';
987 3289         5267 $rfields->[$jmax] = EMPTY_STRING;
988 3289         4235 $rfield_lengths->[$jmax] = 0;
989 3289         4782 $rpatterns->[$jmax] = '#';
990             }
991              
992             # --------------------------------------------------------------------
993             # create an object to hold this line
994             # --------------------------------------------------------------------
995              
996             # The hash keys below must match the list of keys in %valid_LINE_keys.
997             # Values in this hash are accessed directly, except for 'ralignments',
998             # rather than with get/set calls for efficiency.
999 3637         50701 my $new_line = Perl::Tidy::VerticalAligner::Line->new(
1000             {
1001             jmax => $jmax,
1002             rtokens => $rtokens,
1003             rfields => $rfields,
1004             rpatterns => $rpatterns,
1005             rfield_lengths => $rfield_lengths,
1006             indentation => $indentation,
1007             leading_space_count => $leading_space_count,
1008             outdent_long_lines => $outdent_long_lines,
1009             list_seqno => $list_seqno,
1010             list_type => EMPTY_STRING,
1011             is_hanging_side_comment => $is_hanging_side_comment,
1012             rvertical_tightness_flags => $rvertical_tightness_flags,
1013             is_terminal_ternary => $is_terminal_ternary,
1014             j_terminal_match => $j_terminal_match,
1015             end_group => $break_alignment_after,
1016             Kend => $Kend,
1017             ci_level => $ci_level,
1018             level => $level,
1019             level_end => $level_end,
1020             imax_pair => -1,
1021             maximum_line_length => $maximum_line_length,
1022              
1023             ralignments => [],
1024             }
1025             );
1026              
1027 3637         4738 DEVEL_MODE
1028             && check_keys( $new_line, \%valid_LINE_keys,
1029             "Checking line keys at line definition", 1 );
1030              
1031             # --------------------------------------------------------------------
1032             # Decide if this is a simple list of items.
1033             # We use this to be less restrictive in deciding what to align.
1034             # --------------------------------------------------------------------
1035 3637 100       7677 decide_if_list($new_line) if ($list_seqno);
1036              
1037             # --------------------------------------------------------------------
1038             # Append this line to the current group (or start new group)
1039             # --------------------------------------------------------------------
1040              
1041 3637         4317 push @{ $self->[_rgroup_lines_] }, $new_line;
  3637         6442  
1042 3637         5380 $self->[_group_maximum_line_length_] = $maximum_line_length;
1043              
1044             # output this group if it ends in a terminal else or ternary line
1045 3637 100 100     15612 if ( defined($j_terminal_match) ) {
    100          
1046             $self->_flush_group_lines()
1047 20 50       37 if ( @{ $self->[_rgroup_lines_] } );
  20         104  
1048             }
1049              
1050             # Force break after jump to lower level
1051             elsif ($level_end < $level
1052             || $is_closing_token{ substr( $rfields->[0], 0, 1 ) } )
1053             {
1054             $self->_flush_group_lines(-1)
1055 144 50       232 if ( @{ $self->[_rgroup_lines_] } );
  144         612  
1056             }
1057              
1058             else {
1059             ## ok: no output needed
1060             }
1061              
1062             # --------------------------------------------------------------------
1063             # Some old debugging stuff
1064             # --------------------------------------------------------------------
1065 3637         4158 DEBUG_VALIGN && do {
1066             print {*STDOUT} "exiting valign_input fields:";
1067             dump_array( @{$rfields} );
1068             print {*STDOUT} "exiting valign_input tokens:";
1069             dump_array( @{$rtokens} );
1070             print {*STDOUT} "exiting valign_input patterns:";
1071             dump_array( @{$rpatterns} );
1072             };
1073              
1074 3637         9918 return;
1075             } ## end sub valign_input
1076              
1077             sub join_hanging_comment {
1078              
1079 38     38 0 88 my ( $new_line, $old_line ) = @_;
1080              
1081             # Add dummy fields to a hanging side comment to make it look
1082             # like the first line in its potential group. This simplifies
1083             # the coding.
1084              
1085             # Given:
1086             # $new_line = ref to hash of the line to be possibly changed
1087             # $old_line = ref to hash of the previous reference line
1088             # Return:
1089             # true if new line modified
1090             # false otherwise
1091              
1092 38         58 my $jmax = $new_line->{'jmax'};
1093              
1094             # must be 2 fields
1095 38 50       85 return 0 unless ( $jmax == 1 );
1096 38         59 my $rtokens = $new_line->{'rtokens'};
1097              
1098             # the second field must be a comment
1099 38 50       91 return 0 unless ( $rtokens->[0] eq '#' );
1100 38         59 my $rfields = $new_line->{'rfields'};
1101              
1102             # the first field must be empty
1103 38 50       177 return 0 if ( $rfields->[0] !~ /^\s*$/ );
1104              
1105             # the current line must have fewer fields
1106 38         53 my $maximum_field_index = $old_line->{'jmax'};
1107 38 100       98 return 0
1108             if ( $maximum_field_index <= $jmax );
1109              
1110             # looks ok..
1111 3         7 my $rpatterns = $new_line->{'rpatterns'};
1112 3         5 my $rfield_lengths = $new_line->{'rfield_lengths'};
1113              
1114 3         4 $new_line->{'is_hanging_side_comment'} = 1;
1115              
1116 3         5 $jmax = $maximum_field_index;
1117 3         5 $new_line->{'jmax'} = $jmax;
1118 3         7 $rfields->[$jmax] = $rfields->[1];
1119 3         6 $rfield_lengths->[$jmax] = $rfield_lengths->[1];
1120 3         7 $rtokens->[ $jmax - 1 ] = $rtokens->[0];
1121 3         5 $rpatterns->[ $jmax - 1 ] = $rpatterns->[0];
1122              
1123 3         9 foreach my $j ( 1 .. $jmax - 1 ) {
1124 3         5 $rfields->[$j] = EMPTY_STRING;
1125 3         4 $rfield_lengths->[$j] = 0;
1126 3         5 $rtokens->[ $j - 1 ] = EMPTY_STRING;
1127 3         6 $rpatterns->[ $j - 1 ] = EMPTY_STRING;
1128             }
1129 3         7 return 1;
1130             } ## end sub join_hanging_comment
1131              
1132             sub decide_if_list {
1133              
1134 1205     1205 0 1607 my $line = shift;
1135              
1136             # Given:
1137             # $line = ref to hash of values for a line
1138             # Task:
1139             # Set 'list_type' property
1140              
1141             # A list will be taken to be a line with a forced break in which all
1142             # of the field separators are commas or comma-arrows (except for the
1143             # trailing #)
1144              
1145 1205         1877 my $rtokens = $line->{'rtokens'};
1146 1205         1826 my $test_token = $rtokens->[0];
1147 1205         2561 my ( $raw_tok, $lev, $tag, $tok_count ) =
1148             decode_alignment_token($test_token);
1149 1205 100       2867 if ( $is_comma_token{$raw_tok} ) {
1150 1040         1405 my $list_type = $test_token;
1151 1040         1342 my $jmax = $line->{'jmax'};
1152              
1153 1040         2304 foreach ( 1 .. $jmax - 2 ) {
1154 1077         1469 ( $raw_tok, $lev, $tag, $tok_count ) =
1155             decode_alignment_token( $rtokens->[$_] );
1156 1077 100       2037 if ( !$is_comma_token{$raw_tok} ) {
1157 26         36 $list_type = EMPTY_STRING;
1158 26         48 last;
1159             }
1160             }
1161 1040         1800 $line->{'list_type'} = $list_type;
1162             }
1163 1205         1750 return;
1164             } ## end sub decide_if_list
1165              
1166             sub fix_terminal_ternary {
1167              
1168             # Add empty fields as necessary to align a ternary term
1169             # like this:
1170             #
1171             # my $leapyear =
1172             # $year % 4 ? 0
1173             # : $year % 100 ? 1
1174             # : $year % 400 ? 0
1175             # : 1;
1176             #
1177             # returns the index of the terminal question token, if any
1178              
1179 13     13 0 29 my ($rcall_hash) = @_;
1180              
1181 13         39 my $old_line = $rcall_hash->{old_line};
1182 13         30 my $rfields = $rcall_hash->{rfields};
1183 13         26 my $rtokens = $rcall_hash->{rtokens};
1184 13         43 my $rpatterns = $rcall_hash->{rpatterns};
1185 13         25 my $rfield_lengths = $rcall_hash->{rfield_lengths};
1186 13         37 my $group_level = $rcall_hash->{group_level};
1187              
1188 13 50       41 return if ( !$old_line );
1189 44     44   325 use constant EXPLAIN_TERNARY => 0;
  44         70  
  44         57281  
1190              
1191 13 50       42 if (%valign_control_hash) {
1192 0         0 my $align_ok = $valign_control_hash{'?'};
1193 0 0       0 $align_ok = $valign_control_default unless ( defined($align_ok) );
1194 0 0       0 return if ( !$align_ok );
1195             }
1196              
1197 13         20 my $jmax = @{$rfields} - 1;
  13         29  
1198 13         21 my $rfields_old = $old_line->{'rfields'};
1199              
1200 13         25 my $rpatterns_old = $old_line->{'rpatterns'};
1201 13         25 my $rtokens_old = $old_line->{'rtokens'};
1202 13         20 my $maximum_field_index = $old_line->{'jmax'};
1203              
1204             # look for the question mark after the :
1205 13         23 my ($jquestion);
1206             my $depth_question;
1207 13         23 my $pad = EMPTY_STRING;
1208 13         21 my $pad_length = 0;
1209 13         42 foreach my $j ( 0 .. $maximum_field_index - 1 ) {
1210 14         38 my $tok = $rtokens_old->[$j];
1211 14         47 my ( $raw_tok, $lev, $tag_uu, $tok_count_uu ) =
1212             decode_alignment_token($tok);
1213 14 100       48 if ( $raw_tok eq '?' ) {
1214 13         21 $depth_question = $lev;
1215              
1216             # depth must be correct
1217 13 50       41 next if ( $depth_question ne $group_level );
1218              
1219 13         21 $jquestion = $j;
1220 13 50       82 if ( $rfields_old->[ $j + 1 ] =~ /^(\?\s*)/ ) {
1221 13         27 $pad_length = length($1);
1222 13         39 $pad = SPACE x $pad_length;
1223             }
1224             else {
1225 0         0 return; # shouldn't happen
1226             }
1227 13         28 last;
1228             }
1229             }
1230 13 50       58 return if ( !defined($jquestion) ); # shouldn't happen
1231              
1232             # Now splice the tokens and patterns of the previous line
1233             # into the else line to insure a match. Add empty fields
1234             # as necessary.
1235 13         22 my $jadd = $jquestion;
1236              
1237             # Work on copies of the actual arrays in case we have
1238             # to return due to an error
1239 13         20 my @fields = @{$rfields};
  13         31  
1240 13         27 my @patterns = @{$rpatterns};
  13         33  
1241 13         27 my @tokens = @{$rtokens};
  13         27  
1242 13         18 my @field_lengths = @{$rfield_lengths};
  13         28  
1243              
1244 13         21 EXPLAIN_TERNARY && do {
1245             local $LIST_SEPARATOR = '><';
1246             print {*STDOUT} "CURRENT FIELDS=<@{$rfields_old}>\n";
1247             print {*STDOUT} "CURRENT TOKENS=<@{$rtokens_old}>\n";
1248             print {*STDOUT} "CURRENT PATTERNS=<@{$rpatterns_old}>\n";
1249             print {*STDOUT} "UNMODIFIED FIELDS=<@{$rfields}>\n";
1250             print {*STDOUT} "UNMODIFIED TOKENS=<@{$rtokens}>\n";
1251             print {*STDOUT} "UNMODIFIED PATTERNS=<@{$rpatterns}>\n";
1252             };
1253              
1254             # handle cases of leading colon on this line
1255 13 50       69 if ( $fields[0] =~ /^(:\s*)(.*)$/ ) {
1256              
1257 13         59 my ( $colon, $therest ) = ( $1, $2 );
1258              
1259             # Handle sub-case of first field with leading colon plus additional code
1260             # This is the usual situation as at the '1' below:
1261             # ...
1262             # : $year % 400 ? 0
1263             # : 1;
1264 13 50       36 if ($therest) {
1265              
1266             # Split the first field after the leading colon and insert padding.
1267             # Note that this padding will remain even if the terminal value goes
1268             # out on a separate line. This does not seem to look to bad, so no
1269             # mechanism has been included to undo it.
1270 13         28 my $field1_uu = shift @fields;
1271 13         26 my $field_length1 = shift @field_lengths;
1272 13         24 my $len_colon = length($colon);
1273 13         42 unshift @fields, ( $colon, $pad . $therest );
1274 13         31 unshift @field_lengths,
1275             ( $len_colon, $pad_length + $field_length1 - $len_colon );
1276              
1277             # change the leading pattern from : to ?
1278 13 50       74 return if ( $patterns[0] !~ s/^\:/?/ );
1279              
1280             # install leading tokens and patterns of existing line
1281 13         31 unshift( @tokens, @{$rtokens_old}[ 0 .. $jquestion ] );
  13         35  
1282 13         22 unshift( @patterns, @{$rpatterns_old}[ 0 .. $jquestion ] );
  13         31  
1283              
1284             # insert appropriate number of empty fields
1285 13 100       32 splice( @fields, 1, 0, (EMPTY_STRING) x $jadd ) if ($jadd);
1286 13 100       64 splice( @field_lengths, 1, 0, (0) x $jadd ) if ($jadd);
1287             }
1288              
1289             # handle sub-case of first field just equal to leading colon.
1290             # This can happen for example in the example below where
1291             # the leading '(' would create a new alignment token
1292             # : ( $name =~ /[]}]$/ ) ? ( $mname = $name )
1293             # : ( $mname = $name . '->' );
1294             else {
1295              
1296 0 0 0     0 return if ( $jmax <= 0 || $tokens[0] eq '#' ); # shouldn't happen
1297              
1298             # prepend a leading ? onto the second pattern
1299 0         0 $patterns[1] = "?b" . $patterns[1];
1300              
1301             # pad the second field
1302 0         0 $fields[1] = $pad . $fields[1];
1303 0         0 $field_lengths[1] = $pad_length + $field_lengths[1];
1304              
1305             # install leading tokens and patterns of existing line, replacing
1306             # leading token and inserting appropriate number of empty fields
1307 0         0 splice( @tokens, 0, 1, @{$rtokens_old}[ 0 .. $jquestion ] );
  0         0  
1308 0         0 splice( @patterns, 1, 0, @{$rpatterns_old}[ 1 .. $jquestion ] );
  0         0  
1309 0 0       0 splice( @fields, 1, 0, (EMPTY_STRING) x $jadd ) if ($jadd);
1310 0 0       0 splice( @field_lengths, 1, 0, (0) x $jadd ) if ($jadd);
1311             }
1312             }
1313              
1314             # Handle case of no leading colon on this line. This will
1315             # be the case when -wba=':' is used. For example,
1316             # $year % 400 ? 0 :
1317             # 1;
1318             else {
1319              
1320             # install leading tokens and patterns of existing line
1321 0         0 $patterns[0] = '?' . 'b' . $patterns[0];
1322 0         0 unshift( @tokens, @{$rtokens_old}[ 0 .. $jquestion ] );
  0         0  
1323 0         0 unshift( @patterns, @{$rpatterns_old}[ 0 .. $jquestion ] );
  0         0  
1324              
1325             # insert appropriate number of empty fields
1326 0         0 $jadd = $jquestion + 1;
1327 0         0 $fields[0] = $pad . $fields[0];
1328 0         0 $field_lengths[0] = $pad_length + $field_lengths[0];
1329 0 0       0 splice( @fields, 0, 0, (EMPTY_STRING) x $jadd ) if ($jadd);
1330 0 0       0 splice( @field_lengths, 0, 0, (0) x $jadd ) if ($jadd);
1331             }
1332              
1333 13         20 EXPLAIN_TERNARY && do {
1334             local $LIST_SEPARATOR = '><';
1335             print {*STDOUT} "MODIFIED TOKENS=<@tokens>\n";
1336             print {*STDOUT} "MODIFIED PATTERNS=<@patterns>\n";
1337             print {*STDOUT} "MODIFIED FIELDS=<@fields>\n";
1338             };
1339              
1340             # all ok .. update the arrays
1341 13         24 @{$rfields} = @fields;
  13         45  
1342 13         27 @{$rtokens} = @tokens;
  13         27  
1343 13         23 @{$rpatterns} = @patterns;
  13         31  
1344 13         18 @{$rfield_lengths} = @field_lengths;
  13         24  
1345              
1346             # force a flush after this line
1347 13         42 return $jquestion;
1348             } ## end sub fix_terminal_ternary
1349              
1350             sub fix_terminal_else {
1351              
1352 9     9 0 24 my ($rcall_hash) = @_;
1353              
1354             # Add empty fields as necessary to align a balanced terminal
1355             # else block to a previous if/elsif/unless block,
1356             # like this:
1357             #
1358             # if ( 1 || $x ) { print "ok 13\n"; }
1359             # else { print "not ok 13\n"; }
1360             #
1361             # returns a positive value if the else block should be indented
1362             #
1363              
1364 9         21 my $old_line = $rcall_hash->{old_line};
1365 9         21 my $rfields = $rcall_hash->{rfields};
1366 9         19 my $rtokens = $rcall_hash->{rtokens};
1367 9         17 my $rpatterns = $rcall_hash->{rpatterns};
1368 9         17 my $rfield_lengths = $rcall_hash->{rfield_lengths};
1369              
1370 9 50       36 return if ( !$old_line );
1371 9         17 my $jmax = @{$rfields} - 1;
  9         19  
1372 9 50       28 return if ( $jmax <= 0 );
1373              
1374 9 50       41 if (%valign_control_hash) {
1375 0         0 my $align_ok = $valign_control_hash{'{'};
1376 0 0       0 $align_ok = $valign_control_default unless ( defined($align_ok) );
1377 0 0       0 return if ( !$align_ok );
1378             }
1379              
1380             # check for balanced else block following if/elsif/unless
1381 9         16 my $rfields_old = $old_line->{'rfields'};
1382              
1383             # TBD: add handling for 'case'
1384 9 100       74 return if ( $rfields_old->[0] !~ /^(?:if|elsif|unless)\s*$/ );
1385              
1386             # look for the opening brace after the else, and extract the depth
1387 7         18 my $tok_brace = $rtokens->[0];
1388 7         15 my $depth_brace;
1389 7 50       32 if ( $tok_brace =~ /^\{(\d+)/ ) { $depth_brace = $1; }
  7         26  
1390              
1391             # probably: "else # side_comment"
1392 0         0 else { return }
1393              
1394 7         15 my $rpatterns_old = $old_line->{'rpatterns'};
1395 7         11 my $rtokens_old = $old_line->{'rtokens'};
1396 7         12 my $maximum_field_index = $old_line->{'jmax'};
1397              
1398             # be sure the previous if/elsif is followed by an opening paren
1399 7         13 my $jparen = 0;
1400 7         15 my $tok_paren = '(' . $depth_brace;
1401 7         11 my $tok_test = $rtokens_old->[$jparen];
1402 7 50       20 if ( $tok_test ne $tok_paren ) {
1403             ## no opening paren - possible syntax error - give up.
1404 0         0 return;
1405             }
1406              
1407             # Now find the opening block brace
1408 7         11 my ($jbrace);
1409 7         21 foreach my $j ( 1 .. $maximum_field_index - 1 ) {
1410 8         14 my $tok = $rtokens_old->[$j];
1411 8 100       22 if ( $tok eq $tok_brace ) {
1412 7         9 $jbrace = $j;
1413 7         15 last;
1414             }
1415             }
1416 7 50       44 if ( !defined($jbrace) ) {
1417             ## no opening brace - possible syntax error - give up.
1418 0         0 return;
1419             }
1420              
1421             # Now splice the tokens and patterns of the previous line
1422             # into the else line to insure a match. Add empty fields
1423             # as necessary.
1424 7         14 my $jadd = $jbrace - $jparen;
1425 7         12 splice( @{$rtokens}, 0, 0, @{$rtokens_old}[ $jparen .. $jbrace - 1 ] );
  7         18  
  7         37  
1426 7         13 splice( @{$rpatterns}, 1, 0, @{$rpatterns_old}[ $jparen + 1 .. $jbrace ] );
  7         17  
  7         15  
1427 7         13 splice( @{$rfields}, 1, 0, (EMPTY_STRING) x $jadd );
  7         15  
1428 7         15 splice( @{$rfield_lengths}, 1, 0, (0) x $jadd );
  7         16  
1429              
1430             # force a flush after this line if it does not follow a case
1431 7 50       35 if ( $rfields_old->[0] =~ /^case\s*$/ ) { return }
  0         0  
1432 7         21 else { return $jbrace }
1433             } ## end sub fix_terminal_else
1434              
1435             my %is_closing_block_type;
1436              
1437             BEGIN {
1438 44     44   1296 $is_closing_block_type{$_} = 1 for qw# } ] #;
1439             }
1440              
1441             # This is a flag for testing alignment by sub sweep_left_to_right only.
1442             # This test can help find problems with the alignment logic.
1443             # This flag should normally be zero.
1444 44     44   238 use constant TEST_SWEEP_ONLY => 0;
  44         69  
  44         2274  
1445              
1446 44     44   220 use constant EXPLAIN_CHECK_MATCH => 0;
  44         71  
  44         3574  
1447              
1448             sub check_match {
1449              
1450 1330     1330 0 2231 my ( $self, $new_line, $base_line, $prev_line, $group_line_count ) = @_;
1451              
1452             # See if the current line matches the current vertical alignment group.
1453              
1454             # Given:
1455             # $new_line = the line being considered for group inclusion
1456             # $base_line = the first line of the current group
1457             # $prev_line = the line just before $new_line
1458             # $group_line_count = number of lines in the current group
1459              
1460             # Returns: a flag and a value as follows:
1461             # return (0, $imax_align) if the line does not match
1462             # return (1, $imax_align) if the line matches but does not fit
1463             # return (2, $imax_align) if the line matches and fits
1464              
1465 44     44   203 use constant NO_MATCH => 0;
  44         90  
  44         1971  
1466 44     44   194 use constant MATCH_NO_FIT => 1;
  44         97  
  44         1625  
1467 44     44   181 use constant MATCH_AND_FIT => 2;
  44         70  
  44         65991  
1468              
1469             # Return value '$return_value' describes the match with 3 possible values
1470 1330         1533 my $return_value;
1471              
1472             # Return value '$imax_align' is the index of the maximum matching token.
1473             # It will be used in the subsequent left-to-right sweep to align as many
1474             # tokens as possible for lines which partially match.
1475 1330         1602 my $imax_align = -1;
1476              
1477             # variable $GoToMsg explains reason for no match, for debugging
1478 1330         1757 my $GoToMsg = EMPTY_STRING;
1479              
1480 1330         4108 my $jmax = $new_line->{'jmax'};
1481 1330         1808 my $maximum_field_index = $base_line->{'jmax'};
1482              
1483 1330         1713 my $jlimit = $jmax - 2;
1484 1330 100       2356 if ( $jmax > $maximum_field_index ) {
1485 102         183 $jlimit = $maximum_field_index - 2;
1486             }
1487              
1488 1330 100       2177 if ( $new_line->{'is_hanging_side_comment'} ) {
1489              
1490             # HSC's can join the group if they fit
1491             }
1492              
1493             # Everything else
1494             else {
1495              
1496             # A group with hanging side comments ends with the first non hanging
1497             # side comment.
1498 1292 50       2024 if ( $base_line->{'is_hanging_side_comment'} ) {
1499 0         0 $GoToMsg = "end of hanging side comments";
1500 0         0 $return_value = NO_MATCH;
1501             }
1502             else {
1503              
1504             # The number of tokens that this line shares with the previous
1505             # line has been stored with the previous line. This value was
1506             # calculated and stored by sub 'match_line_pair'.
1507 1292         1709 $imax_align = $prev_line->{'imax_pair'};
1508              
1509             # Only the following ci sequences are accepted (issue c225):
1510             # 0 0 0 ... OK
1511             # 0 1 1 ... OK but marginal*
1512             # 1 1 1 ... OK
1513             # This check is rarely activated, but for example we want
1514             # to avoid something like this 'tail wag dog' situation:
1515             # $tag =~ s/\b([a-z]+)/\L\u$1/gio;
1516             # $tag =~ s/\b([b-df-hj-np-tv-z]+)\b/\U$1/gio
1517             # if $tag =~ /-/;
1518             # *Note: we could set a flag for the 0 1 marginal case and
1519             # use it to prevent alignment of selected token types.
1520 1292         1631 my $ci_prev = $prev_line->{'ci_level'};
1521 1292         1541 my $ci_new = $new_line->{'ci_level'};
1522 1292 50 100     4056 if ( $ci_prev != $ci_new
    100 33        
      66        
1523             && $imax_align >= 0
1524             && ( $ci_new == 0 || $group_line_count > 1 ) )
1525             {
1526 0         0 $imax_align = -1;
1527 0         0 $GoToMsg =
1528             "Rejected ci: ci_prev=$ci_prev ci_new=$ci_new num=$group_line_count\n";
1529 0         0 $return_value = NO_MATCH;
1530             }
1531             elsif ( $imax_align != $jlimit ) {
1532 39         90 $GoToMsg = "Not all tokens match: $imax_align != $jlimit\n";
1533 39         64 $return_value = NO_MATCH;
1534             }
1535             else {
1536             ## ok: continue
1537             }
1538             }
1539             }
1540              
1541 1330 100       2294 if ( !defined($return_value) ) {
1542              
1543             # The tokens match, but the lines must have identical number of
1544             # tokens to join the group.
1545 1291 100 100     5656 if ( $maximum_field_index != $jmax ) {
    100          
1546 142         236 $GoToMsg = "token count differs";
1547 142         229 $return_value = NO_MATCH;
1548             }
1549              
1550             # The tokens match. Now See if there is space for this line in the
1551             # current group.
1552             elsif ( $self->check_fit( $new_line, $base_line ) && !TEST_SWEEP_ONLY )
1553             {
1554              
1555 1135         1935 $GoToMsg = "match and fit, imax_align=$imax_align, jmax=$jmax\n";
1556 1135         1355 $return_value = MATCH_AND_FIT;
1557 1135         1476 $imax_align = $jlimit;
1558             }
1559             else {
1560 14         29 $GoToMsg = "match but no fit, imax_align=$imax_align, jmax=$jmax\n";
1561 14         20 $return_value = MATCH_NO_FIT;
1562 14         20 $imax_align = $jlimit;
1563             }
1564             }
1565              
1566             EXPLAIN_CHECK_MATCH
1567 1330         1438 && print
1568             "returning $return_value because $GoToMsg, max match index =i $imax_align, jmax=$jmax\n";
1569              
1570 1330         2736 return ( $return_value, $imax_align );
1571             } ## end sub check_match
1572              
1573             sub check_fit {
1574              
1575 1149     1149 0 1836 my ( $self, $new_line, $old_line ) = @_;
1576              
1577             # The new line has alignments identical to the current group. Now we have
1578             # to fit the new line into the group without causing a field to exceed the
1579             # line length limit.
1580              
1581             # Given:
1582             # $new_line = ref to hash of the new line values
1583             # $old_line = ref to hash of the previous line values
1584             # Returns:
1585             # true if the new line alignments fit the old line
1586             # false otherwise
1587              
1588 1149         1523 my $jmax = $new_line->{'jmax'};
1589 1149         1537 my $leading_space_count = $new_line->{'leading_space_count'};
1590 1149         1560 my $rfield_lengths = $new_line->{'rfield_lengths'};
1591 1149         3159 my $padding_available = $old_line->get_available_space_on_right();
1592 1149         1677 my $jmax_old = $old_line->{'jmax'};
1593              
1594             # Safety check ... only lines with equal array sizes should arrive here
1595             # from sub check_match. So if this error occurs, look at recent changes in
1596             # sub check_match. It is only supposed to check the fit of lines with
1597             # identical numbers of alignment tokens.
1598 1149 50       2364 if ( $jmax_old ne $jmax ) {
1599              
1600 0         0 $self->warning(<<EOM);
1601             Program bug detected in Perl::Tidy::VerticalAligner sub check_fit
1602             unexpected difference in array lengths: $jmax != $jmax_old
1603             EOM
1604 0         0 return;
1605             }
1606              
1607             # Save current columns in case this line does not fit.
1608 1149         1385 my @alignments = @{ $old_line->{'ralignments'} };
  1149         2032  
1609 1149         1762 foreach my $alignment (@alignments) {
1610 4062         6048 $alignment->save_column();
1611             }
1612              
1613             # Loop over all alignments ...
1614 1149         1854 for my $j ( 0 .. $jmax ) {
1615              
1616 4043         6725 my $pad = $rfield_lengths->[$j] - $old_line->current_field_width($j);
1617              
1618 4043 100       5646 if ( $j == 0 ) {
1619 1149         1492 $pad += $leading_space_count;
1620             }
1621              
1622             # Keep going if this field does not need any space.
1623 4043 100       5792 next if ( $pad < 0 );
1624              
1625             # Revert to the starting state if does not fit
1626 2713 100       3827 if ( $pad > $padding_available ) {
1627              
1628             #----------------------------------------------
1629             # Line does not fit -- revert to starting state
1630             #----------------------------------------------
1631 14         19 foreach my $alignment (@alignments) {
1632 43         76 $alignment->restore_column();
1633             }
1634 14         41 return;
1635             }
1636              
1637             # make room for this field
1638 2699         5267 $old_line->increase_field_width( $j, $pad );
1639 2699         3213 $padding_available -= $pad;
1640             }
1641              
1642             #-------------------------------------
1643             # The line fits, the match is accepted
1644             #-------------------------------------
1645 1135         3760 return 1;
1646              
1647             } ## end sub check_fit
1648              
1649             sub install_new_alignments {
1650              
1651 2502     2502 0 3528 my ($new_line) = @_;
1652              
1653             # Given:
1654             # $new_line = ref to hash of a line starting a new group
1655             # Task:
1656             # setup alignment fields for this line
1657              
1658 2502         3565 my $jmax = $new_line->{'jmax'};
1659 2502         3603 my $rfield_lengths = $new_line->{'rfield_lengths'};
1660 2502         3257 my $col = $new_line->{'leading_space_count'};
1661              
1662 2502         3067 my @alignments;
1663 2502         4302 for my $j ( 0 .. $jmax ) {
1664 8399         9513 $col += $rfield_lengths->[$j];
1665              
1666             # create initial alignments for the new group
1667 8399         21529 my $alignment =
1668             Perl::Tidy::VerticalAligner::Alignment->new( { column => $col } );
1669 8399         12498 push @alignments, $alignment;
1670             }
1671 2502         4293 $new_line->{'ralignments'} = \@alignments;
1672 2502         3922 return;
1673             } ## end sub install_new_alignments
1674              
1675             sub copy_old_alignments {
1676 1135     1135 0 1587 my ( $new_line, $old_line ) = @_;
1677 1135         1375 my @new_alignments = @{ $old_line->{'ralignments'} };
  1135         2323  
1678 1135         1992 $new_line->{'ralignments'} = \@new_alignments;
1679 1135         1907 return;
1680             } ## end sub copy_old_alignments
1681              
1682             sub dump_array {
1683              
1684             # debug routine to dump array contents
1685 0     0 0 0 local $LIST_SEPARATOR = ')(';
1686 0         0 print {*STDOUT} "(@_)\n";
  0         0  
1687 0         0 return;
1688             } ## end sub dump_array
1689              
1690             sub level_change {
1691              
1692 10     10 0 14 my ( $self, $leading_space_count, $diff, $level ) = @_;
1693              
1694             # compute decrease in level when we remove $diff spaces from the
1695             # leading spaces
1696              
1697             # Given:
1698             # $leading_space_count = current leading line spaces
1699             # $diff = number of spaces to remove
1700             # $level = current indentation level
1701             # Return:
1702             # $level = updated level accounting for the loss of spaces
1703              
1704 10 50       31 if ($rOpts_indent_columns) {
1705 10         17 my $olev =
1706             int( ( $leading_space_count + $diff ) / $rOpts_indent_columns );
1707 10         19 my $nlev = int( $leading_space_count / $rOpts_indent_columns );
1708 10         16 $level -= ( $olev - $nlev );
1709 10 50       15 if ( $level < 0 ) { $level = 0 }
  0         0  
1710             }
1711 10         14 return $level;
1712             } ## end sub level_change
1713              
1714             ###############################################
1715             # CODE SECTION 4: Code to process comment lines
1716             ###############################################
1717              
1718             sub _flush_comment_lines {
1719              
1720 596     596   1041 my ($self) = @_;
1721              
1722             # Output a group consisting of COMMENT lines
1723              
1724 596         941 my $rgroup_lines = $self->[_rgroup_lines_];
1725 596 50       733 return if ( !@{$rgroup_lines} );
  596         1247  
1726 596         907 my $group_level = $self->[_group_level_];
1727 596         827 my $group_maximum_line_length = $self->[_group_maximum_line_length_];
1728 596         814 my $leading_space_count = $self->[_comment_leading_space_count_];
1729              
1730             # look for excessively long lines
1731 596         1177 my $max_excess = 0;
1732 596         842 foreach my $item ( @{$rgroup_lines} ) {
  596         1191  
1733 686         891 my ( $str_uu, $str_len ) = @{$item};
  686         3758  
1734 686         1137 my $excess =
1735             $str_len + $leading_space_count - $group_maximum_line_length;
1736 686 100       1820 if ( $excess > $max_excess ) {
1737 39         75 $max_excess = $excess;
1738             }
1739             }
1740              
1741             # zero leading space count if any lines are too long
1742 596 100       1411 if ( $max_excess > 0 ) {
1743 37         65 $leading_space_count -= $max_excess;
1744 37 50       121 if ( $leading_space_count < 0 ) { $leading_space_count = 0 }
  37         92  
1745 37         61 my $file_writer_object = $self->[_file_writer_object_];
1746 37         171 my $last_outdented_line_at =
1747             $file_writer_object->get_output_line_number();
1748 37         50 my $nlines = @{$rgroup_lines};
  37         61  
1749 37         75 $self->[_last_outdented_line_at_] =
1750             $last_outdented_line_at + $nlines - 1;
1751 37         54 my $outdented_line_count = $self->[_outdented_line_count_];
1752 37 100       97 if ( !$outdented_line_count ) {
1753 19         40 $self->[_first_outdented_line_at_] = $last_outdented_line_at;
1754             }
1755 37         48 $outdented_line_count += $nlines;
1756 37         69 $self->[_outdented_line_count_] = $outdented_line_count;
1757             }
1758              
1759             # write the lines
1760 596         804 my $outdent_long_lines = 0;
1761              
1762 596         768 foreach my $item ( @{$rgroup_lines} ) {
  596         940  
1763 686         827 my ( $str, $str_len, $Kend ) = @{$item};
  686         1273  
1764 686         7046 $self->valign_output_step_B(
1765             {
1766             leading_space_count => $leading_space_count,
1767             line => $str,
1768             line_length => $str_len,
1769             side_comment_length => 0,
1770             outdent_long_lines => $outdent_long_lines,
1771             rvertical_tightness_flags => undef,
1772             level => $group_level,
1773             level_end => $group_level,
1774             Kend => $Kend,
1775             maximum_line_length => $group_maximum_line_length,
1776             }
1777             );
1778             }
1779              
1780 596         2143 $self->initialize_for_new_group();
1781 596         865 return;
1782             } ## end sub _flush_comment_lines
1783              
1784             ######################################################
1785             # CODE SECTION 5: Code to process groups of code lines
1786             ######################################################
1787              
1788             sub _flush_group_lines {
1789              
1790 2649     2649   4572 my ( $self, ($level_jump) ) = @_;
1791              
1792             # This is the vertical aligner internal flush, which leaves the cache
1793             # intact
1794              
1795             # $level_jump = $next_level-$group_level, if known
1796             # = undef if not known
1797             # Note: only the sign of the jump is needed
1798              
1799 2649         3823 my $rgroup_lines = $self->[_rgroup_lines_];
1800 2649 50       3246 return if ( !@{$rgroup_lines} );
  2649         4855  
1801 2649         4231 my $group_type = $self->[_group_type_];
1802 2649         3430 my $group_level = $self->[_group_level_];
1803              
1804             # Debug
1805 2649         2803 0 && do {
1806             my ( $a, $b, $c ) = caller();
1807             my $nlines = @{$rgroup_lines};
1808             print {*STDOUT}
1809             "APPEND0: _flush_group_lines called from $a $b $c lines=$nlines, type=$group_type \n";
1810             };
1811              
1812             #-------------------------------------------
1813             # Section 1: Handle a group of COMMENT lines
1814             #-------------------------------------------
1815 2649 100       5452 if ( $group_type eq 'COMMENT' ) {
1816 596         1999 $self->_flush_comment_lines();
1817 596         1025 return;
1818             }
1819              
1820             #------------------------------------------------------------------------
1821             # Section 2: Handle line(s) of CODE. Most of the actual work of vertical
1822             # aligning happens here in the following steps:
1823             #------------------------------------------------------------------------
1824              
1825             # STEP 1: Remove most unmatched tokens. They block good alignments.
1826 2053         5815 my ( $max_lev_diff_uu, $saw_side_comment, $saw_signed_number ) =
1827             delete_unmatched_tokens( $rgroup_lines, $group_level );
1828              
1829             # STEP 2: Sweep top to bottom, forming subgroups of lines with exactly
1830             # matching common alignments. The indexes of these subgroups are in the
1831             # return variable.
1832 2053         6874 my $rgroups = $self->sweep_top_down( $rgroup_lines, $group_level );
1833              
1834             # STEP 3: Sweep left to right through the lines, looking for leading
1835             # alignment tokens shared by groups.
1836             sweep_left_to_right( $rgroup_lines, $rgroups, $group_level )
1837 2053 100       2565 if ( @{$rgroups} > 1 );
  2053         4807  
1838              
1839             # STEP 4: Move side comments to a common column if possible.
1840 2053 100       4226 if ($saw_side_comment) {
1841 222         879 $self->align_side_comments( $rgroup_lines, $rgroups );
1842             }
1843              
1844             # STEP 5: For the -lp option, increase the indentation of lists
1845             # to the desired amount, but do not exceed the line length limit.
1846              
1847             # We are allowed to shift a group of lines to the right if:
1848             # (1) its level is greater than the level of the previous group, and
1849             # (2) its level is greater than the level of the next line to be written.
1850              
1851 2053         2668 my $extra_indent_ok;
1852 2053 100       4411 if ( $group_level > $self->[_last_level_written_] ) {
1853              
1854             # Use the level jump to next line to come, if given
1855 1031 100       2002 if ( defined($level_jump) ) {
1856 678         1243 $extra_indent_ok = $level_jump < 0;
1857             }
1858              
1859             # Otherwise, assume the next line has the level of the end of last line.
1860             # This fixes case c008.
1861             else {
1862 353         669 my $level_end = $rgroup_lines->[-1]->{'level_end'};
1863 353         674 $extra_indent_ok = $group_level > $level_end;
1864             }
1865             }
1866              
1867 2053 100       4657 my $extra_leading_spaces =
1868             $extra_indent_ok
1869             ? get_extra_leading_spaces( $rgroup_lines, $rgroups )
1870             : 0;
1871              
1872             # STEP 6: add sign padding to columns numbers if needed
1873 2053 100 100     4640 pad_signed_number_columns($rgroup_lines)
1874             if ( $saw_signed_number && $rOpts_valign_signed_numbers );
1875              
1876             # STEP 7: pad wide equals
1877 2053 100       3674 pad_wide_equals_columns($rgroup_lines)
1878             if ($rOpts_valign_wide_equals);
1879              
1880             # STEP 8: Output the lines.
1881             # All lines in this group have the same leading spacing and maximum line
1882             # length
1883 2053         2916 my $group_leader_length = $rgroup_lines->[0]->{'leading_space_count'};
1884 2053         3160 my $group_maximum_line_length = $rgroup_lines->[0]->{'maximum_line_length'};
1885              
1886 2053         2539 foreach my $line ( @{$rgroup_lines} ) {
  2053         3032  
1887 3637         21042 $self->valign_output_step_A(
1888             {
1889             line => $line,
1890             min_ci_gap => 0,
1891             do_not_align => 0,
1892             group_leader_length => $group_leader_length,
1893             extra_leading_spaces => $extra_leading_spaces,
1894             level => $group_level,
1895             maximum_line_length => $group_maximum_line_length,
1896             }
1897             );
1898             }
1899              
1900             # Let the formatter know that this object has been processed and any
1901             # recoverable spaces have been handled. This is needed for setting the
1902             # closing paren location in -lp mode.
1903 2053         3531 my $object = $rgroup_lines->[0]->{'indentation'};
1904 2053 100       3826 if ( ref($object) ) { $object->set_recoverable_spaces(0) }
  94         287  
1905              
1906 2053         5905 $self->initialize_for_new_group();
1907 2053         3738 return;
1908             } ## end sub _flush_group_lines
1909              
1910             { ## closure for sub sweep_top_down
1911              
1912             my $rall_lines; # all of the lines
1913             my $grp_level; # level of all lines
1914             my $rgroups; # describes the partition of lines we will make here
1915             my $group_line_count; # number of lines in current partition
1916              
1917 44     44   77950 BEGIN { $rgroups = [] }
1918              
1919             sub initialize_for_new_rgroup {
1920 4555     4555 0 5098 $group_line_count = 0;
1921 4555         5291 return;
1922             }
1923              
1924             sub add_to_rgroup {
1925              
1926 3637     3637 0 5199 my ($jend) = @_;
1927              
1928             # Include the line at index $jend in the current alignment group
1929              
1930 3637         4552 my $rline = $rall_lines->[$jend];
1931              
1932 3637         4497 my $jbeg = $jend;
1933 3637 100       5804 if ( $group_line_count == 0 ) {
1934 2502         7926 install_new_alignments($rline);
1935             }
1936             else {
1937 1135         1250 my $rvals = pop @{$rgroups};
  1135         1591  
1938 1135         1531 $jbeg = $rvals->[0];
1939 1135         2175 copy_old_alignments( $rline, $rall_lines->[$jbeg] );
1940             }
1941 3637         4238 push @{$rgroups}, [ $jbeg, $jend, undef ];
  3637         6383  
1942 3637         4479 $group_line_count++;
1943 3637         4507 return;
1944             } ## end sub add_to_rgroup
1945              
1946             sub get_rgroup_jrange {
1947              
1948 1505 50   1505 0 1701 return if ( !@{$rgroups} );
  1505         2641  
1949 1505 50       2603 return if ( $group_line_count <= 0 );
1950 1505         1650 my ( $jbeg, $jend ) = @{ $rgroups->[-1] };
  1505         2555  
1951 1505         2479 return ( $jbeg, $jend );
1952             } ## end sub get_rgroup_jrange
1953              
1954             sub end_rgroup {
1955              
1956 2521     2521 0 3732 my ($imax_align) = @_;
1957              
1958             # End the current alignment group and set its maximum alignment field
1959             # Given:
1960             # $imax_align = maximum field to be vertically aligned
1961              
1962 2521 50       2906 return if ( !@{$rgroups} );
  2521         4491  
1963 2521 100       4473 return if ( $group_line_count <= 0 );
1964              
1965 2502         2895 my ( $jbeg, $jend ) = @{ pop @{$rgroups} };
  2502         5044  
  2502         4884  
1966 2502         3484 push @{$rgroups}, [ $jbeg, $jend, $imax_align ];
  2502         4744  
1967              
1968             # Undo some alignments of poor two-line combinations.
1969             # We had to wait until now to know the line count.
1970 2502 100       4958 if ( $jend - $jbeg == 1 ) {
1971 295         483 my $line_0 = $rall_lines->[$jbeg];
1972 295         504 my $line_1 = $rall_lines->[$jend];
1973              
1974 295         492 my $imax_pair = $line_1->{'imax_pair'};
1975 295 50       744 if ( $imax_pair > $imax_align ) { $imax_align = $imax_pair }
  0         0  
1976              
1977             ## flag for possible future use:
1978             ## my $is_isolated_pair = $imax_pair < 0
1979             ## && ( $jbeg == 0
1980             ## || $rall_lines->[ $jbeg - 1 ]->{'imax_pair'} < 0 );
1981              
1982             my $imax_prev =
1983 295 100       731 $jbeg > 0 ? $rall_lines->[ $jbeg - 1 ]->{'imax_pair'} : -1;
1984              
1985 295         1184 my ( $is_marginal, $imax_align_fix ) =
1986             is_marginal_match( $line_0, $line_1, $grp_level, $imax_align,
1987             $imax_prev );
1988 295 100       770 if ($is_marginal) {
1989 16         66 combine_fields( $line_0, $line_1, $imax_align_fix );
1990             }
1991             }
1992              
1993 2502         4709 initialize_for_new_rgroup();
1994 2502         3154 return;
1995             } ## end sub end_rgroup
1996              
1997             sub block_penultimate_match {
1998              
1999             # emergency reset to prevent sweep_left_to_right from trying to match a
2000             # failed terminal else match
2001 1 50   1 0 1 return if ( @{$rgroups} <= 1 );
  1         4  
2002 1         1 $rgroups->[-2]->[2] = -1;
2003 1         3 return;
2004             } ## end sub block_penultimate_match
2005              
2006             sub sweep_top_down {
2007              
2008 2053     2053 0 3760 my ( $self, $rlines, $group_level ) = @_;
2009              
2010             # This is the first of two major sweeps to find alignments.
2011             # The other is sweep_left_to_right.
2012              
2013             # Given:
2014             # $rlines = ref to hash of lines in this main alignment group
2015             # $group_level = common indentation level of these lines
2016             # Return:
2017             # $rgroups = ref to hash of subgroups created
2018              
2019             # Partition the set of lines into final alignment subgroups
2020             # and store the alignments with the lines.
2021              
2022             # The alignment subgroups we are making here are groups of consecutive
2023             # lines which have (1) identical alignment tokens and (2) do not
2024             # exceed the allowable maximum line length. A later sweep from
2025             # left-to-right ('sweep_lr') will handle additional alignments.
2026              
2027             # transfer args to closure variables
2028 2053         32138 $rall_lines = $rlines;
2029 2053         2843 $grp_level = $group_level;
2030 2053         3211 $rgroups = [];
2031 2053         9165 initialize_for_new_rgroup();
2032 2053 50       2419 if ( !@{$rlines} ) {
  2053         4135  
2033 0         0 DEVEL_MODE && Fault("Unexpected empty alignment group\n");
2034 0         0 return;
2035             }
2036              
2037             # Unset the _end_group flag for the last line if it set because it
2038             # is not needed and can causes problems for -lp formatting
2039 2053         3727 $rall_lines->[-1]->{'end_group'} = 0;
2040              
2041             # Loop over all lines ...
2042 2053         2809 my $jline = -1;
2043 2053         2682 foreach my $new_line ( @{$rall_lines} ) {
  2053         3644  
2044 3637         4130 $jline++;
2045              
2046             # Start a new subgroup if necessary
2047 3637 100       5887 if ( !$group_line_count ) {
2048 2132         4827 add_to_rgroup($jline);
2049 2132 100       4352 if ( $new_line->{'end_group'} ) {
2050 26         80 end_rgroup(-1);
2051             }
2052 2132         3634 next;
2053             }
2054              
2055 1505         2228 my $j_terminal_match = $new_line->{'j_terminal_match'};
2056 1505         2593 my ( $jbeg, $jend_uu ) = get_rgroup_jrange();
2057 1505 50       2802 if ( !defined($jbeg) ) {
2058              
2059             # safety check, shouldn't happen
2060 0         0 $self->warning(<<EOM);
2061             Program bug detected in Perl::Tidy::VerticalAligner sub sweep_top_down
2062             undefined index for group line count $group_line_count
2063             EOM
2064 0         0 $jbeg = $jline;
2065             }
2066 1505         2003 my $base_line = $rall_lines->[$jbeg];
2067              
2068             # Initialize a global flag saying if the last line of the group
2069             # should match end of group and also terminate the group. There
2070             # should be no returns between here and where the flag is handled
2071             # at the bottom.
2072 1505         1750 my $col_matching_terminal = 0;
2073 1505 100       2611 if ( defined($j_terminal_match) ) {
2074              
2075             # remember the column of the terminal ? or { to match with
2076 19         94 $col_matching_terminal =
2077             $base_line->get_column($j_terminal_match);
2078              
2079             # Ignore an undefined value as a defensive step; shouldn't
2080             # normally happen.
2081 19 50       52 $col_matching_terminal = 0
2082             unless ( defined($col_matching_terminal) );
2083             }
2084              
2085             # -------------------------------------------------------------
2086             # Allow hanging side comment to join current group, if any. The
2087             # only advantage is to keep the other tokens in the same group. For
2088             # example, this would make the '=' align here:
2089             # $ax = 1; # side comment
2090             # # hanging side comment
2091             # $boondoggle = 5; # side comment
2092             # $beetle = 5; # side comment
2093              
2094             # here is another example..
2095              
2096             # _rtoc_name_count => {}, # hash to track ..
2097             # _rpackage_stack => [], # stack to check ..
2098             # # name changes
2099             # _rlast_level => \$last_level, # brace indentation
2100             #
2101             #
2102             # If this were not desired, the next step could be skipped.
2103             # -------------------------------------------------------------
2104 1505 100       3663 if ( $new_line->{'is_hanging_side_comment'} ) {
    100          
2105 38         97 join_hanging_comment( $new_line, $base_line );
2106             }
2107              
2108             # If this line has no matching tokens, then flush out the lines
2109             # BEFORE this line unless both it and the previous line have side
2110             # comments. This prevents this line from pushing side comments out
2111             # to the right.
2112             elsif ( $new_line->{'jmax'} == 1 ) {
2113              
2114             # There are no matching tokens, so now check side comments.
2115             # Programming note: accessing arrays with index -1 is
2116             # risky in Perl, but we have verified there is at least one
2117             # line in the group and that there is at least one field,
2118             my $prev_comment =
2119 247         576 $rall_lines->[ $jline - 1 ]->{'rfields'}->[-1];
2120 247         437 my $side_comment = $new_line->{'rfields'}->[-1];
2121              
2122             # do not end group if both lines have side comments
2123 247 100 100     812 if ( !$side_comment || !$prev_comment ) {
2124              
2125             # Otherwise - VSN PATCH for a single number:
2126             # - do not end group if numeric and no side comment, or
2127             # - end if !numeric or side comment
2128 202         384 my $pat = $new_line->{'rpatterns'}->[0];
2129 202   66     922 my $is_numeric = $rOpts_valign_signed_numbers
2130             && ( $pat eq 'n,'
2131             || $pat eq 'n,b' );
2132 202 100 100     787 end_rgroup(-1) if ( !$is_numeric || $side_comment );
2133             }
2134             }
2135             else {
2136             ## ok: continue
2137             }
2138              
2139             # See if the new line matches and fits the current group,
2140             # if it still exists. Flush the current group if not.
2141 1505         1937 my $match_code;
2142 1505 100       2578 if ($group_line_count) {
2143 1330         3542 ( $match_code, my $imax_align ) =
2144             $self->check_match( $new_line, $base_line,
2145             $rall_lines->[ $jline - 1 ],
2146             $group_line_count );
2147 1330 100       2464 if ( $match_code != 2 ) { end_rgroup($imax_align) }
  195         444  
2148             }
2149              
2150             # Store the new line
2151 1505         2736 add_to_rgroup($jline);
2152              
2153 1505 100       4009 if ( defined($j_terminal_match) ) {
    100          
2154              
2155             # Decide if we should fix a terminal match. We can either:
2156             # 1. fix it and prevent the sweep_lr from changing it, or
2157             # 2. leave it alone and let sweep_lr try to fix it.
2158              
2159             # The current logic is to fix it if:
2160             # -it has not joined to previous lines,
2161             # -and either the previous subgroup has just 1 line, or
2162             # -this line matched but did not fit (so sweep won't work)
2163 19         38 my $fixit;
2164 19 100       58 if ( $group_line_count == 1 ) {
2165 3   66     16 $fixit ||= $match_code;
2166 3 100       9 if ( !$fixit ) {
2167 2 50       3 if ( @{$rgroups} > 1 ) {
  2         6  
2168 2         4 my ( $jbegx, $jendx ) = @{ $rgroups->[-2] };
  2         6  
2169 2         4 my $nlines = $jendx - $jbegx + 1;
2170 2   66     10 $fixit ||= $nlines <= 1;
2171             }
2172             }
2173             }
2174              
2175 19 100       45 if ($fixit) {
2176 2         3 $base_line = $new_line;
2177 2         6 my $col_now = $base_line->get_column($j_terminal_match);
2178              
2179             # Ignore an undefined value as a defensive step; shouldn't
2180             # normally happen.
2181 2 50       6 $col_now = 0 unless ( defined($col_now) );
2182              
2183 2         4 my $pad = $col_matching_terminal - $col_now;
2184 2         7 my $padding_available =
2185             $base_line->get_available_space_on_right();
2186 2 100 33     14 if ( $col_now && $pad > 0 && $pad <= $padding_available ) {
      66        
2187 1         4 $base_line->increase_field_width( $j_terminal_match,
2188             $pad );
2189             }
2190              
2191             # do not let sweep_left_to_right change an isolated 'else'
2192 2 100       6 if ( !$new_line->{'is_terminal_ternary'} ) {
2193 1         3 block_penultimate_match();
2194             }
2195             }
2196 19         61 end_rgroup(-1);
2197             }
2198              
2199             # end the group if we know we cannot match next line.
2200             elsif ( $new_line->{'end_group'} ) {
2201 53         141 end_rgroup(-1);
2202             }
2203              
2204             else {
2205             ## ok: continue
2206             }
2207             } ## end loop over lines
2208              
2209 2053         5389 end_rgroup(-1);
2210 2053         3328 return ($rgroups);
2211             } ## end sub sweep_top_down
2212             }
2213              
2214             sub two_line_pad {
2215              
2216 21     21 0 49 my ( $line_m, $line, $imax_min ) = @_;
2217              
2218             # Decide if two adjacent, isolated lines should be aligned
2219              
2220             # Given:
2221             # $line_m, $line = two isolated (list) lines
2222             # imax_min = number of common alignment tokens
2223             # Return:
2224             # $pad_max = maximum suggested pad distance
2225             # = 0 if alignment not recommended
2226              
2227             # Allow alignment if the difference in the two unpadded line lengths
2228             # is not more than either line length. The idea is to avoid
2229             # aligning lines with very different field lengths, like these two:
2230              
2231             # [
2232             # 'VARCHAR', DBI::SQL_VARCHAR, undef, "'", "'", undef, 0, 1,
2233             # 1, 0, 0, 0, undef, 0, 0
2234             # ];
2235              
2236             # Note that this is only for two lines which do not have alignment tokens
2237             # in common with any other lines. It is intended for lists, but it might
2238             # also be used for two non-list lines with a common leading '='.
2239              
2240 21         63 my $rfield_lengths = $line->{'rfield_lengths'};
2241 21         38 my $rfield_lengths_m = $line_m->{'rfield_lengths'};
2242              
2243             # Safety check - shouldn't happen
2244             return 0
2245 21         79 if ( $imax_min >= @{$rfield_lengths}
2246 21 50 33     36 || $imax_min >= @{$rfield_lengths_m} );
  21         55  
2247              
2248 21         30 my $lensum_m = 0;
2249 21         33 my $lensum = 0;
2250 21         48 foreach my $i ( 0 .. $imax_min ) {
2251 54         69 $lensum_m += $rfield_lengths_m->[$i];
2252 54         101 $lensum += $rfield_lengths->[$i];
2253             }
2254              
2255 21 100       69 my ( $lenmin, $lenmax ) =
2256             $lensum >= $lensum_m ? ( $lensum_m, $lensum ) : ( $lensum, $lensum_m );
2257              
2258 21         39 my $patterns_match;
2259 21 50 66     92 if ( $line_m->{'list_type'} && $line->{'list_type'} ) {
2260 17         29 $patterns_match = 1;
2261 17         25 my $rpatterns_m = $line_m->{'rpatterns'};
2262 17         29 my $rpatterns = $line->{'rpatterns'};
2263 17         32 foreach my $i ( 0 .. $imax_min ) {
2264 47         65 my $pat = $rpatterns->[$i];
2265 47         57 my $pat_m = $rpatterns_m->[$i];
2266              
2267             # VSN PATCH: allow numbers to match quotes
2268 47 50 66     99 if ( $pat_m ne $pat && length($pat_m) eq length($pat) ) {
2269 0         0 $pat =~ tr/n/Q/;
2270 0         0 $pat_m =~ tr/n/Q/;
2271             }
2272              
2273 47 100       95 if ( $pat ne $pat_m ) { $patterns_match = 0; last; }
  2         3  
  2         6  
2274             }
2275             }
2276              
2277 21         44 my $pad_max = $lenmax;
2278 21 50 66     75 if ( !$patterns_match && $lenmax > 2 * $lenmin ) { $pad_max = 0 }
  0         0  
2279              
2280 21         47 return $pad_max;
2281             } ## end sub two_line_pad
2282              
2283             sub sweep_left_to_right {
2284              
2285 301     301 0 651 my ( $rlines, $rgroups, $group_level ) = @_;
2286              
2287             # This is the second of two major sweeps to find alignments.
2288             # The other is sweep_top_down.
2289              
2290             # Given:
2291             # $rlines = ref to hash of lines in this main alignment group
2292             # $rgroups = ref to hash of subgroups
2293             # $group_level = common indentation level of these lines
2294             # Task:
2295             # add leading alignments where possible
2296              
2297             # So far we have divided the lines into groups having an equal number of
2298             # identical alignments. Here we are going to look for common leading
2299             # alignments between the different groups and align them when possible.
2300              
2301             # For example, the three lines below are in three groups because each line
2302             # has a different number of commas. In this routine we will sweep from
2303             # left to right, aligning the leading commas as we go, but stopping if we
2304             # hit the line length limit.
2305              
2306             # my ( $num, $numi, $numj, $xyza, $ka, $xyzb, $kb, $aff, $error );
2307             # my ( $i, $j, $error, $aff, $asum, $avec );
2308             # my ( $km, $area, $varea );
2309              
2310             # nothing to do if just one group
2311 301         428 my $ng_max = @{$rgroups} - 1;
  301         551  
2312 301 50       746 return if ( $ng_max <= 0 );
2313              
2314             #---------------------------------------------------------------------
2315             # Step 1: Loop over groups to find all common leading alignment tokens
2316             #---------------------------------------------------------------------
2317              
2318 301         3337 my $line;
2319             my $rtokens;
2320 301         0 my $imax; # index of maximum non-side-comment alignment token
2321 301         0 my $istop; # an optional stopping index
2322 301         0 my $jbeg; # starting line index
2323 301         0 my $jend; # ending line index
2324              
2325 301         0 my $line_m;
2326 301         0 my $rtokens_m;
2327 301         0 my $imax_m;
2328 301         0 my $istop_m;
2329 301         0 my $jbeg_m;
2330 301         0 my $jend_m;
2331              
2332 301         0 my $istop_mm;
2333              
2334             # Look at neighboring pairs of groups and form a simple list
2335             # of all common leading alignment tokens. Foreach such match we
2336             # store [$i, $ng], where
2337             # $i = index of the token in the line (0,1,...)
2338             # $ng is the second of the two groups with this common token
2339 301         0 my @icommon;
2340              
2341             # Hash to hold the maximum alignment change for any group
2342 301         0 my %max_move;
2343              
2344             # a small number of columns
2345 301         377 my $short_pad = 4;
2346              
2347 301         481 my $ng = -1;
2348 301         397 foreach my $item ( @{$rgroups} ) {
  301         513  
2349 750         875 $ng++;
2350              
2351 750         872 $istop_mm = $istop_m;
2352              
2353             # save _m values of previous group
2354 750         867 $line_m = $line;
2355 750         822 $rtokens_m = $rtokens;
2356 750         807 $imax_m = $imax;
2357 750         814 $istop_m = $istop;
2358 750         756 $jbeg_m = $jbeg;
2359 750         894 $jend_m = $jend;
2360              
2361             # Get values for this group. Note that we just have to use values for
2362             # one of the lines of the group since all members have the same
2363             # alignments.
2364 750         799 ( $jbeg, $jend, $istop ) = @{$item};
  750         1131  
2365              
2366 750         995 $line = $rlines->[$jbeg];
2367 750         972 $rtokens = $line->{'rtokens'};
2368 750         987 $imax = $line->{'jmax'} - 2;
2369 750 50       1311 $istop = -1 if ( !defined($istop) );
2370 750 50       1258 $istop = $imax if ( $istop > $imax );
2371              
2372             # Initialize on first group
2373 750 100       1252 next if ( $ng == 0 );
2374              
2375             # Use the minimum index limit of the two groups
2376 449 100       869 my $imax_min = $imax > $imax_m ? $imax_m : $imax;
2377              
2378             # Also impose a limit if given.
2379 449 100       826 if ( $istop_m < $imax_min ) {
2380 65         107 $imax_min = $istop_m;
2381             }
2382              
2383             # Special treatment of two one-line groups isolated from other lines,
2384             # unless they form a simple list or a terminal match. Otherwise the
2385             # alignment can look strange in some cases.
2386 449         804 my $list_type = $rlines->[$jbeg]->{'list_type'};
2387 449 100 100     3772 if (
      100        
      100        
      100        
      100        
      100        
      100        
      100        
2388             $jend == $jbeg
2389             && $jend_m == $jbeg_m
2390             && ( $ng == 1 || $istop_mm < 0 )
2391             && ( $ng == $ng_max || $istop < 0 )
2392             && !$line->{'j_terminal_match'}
2393              
2394             # Only do this for imperfect matches. This is normally true except
2395             # when two perfect matches cannot form a group because the line
2396             # length limit would be exceeded. In that case we can still try
2397             # to match as many alignments as possible.
2398             && ( $imax != $imax_m || $istop_m != $imax_m )
2399             )
2400             {
2401              
2402             # We will just align assignments and simple lists
2403 79 100       252 next if ( $imax_min < 0 );
2404             next
2405 26 100 100     160 if ( $rtokens->[0] !~ /^=\d/
2406             && !$list_type );
2407              
2408             # In this case we will limit padding to a short distance. This
2409             # is a compromise to keep some vertical alignment but prevent large
2410             # gaps, which do not look good for just two lines.
2411 21         81 my $pad_max =
2412             two_line_pad( $rlines->[$jbeg], $rlines->[$jbeg_m], $imax_min );
2413 21 50       56 next if ( !$pad_max );
2414 21         32 my $ng_m = $ng - 1;
2415 21         78 $max_move{"$ng_m"} = $pad_max;
2416 21         67 $max_move{"$ng"} = $pad_max;
2417             }
2418              
2419             # Loop to find all common leading tokens.
2420 391 100       923 if ( $imax_min >= 0 ) {
2421 98         194 foreach my $i ( 0 .. $imax_min ) {
2422 175         249 my $tok = $rtokens->[$i];
2423 175         255 my $tok_m = $rtokens_m->[$i];
2424 175 50       283 last if ( $tok ne $tok_m );
2425 175         417 push @icommon, [ $i, $ng, $tok ];
2426             }
2427             }
2428             }
2429 301 100       915 return unless (@icommon);
2430              
2431             #----------------------------------------------------------
2432             # Step 2: Reorder and consolidate the list into a task list
2433             #----------------------------------------------------------
2434              
2435             # We have to work first from lowest token index to highest, then by group,
2436             # sort our list first on token index then group number
2437 75 50       272 @icommon = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @icommon;
  199         435  
2438              
2439             # Make a task list of the form
2440             # [$i, ng_beg, $ng_end, $tok], ..
2441             # where
2442             # $i is the index of the token to be aligned
2443             # $ng_beg..$ng_end is the group range for this action
2444 75         108 my @todo;
2445 75         133 my ( $i, $ng_end, $tok );
2446 75         143 foreach my $item (@icommon) {
2447 175         231 my $ng_last = $ng_end;
2448 175         1283 my $i_last = $i;
2449 175         220 ( $i, $ng_end, $tok ) = @{$item};
  175         283  
2450 175         214 my $ng_beg = $ng_end - 1;
2451 175 100 100     544 if ( defined($ng_last) && $ng_beg == $ng_last && $i == $i_last ) {
      66        
2452 38         47 my $var = pop @todo;
2453 38         79 $ng_beg = $var->[1];
2454             }
2455 175         325 my ( $raw_tok, $lev, $tag_uu, $tok_count_uu ) =
2456             decode_alignment_token($tok);
2457 175         456 push @todo, [ $i, $ng_beg, $ng_end, $raw_tok, $lev ];
2458             }
2459              
2460             #------------------------------
2461             # Step 3: Execute the task list
2462             #------------------------------
2463             tap_dancer(
2464             {
2465 75         708 rlines => $rlines,
2466             rgroups => $rgroups,
2467             rtodo => \@todo,
2468             rmax_move => \%max_move,
2469             short_pad => $short_pad,
2470             group_level => $group_level,
2471             }
2472             );
2473 75         362 return;
2474             } ## end sub sweep_left_to_right
2475              
2476             { ## closure for sub tap_dancer
2477              
2478             my %is_good_alignment_token;
2479              
2480             BEGIN {
2481              
2482             # One of the most difficult aspects of vertical alignment is knowing
2483             # when not to align. Alignment can go from looking very nice to very
2484             # bad when overdone. In the sweep algorithm there are two special
2485             # cases where we may need to limit padding to a '$short_pad' distance
2486             # to avoid some very ugly formatting:
2487              
2488             # 1. Two isolated lines with partial alignment
2489             # 2. A 'tail-wag-dog' situation, in which a single terminal
2490             # line with partial alignment could cause a significant pad
2491             # increase in many previous lines if allowed to join the alignment.
2492              
2493             # For most alignment tokens, we will allow only a small pad to be
2494             # introduced (the hardwired $short_pad variable) . But for some 'good'
2495             # alignments we can be less restrictive.
2496              
2497             # The hash values are set so that:
2498             # if ($is_good_alignment_token{$raw_tok}) => best
2499             # if defined ($is_good_alignment_token{$raw_tok}) => good or best
2500              
2501             # Start by defining these 'good' alignments, which are allowed more
2502             # padding (so note the '0' hash value here):
2503             $is_good_alignment_token{$_} = 0
2504 44     44   460 for ( COMMA, qw# => = ? if unless or || { # );
2505              
2506             # Promote a few of these to 'best', with essentially no pad limit:
2507 44         94 $is_good_alignment_token{'='} = 1;
2508 44         91 $is_good_alignment_token{'if'} = 1;
2509 44         95 $is_good_alignment_token{'unless'} = 1;
2510 44         35639 $is_good_alignment_token{'=>'} = 1;
2511              
2512             } ## end BEGIN
2513              
2514             sub move_to_common_column {
2515              
2516 140     140 0 243 my ($rcall_hash) = @_;
2517              
2518             # This is a sub called by sub tap_dancer to
2519             # move the alignment column of token $itok to $col_want for a
2520             # sequence of groups.
2521              
2522 140         186 my $rlines = $rcall_hash->{rlines};
2523 140         214 my $rgroups = $rcall_hash->{rgroups};
2524 140         180 my $rmax_move = $rcall_hash->{rmax_move};
2525 140         169 my $ngb = $rcall_hash->{ngb};
2526 140         194 my $nge = $rcall_hash->{nge};
2527 140         177 my $itok = $rcall_hash->{itok};
2528 140         180 my $col_want = $rcall_hash->{col_want};
2529 140         232 my $raw_tok = $rcall_hash->{raw_tok};
2530              
2531 140 100 66     534 return if ( !defined($ngb) || $nge <= $ngb );
2532 128         230 foreach my $ng ( $ngb .. $nge ) {
2533              
2534 291         1420 my ( $jbeg, $jend_uu ) = @{ $rgroups->[$ng] };
  291         470  
2535 291         364 my $line = $rlines->[$jbeg];
2536 291         490 my $col = $line->get_column($itok);
2537 291         374 my $move = $col_want - $col;
2538 291 100       627 if ( $move > 0 ) {
    50          
2539              
2540             # limit padding increase in isolated two lines
2541             next
2542             if ( defined( $rmax_move->{$ng} )
2543             && $move > $rmax_move->{$ng}
2544 93 50 66     307 && !$is_good_alignment_token{$raw_tok} );
      33        
2545              
2546 93         227 $line->increase_field_width( $itok, $move );
2547             }
2548             elsif ( $move < 0 ) {
2549              
2550             # spot to take special action on failure to move
2551             }
2552             else {
2553             ## ok: (move==0)
2554             }
2555             }
2556 128         485 return;
2557             } ## end sub move_to_common_column
2558              
2559             sub tap_dancer {
2560              
2561 75     75 0 176 my ($rcall_hash) = @_;
2562              
2563             # This is the worker routine for sub 'sweep_left_to_right'. It makes
2564             # vertical alignments as it sweeps from left to right over groups
2565             # of lines which have been located and prepared by the caller.
2566              
2567 75         153 my $rlines = $rcall_hash->{rlines};
2568 75         138 my $rgroups = $rcall_hash->{rgroups};
2569 75         144 my $rtodo = $rcall_hash->{rtodo};
2570 75         114 my $rmax_move = $rcall_hash->{rmax_move};
2571 75         127 my $short_pad = $rcall_hash->{short_pad};
2572 75         120 my $group_level = $rcall_hash->{group_level};
2573              
2574             # $blocking_level[$nj is the level at a match failure between groups
2575             # $ng-1 and $ng
2576 75         117 my @blocking_level;
2577 75         179 my $group_list_type = $rlines->[0]->{'list_type'};
2578              
2579 75         142 foreach my $task ( @{$rtodo} ) {
  75         147  
2580 137         191 my ( $itok, $ng_beg, $ng_end, $raw_tok, $lev ) = @{$task};
  137         313  
2581              
2582             # Nothing to do for a single group
2583 137 50       275 next if ( $ng_end <= $ng_beg );
2584              
2585 137         284 my $ng_first; # index of the first group of a continuous sequence
2586             my $col_want; # the common alignment column of a sequence of groups
2587 137         0 my $col_limit; # maximum column before bumping into max line length
2588 137         167 my $line_count_ng_m = 0;
2589 137         172 my $jmax_m;
2590             my $it_stop_m;
2591              
2592             # Loop over the groups
2593             # 'ix_' = index in the array of lines
2594             # 'ng_' = index in the array of groups
2595             # 'it_' = index in the array of tokens
2596 137         184 my $ix_min = $rgroups->[$ng_beg]->[0];
2597 137         193 my $ix_max = $rgroups->[$ng_end]->[1];
2598 137         218 my $lines_total = $ix_max - $ix_min + 1;
2599 137         286 foreach my $ng ( $ng_beg .. $ng_end ) {
2600 312         358 my ( $ix_beg, $ix_end, $it_stop ) = @{ $rgroups->[$ng] };
  312         490  
2601 312         427 my $line_count_ng = $ix_end - $ix_beg + 1;
2602              
2603             # Important: note that since all lines in a group have a common
2604             # alignments object, we just have to work on one of the lines
2605             # (the first line). All of the rest will be changed
2606             # automatically.
2607 312         368 my $line = $rlines->[$ix_beg];
2608 312         369 my $jmax = $line->{'jmax'};
2609              
2610             # the maximum space without exceeding the line length:
2611 312         659 my $avail = $line->get_available_space_on_right();
2612 312         510 my $col = $line->get_column($itok);
2613 312         1276 my $col_max = $col + $avail;
2614              
2615             # Initialize on first group
2616 312 100       521 if ( !defined($col_want) ) {
2617 137         180 $ng_first = $ng;
2618 137         179 $col_want = $col;
2619 137         172 $col_limit = $col_max;
2620 137         157 $line_count_ng_m = $line_count_ng;
2621 137         145 $jmax_m = $jmax;
2622 137         176 $it_stop_m = $it_stop;
2623 137         233 next;
2624             }
2625              
2626             # RULE: Throw a blocking flag upon encountering a token level
2627             # different from the level of the first blocking token. For
2628             # example, in the following example, if the = matches get
2629             # blocked between two groups as shown, then we want to start
2630             # blocking matches at the commas, which are at deeper level, so
2631             # that we do not get the big gaps shown here:
2632              
2633             # my $unknown3 = pack( "v", -2 );
2634             # my $unknown4 = pack( "v", 0x09 );
2635             # my $unknown5 = pack( "VVV", 0x06, 0x00, 0x00 );
2636             # my $num_bbd_blocks = pack( "V", $num_lists );
2637             # my $root_startblock = pack( "V", $root_start );
2638             # my $unknown6 = pack( "VV", 0x00, 0x1000 );
2639              
2640             # On the other hand, it is okay to keep matching at the same
2641             # level such as in a simple list of commas and/or fat commas.
2642              
2643 175   66     380 my $is_blocked = defined( $blocking_level[$ng] )
2644             && $lev > $blocking_level[$ng];
2645              
2646             # TAIL-WAG-DOG RULE: prevent a 'tail-wag-dog' syndrome, meaning:
2647             # Do not let one or two lines with a **different number of
2648             # alignments** open up a big gap in a large block. For
2649             # example, we will prevent something like this, where the first
2650             # line pries open the rest:
2651              
2652             # $worksheet->write( "B7", "http://www.perl.com", undef, $format );
2653             # $worksheet->write( "C7", "", $format );
2654             # $worksheet->write( "D7", "", $format );
2655             # $worksheet->write( "D8", "", $format );
2656             # $worksheet->write( "D8", "", $format );
2657              
2658             # We should exclude from consideration two groups which are
2659             # effectively the same but separated because one does not
2660             # fit in the maximum allowed line length.
2661 175   100     363 my $is_same_group =
2662             $jmax == $jmax_m && $it_stop_m == $jmax_m - 2;
2663              
2664 175         237 my $lines_above = $ix_beg - $ix_min;
2665 175         228 my $lines_below = $lines_total - $lines_above;
2666              
2667             # Increase the tolerable gap for certain favorable factors
2668 175         185 my $factor = 1;
2669 175         231 my $top_level = $lev == $group_level;
2670              
2671             # Align best top level alignment tokens like '=', 'if', ...
2672             # A factor of 10 allows a gap of up to 40 spaces
2673 175 100 100     557 if ( $top_level && $is_good_alignment_token{$raw_tok} ) {
2674 56         69 $factor = 10;
2675             }
2676              
2677             # Otherwise allow some minimal padding of good alignments
2678             else {
2679              
2680 119 100 100     623 if (
      100        
2681              
2682             defined( $is_good_alignment_token{$raw_tok} )
2683              
2684             # We have to be careful if there are just 2 lines.
2685             # This two-line factor allows large gaps only for 2
2686             # lines which are simple lists with fewer items on the
2687             # second line. It gives results similar to previous
2688             # versions of perltidy.
2689             && (
2690             $lines_total > 2
2691             || ( $group_list_type
2692             && $jmax < $jmax_m
2693             && $top_level )
2694             )
2695             )
2696             {
2697 107         128 $factor += 1;
2698 107 100       220 if ($top_level) {
2699 71         88 $factor += 1;
2700             }
2701             }
2702             }
2703              
2704 175         206 my $is_big_gap;
2705 175 100       334 if ( !$is_same_group ) {
2706 146   66     850 $is_big_gap ||=
      33        
2707             ( $lines_above == 1
2708             || $lines_above == 2 && $lines_below >= 4 )
2709             && $col_want > $col + $short_pad * $factor;
2710 146   66     742 $is_big_gap ||=
      33        
2711             ( $lines_below == 1
2712             || $lines_below == 2 && $lines_above >= 4 )
2713             && $col > $col_want + $short_pad * $factor;
2714             }
2715              
2716             # if match is limited by gap size, stop aligning at this level
2717 175 50       321 if ($is_big_gap) {
2718 0         0 $blocking_level[$ng] = $lev - 1;
2719             }
2720              
2721             # quit and restart if it cannot join this batch
2722 175 50 100     949 if ( $col_want > $col_max
      66        
      66        
2723             || $col > $col_limit
2724             || $is_big_gap
2725             || $is_blocked )
2726             {
2727              
2728             # remember the level of the first blocking token
2729 12 100       26 if ( !defined( $blocking_level[$ng] ) ) {
2730 10         22 $blocking_level[$ng] = $lev;
2731             }
2732              
2733             move_to_common_column(
2734             {
2735 12         70 rlines => $rlines,
2736             rgroups => $rgroups,
2737             rmax_move => $rmax_move,
2738             ngb => $ng_first,
2739             nge => $ng - 1,
2740             itok => $itok,
2741             col_want => $col_want,
2742             raw_tok => $raw_tok,
2743             }
2744             );
2745 12         28 $ng_first = $ng;
2746 12         15 $col_want = $col;
2747 12         14 $col_limit = $col_max;
2748 12         26 $line_count_ng_m = $line_count_ng;
2749 12         12 $jmax_m = $jmax;
2750 12         18 $it_stop_m = $it_stop;
2751 12         24 next;
2752             }
2753              
2754 163         232 $line_count_ng_m += $line_count_ng;
2755              
2756             # update the common column and limit
2757 163 100       290 if ( $col > $col_want ) { $col_want = $col }
  49         72  
2758 163 100       373 if ( $col_max < $col_limit ) { $col_limit = $col_max }
  47         96  
2759              
2760             } ## end loop over groups
2761              
2762 137 100       275 if ( $ng_end > $ng_first ) {
2763 128         755 move_to_common_column(
2764             {
2765             rlines => $rlines,
2766             rgroups => $rgroups,
2767             rmax_move => $rmax_move,
2768             ngb => $ng_first,
2769             nge => $ng_end,
2770             itok => $itok,
2771             col_want => $col_want,
2772             raw_tok => $raw_tok,
2773             }
2774             );
2775             }
2776             } ## end loop over tasks
2777              
2778 75         168 return;
2779             } ## end sub tap_dancer
2780             }
2781              
2782             sub delete_selected_tokens {
2783              
2784 543     543 0 1005 my ( $line_obj, $ridel ) = @_;
2785              
2786             # Given:
2787             # $line_obj = the line to be modified
2788             # $ridel = a ref to list of indexes to be deleted
2789              
2790             # remove unused alignment token(s) to improve alignment chances
2791              
2792 543 50 33     2235 return if ( !defined($line_obj) || !defined($ridel) || !@{$ridel} );
  543   33     1313  
2793              
2794 543         910 my $jmax_old = $line_obj->{'jmax'};
2795 543         900 my $rfields_old = $line_obj->{'rfields'};
2796 543         856 my $rfield_lengths_old = $line_obj->{'rfield_lengths'};
2797 543         795 my $rpatterns_old = $line_obj->{'rpatterns'};
2798 543         780 my $rtokens_old = $line_obj->{'rtokens'};
2799 543         756 my $j_terminal_match = $line_obj->{'j_terminal_match'};
2800              
2801 44     44   357 use constant EXPLAIN_DELETE_SELECTED => 0;
  44         80  
  44         119272  
2802              
2803 543         1030 local $LIST_SEPARATOR = '> <';
2804 543         622 EXPLAIN_DELETE_SELECTED && print <<EOM;
2805             delete indexes: <@{$ridel}>
2806             old jmax: $jmax_old
2807             old tokens: <@{$rtokens_old}>
2808             old patterns: <@{$rpatterns_old}>
2809             old fields: <@{$rfields_old}>
2810             old field_lengths: <@{$rfield_lengths_old}>
2811             EOM
2812              
2813 543         748 my $rfields_new = [];
2814 543         705 my $rpatterns_new = [];
2815 543         681 my $rtokens_new = [];
2816 543         779 my $rfield_lengths_new = [];
2817              
2818             # Convert deletion list to a hash to allow any order, multiple entries,
2819             # and avoid problems with index values out of range
2820 543         701 my %delete_me = map { $_ => 1 } @{$ridel};
  893         2342  
  543         951  
2821              
2822 543         1071 my $pattern_0 = $rpatterns_old->[0];
2823 543         847 my $field_0 = $rfields_old->[0];
2824 543         748 my $field_length_0 = $rfield_lengths_old->[0];
2825 543         717 push @{$rfields_new}, $field_0;
  543         967  
2826 543         695 push @{$rfield_lengths_new}, $field_length_0;
  543         822  
2827 543         643 push @{$rpatterns_new}, $pattern_0;
  543         857  
2828              
2829             # Loop to either copy items or concatenate fields and patterns
2830 543         714 my $jmin_del;
2831 543         1018 foreach my $j ( 0 .. $jmax_old - 1 ) {
2832 1741         2099 my $token = $rtokens_old->[$j];
2833 1741         2319 my $field = $rfields_old->[ $j + 1 ];
2834 1741         2014 my $field_length = $rfield_lengths_old->[ $j + 1 ];
2835 1741         2203 my $pattern = $rpatterns_old->[ $j + 1 ];
2836 1741 100       2688 if ( !$delete_me{$j} ) {
2837 848         920 push @{$rtokens_new}, $token;
  848         1350  
2838 848         938 push @{$rfields_new}, $field;
  848         1158  
2839 848         938 push @{$rpatterns_new}, $pattern;
  848         1130  
2840 848         959 push @{$rfield_lengths_new}, $field_length;
  848         1404  
2841             }
2842             else {
2843 893 100       1503 if ( !defined($jmin_del) ) { $jmin_del = $j }
  543         758  
2844 893         1593 $rfields_new->[-1] .= $field;
2845 893         1140 $rfield_lengths_new->[-1] += $field_length;
2846 893         1425 $rpatterns_new->[-1] .= $pattern;
2847             }
2848             }
2849              
2850             # ----- x ------ x ------ x ------
2851             #t 0 1 2 <- token indexing
2852             #f 0 1 2 3 <- field and pattern
2853              
2854 543         674 my $jmax_new = @{$rfields_new} - 1;
  543         850  
2855 543         871 $line_obj->{'rtokens'} = $rtokens_new;
2856 543         801 $line_obj->{'rpatterns'} = $rpatterns_new;
2857 543         746 $line_obj->{'rfields'} = $rfields_new;
2858 543         722 $line_obj->{'rfield_lengths'} = $rfield_lengths_new;
2859 543         725 $line_obj->{'jmax'} = $jmax_new;
2860              
2861             # The value of j_terminal_match will be incorrect if we delete tokens prior
2862             # to it. We will have to give up on aligning the terminal tokens if this
2863             # happens.
2864 543 100 100     6716 if ( defined($j_terminal_match) && $jmin_del <= $j_terminal_match ) {
2865 1         2 $line_obj->{'j_terminal_match'} = undef;
2866             }
2867              
2868             # update list type -
2869 543 100       1128 if ( $line_obj->{'list_seqno'} ) {
2870              
2871             ## This works, but for efficiency see if we need to make a change:
2872             ## decide_if_list($line_obj);
2873              
2874             # An existing list will still be a list but with possibly different
2875             # leading token
2876 76         143 my $old_list_type = $line_obj->{'list_type'};
2877 76         131 my $new_list_type = EMPTY_STRING;
2878 76 100       369 if ( $rtokens_new->[0] =~ /^(=>|,)/ ) {
2879 49         82 $new_list_type = $rtokens_new->[0];
2880             }
2881 76 100 100     287 if ( !$old_list_type || $old_list_type ne $new_list_type ) {
2882 44         123 decide_if_list($line_obj);
2883             }
2884             }
2885              
2886 543         611 EXPLAIN_DELETE_SELECTED && print <<EOM;
2887              
2888             new jmax: $jmax_new
2889             new tokens: <@{$rtokens_new}>
2890             new patterns: <@{$rpatterns_new}>
2891             new fields: <@{$rfields_new}>
2892             EOM
2893 543         2543 return;
2894             } ## end sub delete_selected_tokens
2895              
2896             { ## closure for sub decode_alignment_token
2897              
2898             # This routine is called repeatedly for each token, so it needs to be
2899             # efficient. We can speed things up by remembering the inputs and outputs
2900             # in a hash.
2901             my %decoded_token;
2902              
2903             sub initialize_decode {
2904              
2905             # We will re-initialize the hash for each file. Otherwise, there is
2906             # a danger that the hash can become arbitrarily large if a very large
2907             # number of files is processed at once.
2908 658     658 0 4371 %decoded_token = ();
2909 658         899 return;
2910             } ## end sub initialize_decode
2911              
2912             sub decode_alignment_token {
2913              
2914 10967     10967 0 14113 my ($tok) = @_;
2915              
2916             # Unpack the values packed in an alignment token
2917              
2918             # Given:
2919             # $tok = an alignment token
2920             # Returns:
2921             # ( $raw_tok, $lev, $tag, $tok_count )
2922             #
2923             # Usage:
2924             # my ( $raw_tok, $lev, $tag, $tok_count ) =
2925             # decode_alignment_token($token);
2926              
2927             # Alignment tokens have a trailing decimal level and optional tag (for
2928             # commas):
2929             # For example, the first comma in the following line
2930             # sub banner { crlf; report( shift, '/', shift ); crlf }
2931             # is decorated as follows:
2932             # ,2+report-6 => (tok,lev,tag) =qw( , 2 +report-6)
2933              
2934             # An optional token count may be appended with a leading dot.
2935             # Currently this is only done for '=' tokens but this could change.
2936             # For example, consider the following line:
2937             # $nport = $port = shift || $name;
2938             # The first '=' may either be '=0' or '=0.1' [level 0, first equals]
2939             # The second '=' will be '=0.2' [level 0, second equals]
2940              
2941 10967 100       16902 if ( defined( $decoded_token{$tok} ) ) {
2942 9284         9304 return @{ $decoded_token{$tok} };
  9284         24872  
2943             }
2944              
2945 1683         3088 my ( $raw_tok, $lev, $tag, $tok_count ) = ( $tok, 0, EMPTY_STRING, 1 );
2946 1683 100       8169 if ( $tok =~ /^(\D+)(\d+)([^\.]*)(\.(\d+))?$/ ) {
2947 1322         3121 $raw_tok = $1;
2948 1322         2086 $lev = $2;
2949 1322 100       3143 $tag = $3 if ($3);
2950 1322 100       3177 $tok_count = $5 if ($5);
2951             }
2952 1683         4500 my @vals = ( $raw_tok, $lev, $tag, $tok_count );
2953 1683         6102 $decoded_token{$tok} = \@vals;
2954 1683         5545 return @vals;
2955             } ## end sub decode_alignment_token
2956             }
2957              
2958             sub delete_unmatched_tokens {
2959              
2960 2053     2053 0 3636 my ( $rlines, $group_level ) = @_;
2961              
2962             # Remove as many obviously un-needed alignment tokens as possible.
2963             # This will prevent them from interfering with the final alignment.
2964              
2965             # Given:
2966             # $rlines = ref to hash of all lines in this alignment group
2967             # $group_level = their comment indentation level
2968              
2969             # Return:
2970 2053         2648 my $max_lev_diff = 0; # used to avoid a call to prune_tree
2971 2053         2664 my $saw_side_comment = 0; # used to avoid a call for side comments
2972 2053         2473 my $saw_signed_number = 0; # used to avoid a call for -vsn
2973              
2974             # Handle no lines -- shouldn't happen
2975 2053 50       2441 return unless ( @{$rlines} );
  2053         3858  
2976              
2977             # Handle a single line
2978 2053 100       2386 if ( @{$rlines} == 1 ) {
  2053         4106  
2979 1373         2000 my $line = $rlines->[0];
2980 1373         2568 my $jmax = $line->{'jmax'};
2981 1373         2252 my $length = $line->{'rfield_lengths'}->[$jmax];
2982 1373         2052 $saw_side_comment = $length > 0;
2983 1373         4313 return ( $max_lev_diff, $saw_side_comment, $saw_signed_number );
2984             }
2985              
2986             # ignore hanging side comments in these operations
2987 680         1150 my @filtered = grep { !$_->{'is_hanging_side_comment'} } @{$rlines};
  2264         5073  
  680         1566  
2988 680         1287 my $rnew_lines = \@filtered;
2989              
2990 680         1000 $saw_side_comment = @filtered != @{$rlines};
  680         1220  
2991 680         1012 $max_lev_diff = 0;
2992              
2993             # nothing to do if all lines were hanging side comments
2994 680         869 my $jmax = @{$rnew_lines} - 1;
  680         1058  
2995 680 100       3980 return ( $max_lev_diff, $saw_side_comment, $saw_signed_number )
2996             if ( $jmax < 0 );
2997              
2998             #----------------------------------------------------
2999             # Create a hash of alignment token info for each line
3000             #----------------------------------------------------
3001 679         1853 ( my $rline_hashes, my $requals_info, $saw_side_comment, $max_lev_diff ) =
3002             make_alignment_info( $group_level, $rnew_lines, $saw_side_comment );
3003              
3004             #------------------------------------------------------------
3005             # Find independent subgroups of lines. Neighboring subgroups
3006             # do not have a common alignment token.
3007             #------------------------------------------------------------
3008 679         1098 my @subgroups;
3009 679         1329 push @subgroups, [ 0, $jmax ];
3010 679         1423 foreach my $jl ( 0 .. $jmax - 1 ) {
3011 1537 100       3013 if ( $rnew_lines->[$jl]->{'end_group'} ) {
3012 79         139 $subgroups[-1]->[1] = $jl;
3013 79         281 push @subgroups, [ $jl + 1, $jmax ];
3014             }
3015             }
3016              
3017             #-----------------------------------------------------------
3018             # PASS 1 over subgroups to remove unmatched alignment tokens
3019             #-----------------------------------------------------------
3020             delete_unmatched_tokens_main_loop(
3021 679         2311 $group_level, $rnew_lines, \@subgroups,
3022             $rline_hashes, $requals_info
3023             );
3024              
3025             #----------------------------------------------------------------
3026             # PASS 2: Construct a tree of matched lines and delete some small
3027             # deeper levels of tokens. They also block good alignments.
3028             #----------------------------------------------------------------
3029 679 100       3489 prune_alignment_tree($rnew_lines) if ($max_lev_diff);
3030              
3031             #--------------------------------------------
3032             # PASS 3: compare all lines for common tokens
3033             #--------------------------------------------
3034 679         2111 $saw_signed_number =
3035             match_line_pairs( $rlines, $rnew_lines, \@subgroups, $group_level );
3036              
3037 679         6799 return ( $max_lev_diff, $saw_side_comment, $saw_signed_number );
3038             } ## end sub delete_unmatched_tokens
3039              
3040             sub make_alignment_info {
3041              
3042 679     679 0 1567 my ( $group_level, $rnew_lines, $saw_side_comment ) = @_;
3043              
3044             # Create a hash of alignment token info for each line
3045             # This info will be used to find common alignments
3046              
3047             # Given:
3048             # $group_level = common indentation level
3049             # $rnew_lines = ref to hash of line info
3050             # $saw_side_comment = true if there is a side comment
3051             # Return:
3052             # $rline_hashes = ref to hash with new line vars
3053             # \@equals_info = ref to array with info on any '=' tokens
3054             # $saw_side_comment = updated side comment flag
3055             # $max_lev_diff = maximum level change seen
3056              
3057             #----------------
3058             # Loop over lines
3059             #----------------
3060 679         977 my $rline_hashes = [];
3061 679         932 my @equals_info;
3062 679         906 my $jmax = @{$rnew_lines} - 1;
  679         994  
3063 679         1101 my $max_lev_diff = 0;
3064 679         948 foreach my $line ( @{$rnew_lines} ) {
  679         1397  
3065 2216         2542 my $rhash = {};
3066 2216         3225 my $rtokens = $line->{'rtokens'};
3067 2216         2831 my $rpatterns = $line->{'rpatterns'};
3068 2216         2409 my $i = 0;
3069 2216         3799 my ( $i_eq, $tok_eq, $pat_eq );
3070 2216         0 my ( $lev_min, $lev_max );
3071 2216         2367 foreach my $tok ( @{$rtokens} ) {
  2216         2995  
3072 6103         7750 my ( $raw_tok, $lev, $tag, $tok_count ) =
3073             decode_alignment_token($tok);
3074              
3075 6103 100       9035 if ( $tok ne '#' ) {
3076 3887 100       5011 if ( !defined($lev_min) ) {
3077 2048         2276 $lev_min = $lev;
3078 2048         2387 $lev_max = $lev;
3079             }
3080             else {
3081 1839 100       2792 if ( $lev < $lev_min ) { $lev_min = $lev }
  84         141  
3082 1839 100       2594 if ( $lev > $lev_max ) { $lev_max = $lev }
  305         387  
3083             }
3084             }
3085             else {
3086 2216 100       3552 if ( !$saw_side_comment ) {
3087 2009         3235 my $length = $line->{'rfield_lengths'}->[ $i + 1 ];
3088 2009   66     4541 $saw_side_comment ||= $length;
3089             }
3090             }
3091              
3092             # Possible future upgrade: for multiple matches,
3093             # record [$i1, $i2, ..] instead of $i
3094 6103         14072 $rhash->{$tok} =
3095             [ $i, undef, undef, $raw_tok, $lev, $tag, $tok_count ];
3096              
3097             # remember the first equals at line level
3098 6103 100 100     12593 if ( !defined($i_eq) && $raw_tok eq '=' ) {
3099              
3100 637 100       1212 if ( $lev eq $group_level ) {
3101 520         662 $i_eq = $i;
3102 520         641 $tok_eq = $tok;
3103 520         799 $pat_eq = $rpatterns->[$i];
3104             }
3105             }
3106 6103         7305 $i++;
3107             }
3108 2216         2556 push @{$rline_hashes}, $rhash;
  2216         3166  
3109 2216         4810 push @equals_info, [ $i_eq, $tok_eq, $pat_eq ];
3110 2216 100       3375 if ( defined($lev_min) ) {
3111 2048         2719 my $lev_diff = $lev_max - $lev_min;
3112 2048 100       3837 if ( $lev_diff > $max_lev_diff ) { $max_lev_diff = $lev_diff }
  182         378  
3113             }
3114             }
3115              
3116             #----------------------------------------------------
3117             # Loop to compare each line pair and remember matches
3118             #----------------------------------------------------
3119 679         1023 my $rtok_hash = {};
3120 679         991 my $nr = 0;
3121 679         1530 foreach my $jl ( 0 .. $jmax - 1 ) {
3122 1537         1813 my $nl = $nr;
3123 1537         1733 $nr = 0;
3124 1537         1888 my $jr = $jl + 1;
3125 1537         2132 my $rhash_l = $rline_hashes->[$jl];
3126 1537         1921 my $rhash_r = $rline_hashes->[$jr];
3127 1537         1720 foreach my $tok ( keys %{$rhash_l} ) {
  1537         3741  
3128 3679 100       5506 if ( defined( $rhash_r->{$tok} ) ) {
3129 3118         3479 my $il = $rhash_l->{$tok}->[0];
3130 3118         3469 my $ir = $rhash_r->{$tok}->[0];
3131 3118         3516 $rhash_l->{$tok}->[2] = $ir;
3132 3118         3309 $rhash_r->{$tok}->[1] = $il;
3133 3118 100       4862 if ( $tok ne '#' ) {
3134 1581         1834 push @{ $rtok_hash->{$tok} }, ( $jl, $jr );
  1581         5512  
3135 1581         2168 $nr++;
3136             }
3137             }
3138             }
3139              
3140             # Set a line break if no matching tokens between these lines
3141             # (this is not strictly necessary now but does not hurt)
3142 1537 100 100     3733 if ( $nr == 0 && $nl > 0 ) {
3143 39         79 $rnew_lines->[$jl]->{'end_group'} = 1;
3144             }
3145              
3146             # Also set a line break if both lines have simple equals but with
3147             # different leading characters in patterns. This check is similar
3148             # to one in sub check_match, and will prevent sub
3149             # prune_alignment_tree from removing alignments which otherwise
3150             # should be kept. This fix is rarely needed, but it can
3151             # occasionally improve formatting.
3152             # For example:
3153             # my $name = $this->{Name};
3154             # $type = $this->ctype($genlooptype) if defined $genlooptype;
3155             # my $declini = ( $asgnonly ? "" : "\t$type *" );
3156             # my $cast = ( $type ? "($type *)" : "" );
3157             # The last two lines start with 'my' and will not match the
3158             # previous line starting with $type, so we do not want
3159             # prune_alignment tree to delete their ? : alignments at a deeper
3160             # level.
3161 1537         1762 my ( $i_eq_l, $tok_eq_l, $pat_eq_l ) = @{ $equals_info[$jl] };
  1537         2549  
3162 1537         1677 my ( $i_eq_r, $tok_eq_r, $pat_eq_r ) = @{ $equals_info[$jr] };
  1537         2217  
3163 1537 100 100     3940 if ( defined($i_eq_l) && defined($i_eq_r) ) {
3164              
3165             # Also, do not align equals across a change in ci level
3166             my $ci_jump = $rnew_lines->[$jl]->{'ci_level'} !=
3167 268         557 $rnew_lines->[$jr]->{'ci_level'};
3168              
3169 268 100 66     2070 if (
      66        
      100        
      100        
3170             $tok_eq_l eq $tok_eq_r
3171             && $i_eq_l == 0
3172             && $i_eq_r == 0
3173             && ( substr( $pat_eq_l, 0, 1 ) ne substr( $pat_eq_r, 0, 1 )
3174             || $ci_jump )
3175             )
3176             {
3177 13         37 $rnew_lines->[$jl]->{'end_group'} = 1;
3178             }
3179             }
3180             }
3181 679         2899 return ( $rline_hashes, \@equals_info, $saw_side_comment, $max_lev_diff );
3182             } ## end sub make_alignment_info
3183              
3184             sub delete_unmatched_tokens_main_loop {
3185              
3186 679     679 0 1479 my ( $group_level, $rnew_lines, $rsubgroups, $rline_hashes, $requals_info )
3187             = @_;
3188              
3189             #--------------------------------------------------------------
3190             # Main loop over subgroups to remove unmatched alignment tokens
3191             #--------------------------------------------------------------
3192              
3193             # flag to allow skipping pass 2 - not currently used
3194 679         923 my $saw_large_group;
3195              
3196 679         1357 my $has_terminal_match = $rnew_lines->[-1]->{'j_terminal_match'};
3197              
3198 679         938 foreach my $item ( @{$rsubgroups} ) {
  679         1125  
3199 758         1028 my ( $jbeg, $jend ) = @{$item};
  758         1309  
3200              
3201 758         3569 my $nlines = $jend - $jbeg + 1;
3202              
3203             #---------------------------------------------------
3204             # Look for complete if/elsif/else and ternary blocks
3205             #---------------------------------------------------
3206              
3207             # We are looking for a common '$dividing_token' like these:
3208              
3209             # if ( $b and $s ) { $p->{'type'} = 'a'; }
3210             # elsif ($b) { $p->{'type'} = 'b'; }
3211             # elsif ($s) { $p->{'type'} = 's'; }
3212             # else { $p->{'type'} = ''; }
3213             # ^----------- dividing_token
3214              
3215             # my $severity =
3216             # !$routine ? '[PFX]'
3217             # : $routine =~ /warn.*_d\z/ ? '[DS]'
3218             # : $routine =~ /ck_warn/ ? 'W'
3219             # : $routine =~ /ckWARN\d*reg_d/ ? 'S'
3220             # : $routine =~ /ckWARN\d*reg/ ? 'W'
3221             # : $routine =~ /vWARN\d/ ? '[WDS]'
3222             # : '[PFX]';
3223             # ^----------- dividing_token
3224              
3225             # Only look for groups which are more than 2 lines long. Two lines
3226             # can get messed up doing this, probably due to the various
3227             # two-line rules.
3228              
3229 758         3392 my $dividing_token;
3230             my %token_line_count;
3231 758 100       1783 if ( $nlines > 2 ) {
3232              
3233 357         766 foreach my $jj ( $jbeg .. $jend ) {
3234 1515         1662 my %seen;
3235 1515         1759 my $line = $rnew_lines->[$jj];
3236 1515         1810 my $rtokens = $line->{'rtokens'};
3237 1515         1505 foreach my $tok ( @{$rtokens} ) {
  1515         1909  
3238 4293 100       5901 if ( !$seen{$tok} ) {
3239 3618         3979 $seen{$tok}++;
3240 3618         5036 $token_line_count{$tok}++;
3241             }
3242             }
3243             }
3244              
3245 357         885 foreach my $tok ( keys %token_line_count ) {
3246 1096 100       1993 if ( $token_line_count{$tok} == $nlines ) {
3247 659 100 100     2629 if ( substr( $tok, 0, 1 ) eq '?'
      100        
3248             || substr( $tok, 0, 1 ) eq '{' && $tok =~ /^\{\d+if/ )
3249             {
3250 23         46 $dividing_token = $tok;
3251 23         59 last;
3252             }
3253             }
3254             }
3255             }
3256              
3257             #-------------------------------------------------------------
3258             # Loop over subgroup lines to remove unwanted alignment tokens
3259             #-------------------------------------------------------------
3260 758         1545 foreach my $jj ( $jbeg .. $jend ) {
3261 2216         2794 my $line = $rnew_lines->[$jj];
3262 2216         2753 my $rtokens = $line->{'rtokens'};
3263 2216         2531 my $rhash = $rline_hashes->[$jj];
3264 2216         2749 my $i_eq = $requals_info->[$jj]->[0];
3265 2216         2313 my @idel;
3266 2216         2315 my $imax = @{$rtokens} - 2;
  2216         2728  
3267 2216         2481 my $delete_above_level;
3268             my $deleted_assignment_token;
3269              
3270 2216         2482 my $saw_dividing_token = EMPTY_STRING;
3271 2216   100     7268 $saw_large_group ||= $nlines > 2 && $imax > 1;
      100        
3272              
3273             # Loop over all alignment tokens
3274 2216         3023 foreach my $i ( 0 .. $imax ) {
3275 3887         4575 my $tok = $rtokens->[$i];
3276 3887 50       5452 next if ( $tok eq '#' ); # shouldn't happen
3277             my ( $iii_uu, $il, $ir, $raw_tok, $lev, $tag_uu, $tok_count ) =
3278 3887         3806 @{ $rhash->{$tok} };
  3887         9823  
3279              
3280             #------------------------------------------------------
3281             # Here is the basic RULE: remove an unmatched alignment
3282             # which does not occur in the surrounding lines.
3283             #------------------------------------------------------
3284 3887   100     6814 my $delete_me = !defined($il) && !defined($ir);
3285              
3286             # Apply any user controls. Note that not all lines pass
3287             # this way so they have to be applied elsewhere too.
3288 3887         3868 my $align_ok = 1;
3289 3887 100       5503 if (%valign_control_hash) {
3290 49         59 $align_ok = $valign_control_hash{$raw_tok};
3291 49 100       75 $align_ok = $valign_control_default
3292             unless ( defined($align_ok) );
3293 49   100     98 $delete_me ||= !$align_ok;
3294             }
3295              
3296             # But now we modify this with exceptions...
3297              
3298             # EXCEPTION 1: If we are in a complete ternary or
3299             # if/elsif/else group, and this token is not on every line
3300             # of the group, should we delete it to preserve overall
3301             # alignment?
3302 3887 100       5142 if ($dividing_token) {
3303 163 100       274 if ( $token_line_count{$tok} >= $nlines ) {
3304 132   100     336 $saw_dividing_token ||= $tok eq $dividing_token;
3305             }
3306             else {
3307              
3308             # For shorter runs, delete toks to save alignment.
3309             # For longer runs, keep toks after the '{' or '?'
3310             # to allow sub-alignments within braces. The
3311             # number 5 lines is arbitrary but seems to work ok.
3312 31   66     105 $delete_me ||= ( $nlines < 5 || !$saw_dividing_token );
      100        
3313             }
3314             }
3315              
3316             # EXCEPTION 2: Remove all tokens above a certain level
3317             # following a previous deletion. For example, we have to
3318             # remove tagged higher level alignment tokens following a
3319             # '=>' deletion because the tags of higher level tokens
3320             # will now be incorrect. For example, this will prevent
3321             # aligning commas as follows after deleting the second '=>'
3322             # $w->insert(
3323             # ListBox => origin => [ 270, 160 ],
3324             # size => [ 200, 55 ],
3325             # );
3326 3887 100       5175 if ( defined($delete_above_level) ) {
3327 334 100       620 if ( $lev > $delete_above_level ) {
3328 160   100     321 $delete_me ||= 1;
3329             }
3330             else {
3331 174         236 $delete_above_level = undef;
3332             }
3333             }
3334              
3335             # EXCEPTION 3: Remove all but certain tokens after an
3336             # assignment deletion.
3337 3887 100 100     5348 if (
      100        
3338             $deleted_assignment_token
3339             && ( $lev > $group_level
3340             || !$is_if_or{$raw_tok} )
3341             )
3342             {
3343 62   100     160 $delete_me ||= 1;
3344             }
3345              
3346             # EXCEPTION 4: Do not touch the first line of a 2 line
3347             # terminal match, such as below, because j_terminal has
3348             # already been set.
3349             # if ($tag) { $tago = "<$tag>"; $tagc = "</$tag>"; }
3350             # else { $tago = $tagc = ''; }
3351             # But see snippets 'else1.t' and 'else2.t'
3352 3887 100 100     7413 $delete_me = 0
      100        
3353             if ( $jj == $jbeg
3354             && $has_terminal_match
3355             && $nlines == 2 );
3356              
3357             # EXCEPTION 5: misc additional rules for commas and equals
3358 3887 100 100     6630 if ( $delete_me && $tok_count == 1 ) {
3359              
3360             # okay to delete second and higher copies of a token
3361              
3362             # for a comma...
3363 840 100       1545 if ( $raw_tok eq COMMA ) {
3364              
3365             # Do not delete commas before an equals
3366 305 100 100     1911 $delete_me = 0
3367             if ( defined($i_eq) && $i < $i_eq );
3368              
3369             # Do not delete line-level commas
3370 305 100       593 $delete_me = 0 if ( $lev <= $group_level );
3371             }
3372              
3373             # For an assignment at group level..
3374 840 100 100     2325 if ( $is_assignment{$raw_tok}
3375             && $lev == $group_level )
3376             {
3377              
3378             # Do not delete if it is the last alignment of
3379             # multiple tokens; this will prevent some
3380             # undesirable alignments
3381 136 100 100     469 if ( $imax > 0 && $i == $imax ) {
3382 13         22 $delete_me = 0;
3383             }
3384              
3385             # Otherwise, set a flag to delete most
3386             # remaining tokens
3387 123         201 else { $deleted_assignment_token = $raw_tok }
3388             }
3389             }
3390              
3391             # Do not let a user exclusion be reactivated by above rules
3392 3887   66     8898 $delete_me ||= !$align_ok;
3393              
3394             #------------------------------------
3395             # Add this token to the deletion list
3396             #------------------------------------
3397 3887 100       6066 if ($delete_me) {
3398 778         986 push @idel, $i;
3399              
3400             # update deletion propagation flags
3401 778 100 66     1749 if ( !defined($delete_above_level)
3402             || $lev < $delete_above_level )
3403             {
3404              
3405             # delete all following higher level alignments
3406 618         742 $delete_above_level = $lev;
3407              
3408             # but keep deleting after => to next lower level
3409             # to avoid some bizarre alignments
3410 618 100       1447 if ( $raw_tok eq '=>' ) {
3411 55         118 $delete_above_level = $lev - 1;
3412             }
3413             }
3414             }
3415             } ## End loop over alignment tokens
3416              
3417             # Process all deletion requests for this line
3418 2216 100       4442 if (@idel) {
3419 483         1351 delete_selected_tokens( $line, \@idel );
3420             }
3421             } ## End loop over lines
3422             } ## End main loop over subgroups
3423              
3424 679         1333 return;
3425             } ## end sub delete_unmatched_tokens_main_loop
3426              
3427             sub match_line_pairs {
3428              
3429 679     679 0 1436 my ( $rlines, $rnew_lines, $rsubgroups, $group_level ) = @_;
3430              
3431             # Compare each pair of lines and save information about common matches
3432              
3433             # Given:
3434             # $rlines = list of lines including hanging side comments
3435             # $rnew_lines = list of lines without any hanging side comments
3436             # $rsubgroups = list of subgroups of the new lines
3437             # Return:
3438             # $saw_signed_number = true if a field has a signed number
3439             # (needed for --valign-signed-numbers)
3440              
3441             # NOTE: A possible future generalization would be to change
3442             # imax_pair => $imax_align into a ref with additional information:
3443             # imax_pair => [$imax_align, $rMsg, ... ]
3444             # This could eventually hold multi-level match info
3445              
3446             # Previous line vars
3447 679         2590 my ( $line_m, $rtokens_m, $rpatterns_m, $rfield_lengths_m, $imax_m,
3448             $list_type_m, $ci_level_m );
3449              
3450             # Current line vars
3451 679         0 my ( $line, $rtokens, $rpatterns, $rfield_lengths, $imax, $list_type,
3452             $ci_level );
3453              
3454             # Return parameter to avoid calls to sub pad_signed_number_columns
3455 679         0 my $saw_signed_number;
3456              
3457             # loop over subgroups
3458 679         993 foreach my $item ( @{$rsubgroups} ) {
  679         1209  
3459 758         992 my ( $jbeg, $jend ) = @{$item};
  758         1345  
3460 758         1289 my $nlines = $jend - $jbeg + 1;
3461 758 100       1704 next if ( $nlines <= 1 );
3462              
3463             # loop over lines in a subgroup
3464 657         1266 foreach my $jj ( $jbeg .. $jend ) {
3465              
3466 2115         2325 $line_m = $line;
3467 2115         2182 $rtokens_m = $rtokens;
3468 2115         2150 $rpatterns_m = $rpatterns;
3469 2115         2138 $rfield_lengths_m = $rfield_lengths;
3470 2115         4452 $imax_m = $imax;
3471 2115         2226 $list_type_m = $list_type;
3472 2115         2222 $ci_level_m = $ci_level;
3473              
3474 2115         2477 $line = $rnew_lines->[$jj];
3475 2115         2690 $rtokens = $line->{'rtokens'};
3476 2115         2708 $rpatterns = $line->{'rpatterns'};
3477 2115         2467 $rfield_lengths = $line->{'rfield_lengths'};
3478 2115         2107 $imax = @{$rtokens} - 2;
  2115         2499  
3479 2115         2865 $list_type = $line->{'list_type'};
3480 2115         2575 $ci_level = $line->{'ci_level'};
3481              
3482             # Quick approximate check for signed numbers in this line.
3483             # This speeds up large runs by about 0.5%
3484 2115 100       3138 if ( !$saw_signed_number ) {
3485              
3486 1942         2437 my $rfields = $line->{'rfields'};
3487 1942         2744 foreach my $i ( 0 .. $imax + 1 ) {
3488 4637 100       8013 next if ( index( $rpatterns->[$i], 'n' ) < 0 );
3489 954         1282 my $field = $rfields->[$i];
3490 954 100 100     2684 if ( index( $field, '-' ) >= 0
3491             || index( $field, '+' ) >= 0 )
3492             {
3493 72         116 $saw_signed_number = 1;
3494 72         136 last;
3495             }
3496             }
3497             }
3498              
3499             # nothing to do for first line
3500 2115 100       3413 next if ( $jj == $jbeg );
3501              
3502 1458         1765 my $ci_jump = $ci_level - $ci_level_m;
3503              
3504 1458 100       2304 my $imax_min = $imax_m < $imax ? $imax_m : $imax;
3505              
3506 1458         1659 my $imax_align = -1;
3507              
3508             # find number of leading common tokens
3509              
3510             #---------------------------------
3511             # No match to hanging side comment
3512             #---------------------------------
3513 1458 50 100     4041 if ( $line->{'is_hanging_side_comment'} ) {
    100          
3514              
3515             # Should not get here; HSC's have been filtered out
3516 0         0 $imax_align = -1;
3517             }
3518              
3519             #-----------------------------
3520             # Handle comma-separated lists
3521             #-----------------------------
3522             elsif ( $list_type && $list_type eq $list_type_m ) {
3523              
3524             # do not align lists across a ci jump with new list method
3525 526 50       888 if ($ci_jump) { $imax_min = -1 }
  0         0  
3526              
3527 526         676 my $i_nomatch = $imax_min + 1;
3528 526         758 foreach my $i ( 0 .. $imax_min ) {
3529 1049         1270 my $tok = $rtokens->[$i];
3530 1049         1188 my $tok_m = $rtokens_m->[$i];
3531 1049 50       1669 if ( $tok ne $tok_m ) {
3532 0         0 $i_nomatch = $i;
3533 0         0 last;
3534             }
3535             }
3536              
3537 526         674 $imax_align = $i_nomatch - 1;
3538             }
3539              
3540             #-----------------
3541             # Handle non-lists
3542             #-----------------
3543             else {
3544 932         1197 my $i_nomatch = $imax_min + 1;
3545 932         1363 foreach my $i ( 0 .. $imax_min ) {
3546 923         1205 my $tok = $rtokens->[$i];
3547 923         1087 my $tok_m = $rtokens_m->[$i];
3548 923 100       1503 if ( $tok ne $tok_m ) {
3549 32         48 $i_nomatch = $i;
3550 32         57 last;
3551             }
3552              
3553 891         1120 my $pat = $rpatterns->[$i];
3554 891         1040 my $pat_m = $rpatterns_m->[$i];
3555              
3556             # VSN PATCH: allow numbers to match quotes
3557 891 100       1364 if ( $pat_m ne $pat ) {
3558 200         302 $pat =~ tr/n/Q/;
3559 200         271 $pat_m =~ tr/n/Q/;
3560             }
3561              
3562             # If patterns don't match, we have to be careful...
3563 891 100       1524 if ( $pat_m ne $pat ) {
3564 188         322 my $pad =
3565             $rfield_lengths->[$i] - $rfield_lengths_m->[$i];
3566 188         972 my $match_code = compare_patterns(
3567             {
3568             group_level => $group_level,
3569             tok => $tok,
3570             pat => $pat,
3571             pat_m => $pat_m,
3572             pad => $pad,
3573             ## tok_m => $tok_m,
3574             }
3575             );
3576 188 100       562 if ($match_code) {
3577 7 100       20 if ( $match_code == 1 ) { $i_nomatch = $i }
  6 50       20  
3578 1         1 elsif ( $match_code == 2 ) { $i_nomatch = 0 }
3579             else { } ##ok
3580 7         15 last;
3581             }
3582             }
3583             }
3584 932         1142 $imax_align = $i_nomatch - 1;
3585             }
3586              
3587 1458         2337 $line_m->{'imax_pair'} = $imax_align;
3588              
3589             } ## end loop over lines
3590              
3591             # Put fence at end of subgroup
3592 657         1356 $line->{'imax_pair'} = -1;
3593              
3594             } ## end loop over subgroups
3595              
3596             # if there are hanging side comments, propagate the pair info down to them
3597             # so that lines can just look back one line for their pair info.
3598 679 100       977 if ( @{$rlines} > @{$rnew_lines} ) {
  679         942  
  679         1532  
3599 26         44 my $last_pair_info = -1;
3600 26         38 foreach my $line_t ( @{$rlines} ) {
  26         69  
3601 99 100       157 if ( $line_t->{'is_hanging_side_comment'} ) {
3602 41         73 $line_t->{'imax_pair'} = $last_pair_info;
3603             }
3604             else {
3605 58         77 $last_pair_info = $line_t->{'imax_pair'};
3606             }
3607             }
3608             }
3609 679         1485 return $saw_signed_number;
3610             } ## end sub match_line_pairs
3611              
3612             sub compare_patterns {
3613              
3614 188     188 0 319 my ($rcall_hash) = @_;
3615              
3616             # This is a helper routine for sub match_line_pairs to decide if patterns
3617             # in two lines match well enough
3618             # Given these values in $rcall_hash:
3619             # $tok_m, $pat_m = token and pattern of first line
3620             # $tok, $pat = token and pattern of second line
3621             # $pad = 0 if no padding is needed, !=0 otherwise
3622             # Return code:
3623             # 0 = patterns match, continue
3624             # 1 = no match
3625             # 2 = no match, and lines do not match at all
3626              
3627 188         298 my $group_level = $rcall_hash->{group_level};
3628 188         264 my $tok = $rcall_hash->{tok};
3629             ## my $tok_m = $rcall_hash->{tok_m};
3630 188         283 my $pat = $rcall_hash->{pat};
3631 188         257 my $pat_m = $rcall_hash->{pat_m};
3632 188         273 my $pad = $rcall_hash->{pad};
3633              
3634 188         242 my $GoToMsg = EMPTY_STRING;
3635 188         236 my $return_code = 0;
3636              
3637 44     44   341 use constant EXPLAIN_COMPARE_PATTERNS => 0;
  44         86  
  44         48754  
3638              
3639 188         388 my ( $alignment_token, $lev, $tag_uu, $tok_count_uu ) =
3640             decode_alignment_token($tok);
3641              
3642             # We have to be very careful about aligning commas
3643             # when the pattern's don't match, because it can be
3644             # worse to create an alignment where none is needed
3645             # than to omit one. Here's an example where the ','s
3646             # are not in named containers. The first line below
3647             # should not match the next two:
3648             # ( $a, $b ) = ( $b, $r );
3649             # ( $x1, $x2 ) = ( $x2 - $q * $x1, $x1 );
3650             # ( $y1, $y2 ) = ( $y2 - $q * $y1, $y1 );
3651 188 100       600 if ( $alignment_token eq COMMA ) {
    100          
    100          
3652              
3653             # do not align commas unless they are in named
3654             # containers
3655 26 100       98 if ( $tok !~ /[A-Za-z]/ ) {
3656 3         4 $return_code = 1;
3657 3         6 $GoToMsg = "do not align commas in unnamed containers";
3658             }
3659             else {
3660 23         35 $return_code = 0;
3661             }
3662             }
3663              
3664             # do not align parens unless patterns match;
3665             # large ugly spaces can occur in math expressions.
3666             elsif ( $alignment_token eq '(' ) {
3667              
3668             # But we can allow a match if the parens don't
3669             # require any padding.
3670 3 50       9 if ( $pad != 0 ) {
3671 3         5 $return_code = 1;
3672 3         3 $GoToMsg = "do not align '(' unless patterns match or pad=0";
3673             }
3674             else {
3675 0         0 $return_code = 0;
3676             }
3677             }
3678              
3679             # Handle an '=' alignment with different patterns to
3680             # the left.
3681             elsif ( $alignment_token eq '=' ) {
3682              
3683             # It is best to be a little restrictive when
3684             # aligning '=' tokens. Here is an example of
3685             # two lines that we will not align:
3686             # my $variable=6;
3687             # $bb=4;
3688             # The problem is that one is a 'my' declaration,
3689             # and the other isn't, so they're not very similar.
3690             # We will filter these out by comparing the first
3691             # letter of the pattern. This is crude, but works
3692             # well enough.
3693 20 50       101 if ( substr( $pat_m, 0, 1 ) ne substr( $pat, 0, 1 ) ) {
    100          
3694 0         0 $GoToMsg = "first character before equals differ";
3695 0         0 $return_code = 1;
3696             }
3697              
3698             # The introduction of sub 'prune_alignment_tree'
3699             # enabled alignment of lists left of the equals with
3700             # other scalar variables. For example:
3701             # my ( $D, $s, $e ) = @_;
3702             # my $d = length $D;
3703             # my $c = $e - $s - $d;
3704              
3705             # But this would change formatting of a lot of scripts,
3706             # so for now we prevent alignment of comma lists on the
3707             # left with scalars on the left. We will also prevent
3708             # any partial alignments.
3709              
3710             # set return code 2 if the = is at line level, but
3711             # set return code 1 if the = is below line level, i.e.
3712             # sub new { my ( $p, $v ) = @_; bless \$v, $p }
3713             # sub iter { my ($x) = @_; return undef if $$x < 0; return $$x--; }
3714              
3715             elsif (
3716             ( index( $pat_m, COMMA ) >= 0 ) ne ( index( $pat, COMMA ) >= 0 ) )
3717             {
3718 1         3 $GoToMsg = "mixed commas/no-commas before equals";
3719 1         2 $return_code = 1;
3720 1 50       4 if ( $lev eq $group_level ) {
3721 1         2 $return_code = 2;
3722             }
3723             }
3724             else {
3725 19         29 $return_code = 0;
3726             }
3727             }
3728             else {
3729 139         194 $return_code = 0;
3730             }
3731              
3732             EXPLAIN_COMPARE_PATTERNS
3733             && $return_code
3734 188         208 && print {*STDOUT} "no match because $GoToMsg\n";
3735              
3736 188         312 return $return_code;
3737              
3738             } ## end sub compare_patterns
3739              
3740             sub fat_comma_to_comma {
3741              
3742 845     845 0 1097 my ($str) = @_;
3743              
3744             # Given:
3745             # $str = a decorated fat comma alignment token
3746              
3747             # Change '=>' to ','
3748             # and remove any trailing decimal count because currently fat commas have a
3749             # count and commas do not.
3750              
3751             # For example, change '=>2+{-3.2' into ',2+{-3'
3752 845 100       1730 if ( $str =~ /^=>([^\.]*)/ ) { $str = COMMA . $1 }
  181         392  
3753 845         1395 return $str;
3754             } ## end sub fat_comma_to_comma
3755              
3756             sub get_line_token_info {
3757              
3758 174     174 0 350 my ($rlines) = @_;
3759              
3760             # Given:
3761             # $rlines = ref to array of lines in this group
3762              
3763             # Scan lines of tokens and return summary information about the range of
3764             # levels and patterns.
3765              
3766             # First scan to check monotonicity. Here is an example of several
3767             # lines which are monotonic. The = is the lowest level, and
3768             # the commas are all one level deeper. So this is not nonmonotonic.
3769             # $$d{"weeks"} = [ "w", "wk", "wks", "week", "weeks" ];
3770             # $$d{"days"} = [ "d", "day", "days" ];
3771             # $$d{"hours"} = [ "h", "hr", "hrs", "hour", "hours" ];
3772 174         241 my @all_token_info;
3773 174         256 my $all_monotonic = 1;
3774 174         260 foreach my $jj ( 0 .. @{$rlines} - 1 ) {
  174         423  
3775 703         943 my ($line) = $rlines->[$jj];
3776 703         908 my $rtokens = $line->{'rtokens'};
3777 703         719 my $last_lev;
3778 703         763 my $is_monotonic = 1;
3779 703         765 my $i = -1;
3780 703         708 foreach my $tok ( @{$rtokens} ) {
  703         937  
3781 1838         1768 $i++;
3782 1838         2152 my ( $raw_tok, $lev, $tag, $tok_count ) =
3783             decode_alignment_token($tok);
3784 1838         2126 push @{ $all_token_info[$jj] },
  1838         3725  
3785             [ $raw_tok, $lev, $tag, $tok_count ];
3786 1838 100       2859 last if ( $tok eq '#' );
3787 1135 100 100     2248 if ( $i > 0 && $lev < $last_lev ) { $is_monotonic = 0 }
  90         114  
3788 1135         1401 $last_lev = $lev;
3789             }
3790 703 100       1318 if ( !$is_monotonic ) { $all_monotonic = 0 }
  87         133  
3791             }
3792              
3793 174         326 my $rline_values = [];
3794 174         264 foreach my $jj ( 0 .. @{$rlines} - 1 ) {
  174         398  
3795 703         919 my ($line) = $rlines->[$jj];
3796              
3797 703         948 my $rtokens = $line->{'rtokens'};
3798 703         747 my $i = -1;
3799 703         780 my ( $lev_min, $lev_max );
3800 703         789 my $token_pattern_max = EMPTY_STRING;
3801 703         754 my %saw_level;
3802 703         740 my $is_monotonic = 1;
3803              
3804             # find the index of the last token before the side comment
3805 703         749 my $imax = @{$rtokens} - 2;
  703         808  
3806 703         780 my $imax_true = $imax;
3807              
3808             # If the entire group is monotonic, and the line ends in a comma list,
3809             # walk it back to the first such comma. this will have the effect of
3810             # making all trailing ragged comma lists match in the prune tree
3811             # routine. these trailing comma lists can better be handled by later
3812             # alignment rules.
3813              
3814             # Treat fat commas the same as commas here by converting them to
3815             # commas. This will improve the chance of aligning the leading parts
3816             # of ragged lists.
3817              
3818 703         1196 my $tok_end = fat_comma_to_comma( $rtokens->[$imax] );
3819 703 100 100     1831 if ( $all_monotonic && $tok_end =~ /^,/ ) {
3820 146         186 my $ii = $imax - 1;
3821 146   100     475 while ( $ii >= 0
3822             && fat_comma_to_comma( $rtokens->[$ii] ) eq $tok_end )
3823             {
3824 93         112 $imax = $ii;
3825 93         134 $ii--;
3826             } ## end while ( $ii >= 0 && fat_comma_to_comma...)
3827             }
3828              
3829             # make a first pass to find level range
3830 703         797 my $last_lev;
3831 703         772 foreach my $tok ( @{$rtokens} ) {
  703         924  
3832 1745         1710 $i++;
3833 1745 100       2325 last if ( $i > $imax );
3834 1042 50       1422 last if ( $tok eq '#' );
3835             my ( $raw_tok_uu, $lev, $tag_uu, $tok_count_uu ) =
3836 1042         1019 @{ $all_token_info[$jj]->[$i] };
  1042         1696  
3837              
3838 1042 50       1449 last if ( $tok eq '#' );
3839 1042         1453 $token_pattern_max .= $tok;
3840 1042         1249 $saw_level{$lev}++;
3841 1042 100       1471 if ( !defined($lev_min) ) {
3842 591         654 $lev_min = $lev;
3843 591         672 $lev_max = $lev;
3844             }
3845             else {
3846 451 100       659 if ( $lev < $lev_min ) { $lev_min = $lev; }
  60         78  
3847 451 100       646 if ( $lev > $lev_max ) { $lev_max = $lev; }
  140         171  
3848 451 100       658 if ( $lev < $last_lev ) { $is_monotonic = 0 }
  90         118  
3849             }
3850 1042         1189 $last_lev = $lev;
3851             }
3852              
3853             # handle no levels
3854 703         857 my $rtoken_patterns = {};
3855 703         784 my $rtoken_indexes = {};
3856 703         1836 my @levs = sort { $a <=> $b } keys %saw_level;
  207         617  
3857 703 100       1419 if ( !defined($lev_min) ) {
    100          
3858 112         160 $lev_min = -1;
3859 112         134 $lev_max = -1;
3860 112         179 $levs[0] = -1;
3861 112         261 $rtoken_patterns->{$lev_min} = EMPTY_STRING;
3862 112         240 $rtoken_indexes->{$lev_min} = [];
3863             }
3864              
3865             # handle one level
3866             elsif ( $lev_max == $lev_min ) {
3867 400         668 $rtoken_patterns->{$lev_max} = $token_pattern_max;
3868 400         887 $rtoken_indexes->{$lev_max} = [ ( 0 .. $imax ) ];
3869             }
3870              
3871             # handle multiple levels
3872             else {
3873 191         389 $rtoken_patterns->{$lev_max} = $token_pattern_max;
3874 191         491 $rtoken_indexes->{$lev_max} = [ ( 0 .. $imax ) ];
3875              
3876 191         296 my $lev_top = pop @levs; # already did max level
3877 191         251 my $itok = -1;
3878 191         217 foreach my $tok ( @{$rtokens} ) {
  191         300  
3879 789         761 $itok++;
3880 789 100       1050 last if ( $itok > $imax );
3881             my ( $raw_tok, $lev, $tag_uu, $tok_count_uu ) =
3882 598         557 @{ $all_token_info[$jj]->[$itok] };
  598         890  
3883 598 50       915 last if ( $raw_tok eq '#' );
3884 598         670 foreach my $lev_test (@levs) {
3885 638 100       990 next if ( $lev > $lev_test );
3886 315         494 $rtoken_patterns->{$lev_test} .= $tok;
3887 315         362 push @{ $rtoken_indexes->{$lev_test} }, $itok;
  315         615  
3888             }
3889             }
3890 191         294 push @levs, $lev_top;
3891             }
3892              
3893 703         834 push @{$rline_values},
  703         2026  
3894             [
3895             $lev_min, $lev_max, $rtoken_patterns, \@levs,
3896             $rtoken_indexes, $is_monotonic, $imax_true, $imax,
3897             ];
3898              
3899             # debug
3900 703         1391 0 && do {
3901             local $LIST_SEPARATOR = ')(';
3902             print "lev_min=$lev_min, lev_max=$lev_max, levels=(@levs)\n";
3903             foreach my $key ( sort keys %{$rtoken_patterns} ) {
3904             print "$key => $rtoken_patterns->{$key}\n";
3905             print "$key => @{$rtoken_indexes->{$key}}\n";
3906             }
3907             };
3908             } ## end loop over lines
3909 174         1141 return ( $rline_values, $all_monotonic );
3910             } ## end sub get_line_token_info
3911              
3912             sub prune_alignment_tree {
3913              
3914 174     174 0 392 my ($rlines) = @_;
3915              
3916             # Given:
3917             # $rlines = ref to array of lines in this group
3918              
3919             # Prune the tree of alignments to limit depth of alignments
3920              
3921 174         296 my $jmax = @{$rlines} - 1;
  174         352  
3922 174 50       468 return if ( $jmax <= 0 );
3923              
3924             # Vertical alignment in perltidy is done as an iterative process. The
3925             # starting point is to mark all possible alignment tokens ('=', ',', '=>',
3926             # etc) for vertical alignment. Then we have to delete all alignments
3927             # which, if actually made, would detract from overall alignment. This
3928             # is done in several phases of which this is one.
3929              
3930             # In this routine we look at the alignments of a group of lines as a
3931             # hierarchical tree. We will 'prune' the tree to limited depths if that
3932             # will improve overall alignment at the lower depths.
3933             # For each line we will be looking at its alignment patterns down to
3934             # different fixed depths. For each depth, we include all lower depths and
3935             # ignore all higher depths. We want to see if we can get alignment of a
3936             # larger group of lines if we ignore alignments at some lower depth.
3937             # Here is an # example:
3938              
3939             # for (
3940             # [ '$var', sub { join $_, "bar" }, 0, "bar" ],
3941             # [ 'CONSTANT', sub { join "foo", "bar" }, 0, "bar" ],
3942             # [ 'CONSTANT', sub { join "foo", "bar", 3 }, 1, "barfoo3" ],
3943             # [ '$myvar', sub { my $var; join $var, "bar" }, 0, "bar" ],
3944             # );
3945              
3946             # In the above example, all lines have three commas at the lowest depth
3947             # (zero), so if there were no other alignments, these lines would all
3948             # align considering only the zero depth alignment token. But some lines
3949             # have additional comma alignments at the next depth, so we need to decide
3950             # if we should drop those to keep the top level alignments, or keep those
3951             # for some additional low level alignments at the expense losing some top
3952             # level alignments. In this case we will drop the deeper level commas to
3953             # keep the entire collection aligned. But in some cases the decision could
3954             # go the other way.
3955              
3956             # The tree for this example at the zero depth has one node containing
3957             # all four lines, since they are identical at zero level (three commas).
3958             # At depth one, there are three 'children' nodes, namely:
3959             # - lines 1 and 2, which have a single comma in the 'sub' at depth 1
3960             # - line 3, which has 2 commas at depth 1
3961             # - line4, which has a ';' and a ',' at depth 1
3962             # There are no deeper alignments in this example.
3963             # so the tree structure for this example is:
3964             #
3965             # depth 0 depth 1 depth 2
3966             # [lines 1-4] -- [line 1-2] - (empty)
3967             # | [line 3] - (empty)
3968             # | [line 4] - (empty)
3969              
3970             # We can carry this to any depth, but it is not really useful to go below
3971             # depth 2. To cleanly stop there, we will consider depth 2 to contain all
3972             # alignments at depth >=2.
3973              
3974 44     44   315 use constant EXPLAIN_PRUNE => 0;
  44         86  
  44         51936  
3975              
3976             #-------------------------------------------------------------------
3977             # Prune Tree Step 1. Start by scanning the lines and collecting info
3978             #-------------------------------------------------------------------
3979              
3980             # Note that the caller had this info but we have to redo this now because
3981             # alignment tokens may have been deleted.
3982 174         578 my ( $rline_values, $all_monotonic ) = get_line_token_info($rlines);
3983              
3984             # If all the lines have levels which increase monotonically from left to
3985             # right, then the sweep-left-to-right pass can do a better job of alignment
3986             # than pruning, and without deleting alignments.
3987 174 100       974 return if ($all_monotonic);
3988              
3989             # Contents of $rline_values
3990             # [
3991             # $lev_min, $lev_max, $rtoken_patterns, \@levs,
3992             # $rtoken_indexes, $is_monotonic, $imax_true, $imax,
3993             # ];
3994              
3995             # We can work to any depth, but there is little advantage to working
3996             # to a depth greater than 2
3997 36         59 my $MAX_DEPTH = 2;
3998              
3999             # This arrays will hold the tree of alignment tokens at different depths
4000             # for these lines.
4001 36         61 my @match_tree;
4002              
4003             # Tree nodes contain these values:
4004             # $match_tree[$depth] = [$jbeg, $jend, $n_parent, $level, $pattern,
4005             # $nc_beg_p, $nc_end_p, $rindexes];
4006             # where
4007             # $depth = 0,1,2 = index of depth of the match
4008              
4009             # $jbeg beginning index j of the range of lines in this match
4010             # $jend ending index j of the range of lines in this match
4011             # $n_parent = index of the containing group at $depth-1, if it exists
4012             # $level = actual level of code being matched in this group
4013             # $pattern = alignment pattern being matched
4014             # $nc_beg_p = first child
4015             # $nc_end_p = last child
4016             # $rindexes = ref to token indexes
4017              
4018             # the patterns and levels of the current group being formed at each depth
4019 36         128 my ( @token_patterns_current, @levels_current, @token_indexes_current );
4020              
4021             # the patterns and levels of the next line being tested at each depth
4022 36         0 my ( @token_patterns_next, @levels_next, @token_indexes_next );
4023              
4024             #-----------------------------------------------------------
4025             # define a recursive worker subroutine for tree construction
4026             #-----------------------------------------------------------
4027              
4028             # This is a recursive routine which is called if a match condition changes
4029             # at any depth when a new line is encountered. It ends the match node
4030             # which changed plus all deeper nodes attached to it.
4031 36         0 my $end_node;
4032             $end_node = sub {
4033              
4034 345     345   425 my ( $depth, $jl, $n_parent ) = @_;
4035              
4036             # $depth is the tree depth
4037             # $jl is the index of the line
4038             # $n_parent is index of the parent node of this node
4039              
4040 345 100       475 return if ( $depth > $MAX_DEPTH );
4041              
4042             # end any current group at this depth
4043 252 100 100     561 if ( $jl >= 0
      66        
      100        
4044             && defined( $match_tree[$depth] )
4045 77         255 && @{ $match_tree[$depth] }
4046             && defined( $levels_current[$depth] ) )
4047             {
4048 71         97 $match_tree[$depth]->[-1]->[1] = $jl;
4049             }
4050              
4051             # Define the index of the node we will create below
4052 252         262 my $ng_self = 0;
4053 252 100       412 if ( defined( $match_tree[$depth] ) ) {
4054 77         117 $ng_self = @{ $match_tree[$depth] };
  77         99  
4055             }
4056              
4057             # end any next deeper child node(s)
4058 252         589 $end_node->( $depth + 1, $jl, $ng_self );
4059              
4060             # update the levels being matched
4061 252         360 $token_patterns_current[$depth] = $token_patterns_next[$depth];
4062 252         286 $token_indexes_current[$depth] = $token_indexes_next[$depth];
4063 252         309 $levels_current[$depth] = $levels_next[$depth];
4064              
4065             # Do not start a new group at this level if it is not being used
4066 252 100 66     703 if ( !defined( $levels_next[$depth] )
      66        
4067             || $depth > 0
4068             && $levels_next[$depth] <= $levels_next[ $depth - 1 ] )
4069             {
4070 127         154 return;
4071             }
4072              
4073             # Create a node for the next group at this depth. We initially assume
4074             # that it will continue to $jmax, and correct that later if the node
4075             # ends earlier.
4076 125         139 push @{ $match_tree[$depth] },
  125         428  
4077             [
4078             $jl + 1, $jmax, $n_parent, $levels_current[$depth],
4079             $token_patterns_current[$depth],
4080             undef, undef, $token_indexes_current[$depth],
4081             ];
4082              
4083 125         195 return;
4084 36         263 }; ## end $end_node = sub
4085              
4086             #-----------------------------------------------------
4087             # Prune Tree Step 2. Loop to form the tree of matches.
4088             #-----------------------------------------------------
4089 36         112 foreach my $jp ( 0 .. $jmax ) {
4090              
4091             # working with two adjacent line indexes, 'm'=minus, 'p'=plus
4092 246         275 my $jm = $jp - 1;
4093              
4094             # Pull out needed values for the next line
4095             my ( $lev_min_uu, $lev_max_uu, $rtoken_patterns, $rlevs,
4096             $rtoken_indexes, $is_monotonic_uu, $imax_true_uu, $imax_uu )
4097 246         246 = @{ $rline_values->[$jp] };
  246         470  
4098              
4099             # Transfer levels and patterns for this line to the working arrays.
4100             # If the number of levels differs from our chosen MAX_DEPTH ...
4101             # if fewer than MAX_DEPTH: leave levels at missing depths undefined
4102             # if more than MAX_DEPTH: set the MAX_DEPTH level to be the maximum
4103 246         298 @levels_next = @{$rlevs}[ 0 .. $MAX_DEPTH ];
  246         400  
4104 246 100       259 if ( @{$rlevs} > $MAX_DEPTH ) {
  246         383  
4105 5         6 $levels_next[$MAX_DEPTH] = $rlevs->[-1];
4106             }
4107 246         277 my $depth = 0;
4108 246         269 foreach my $item (@levels_next) {
4109             $token_patterns_next[$depth] =
4110 738 100       1022 defined($item) ? $rtoken_patterns->{$item} : undef;
4111             $token_indexes_next[$depth] =
4112 738 100       934 defined($item) ? $rtoken_indexes->{$item} : undef;
4113 738         786 $depth++;
4114             }
4115              
4116             # Look for a change in match groups...
4117              
4118             # Initialize on the first line
4119 246 100       660 if ( $jp == 0 ) {
    100          
    50          
4120 36         55 my $n_parent;
4121 36         94 $end_node->( 0, $jm, $n_parent );
4122             }
4123              
4124             # End groups if a hard flag has been set
4125             elsif ( $rlines->[$jm]->{'end_group'} ) {
4126 11         19 my $n_parent;
4127 11         23 $end_node->( 0, $jm, $n_parent );
4128             }
4129              
4130             # Continue at hanging side comment
4131             elsif ( $rlines->[$jp]->{'is_hanging_side_comment'} ) {
4132 0         0 next;
4133             }
4134              
4135             # Otherwise see if anything changed and update the tree if so
4136             else {
4137 199         262 foreach my $dep ( 0 .. $MAX_DEPTH ) {
4138              
4139 413         428 my $def_current = defined( $token_patterns_current[$dep] );
4140 413         424 my $def_next = defined( $token_patterns_next[$dep] );
4141 413 100 100     765 last if ( !$def_current && !$def_next );
4142 261 100 100     1124 if ( !$def_current
      100        
4143             || !$def_next
4144             || $token_patterns_current[$dep] ne
4145             $token_patterns_next[$dep] )
4146             {
4147 46         63 my $n_parent;
4148 46 100 66     131 if ( $dep > 0 && defined( $match_tree[ $dep - 1 ] ) ) {
4149 23         23 $n_parent = @{ $match_tree[ $dep - 1 ] } - 1;
  23         42  
4150             }
4151 46         94 $end_node->( $dep, $jm, $n_parent );
4152 46         86 last;
4153             }
4154             }
4155             }
4156             } ## end loop to form tree of matches
4157              
4158             #---------------------------------------------------------
4159             # Prune Tree Step 3. Make links from parent to child nodes
4160             #---------------------------------------------------------
4161              
4162             # It seemed cleaner to do this as a separate step rather than during tree
4163             # construction. The children nodes have links up to the parent node which
4164             # created them. Now make links in the opposite direction, so the parents
4165             # can find the children. We store the range of children nodes ($nc_beg,
4166             # $nc_end) of each parent with two additional indexes in the original array.
4167             # These will be undef if no children.
4168 36         84 foreach my $depth ( reverse( 1 .. $MAX_DEPTH ) ) {
4169 72 100       147 next unless ( defined( $match_tree[$depth] ) );
4170 37         51 my $nc_max = @{ $match_tree[$depth] } - 1;
  37         79  
4171 37         57 my $np_now;
4172 37         105 foreach my $nc ( 0 .. $nc_max ) {
4173 55         82 my $np = $match_tree[$depth]->[$nc]->[2];
4174 55 50       111 if ( !defined($np) ) {
4175              
4176             # shouldn't happen
4177             #print STDERR "lost child $np at depth $depth\n";
4178 0         0 next;
4179             }
4180 55 100 100     159 if ( !defined($np_now) || $np != $np_now ) {
4181 40         56 $np_now = $np;
4182 40         81 $match_tree[ $depth - 1 ]->[$np]->[5] = $nc;
4183             }
4184 55         130 $match_tree[ $depth - 1 ]->[$np]->[6] = $nc;
4185             }
4186             } ## end loop to make links down to the child nodes
4187              
4188 36         51 EXPLAIN_PRUNE > 0 && do {
4189             print "Tree complete. Found these groups:\n";
4190             foreach my $depth ( 0 .. $MAX_DEPTH ) {
4191             Dump_tree_groups( \@{ $match_tree[$depth] }, "depth=$depth" );
4192             }
4193             };
4194              
4195             #------------------------------------------------------
4196             # Prune Tree Step 4. Make a list of nodes to be deleted
4197             #------------------------------------------------------
4198              
4199             # list of lines with tokens to be deleted:
4200             # [$jbeg, $jend, $level_keep]
4201             # $jbeg..$jend is the range of line indexes,
4202             # $level_keep is the minimum level to keep
4203 36         63 my @delete_list;
4204              
4205             # We work with a list of nodes to visit at the next deeper depth.
4206             my @todo_list;
4207 36 50       98 if ( defined( $match_tree[0] ) ) {
4208 36         57 @todo_list = ( 0 .. @{ $match_tree[0] } - 1 );
  36         128  
4209             }
4210              
4211 36         67 foreach my $depth ( 0 .. $MAX_DEPTH ) {
4212 101 100       202 last if ( !@todo_list );
4213 65         80 my @todo_next;
4214 65         99 foreach my $np (@todo_list) {
4215             my ( $jbeg_p, $jend_p, $np_p_uu, $lev_p, $pat_p_uu, $nc_beg_p,
4216             $nc_end_p, $rindexes_p_uu )
4217 103         109 = @{ $match_tree[$depth]->[$np] };
  103         217  
4218 103         136 my $nlines_p = $jend_p - $jbeg_p + 1;
4219              
4220             # nothing to do if no children
4221 103 100       211 next unless ( defined($nc_beg_p) );
4222              
4223             # Define the number of lines to either keep or delete a child node.
4224             # This is the key decision we have to make. We want to delete
4225             # short runs of matched lines, and keep long runs. It seems easier
4226             # for the eye to follow breaks in monotonic level changes than
4227             # non-monotonic level changes. For example, the following looks
4228             # best if we delete the lower level alignments:
4229              
4230             # [1] ~~ [];
4231             # [ ["foo"], ["bar"] ] ~~ [ qr/o/, qr/a/ ];
4232             # [ qr/o/, qr/a/ ] ~~ [ ["foo"], ["bar"] ];
4233             # [ "foo", "bar" ] ~~ [ qr/o/, qr/a/ ];
4234             # [ qr/o/, qr/a/ ] ~~ [ "foo", "bar" ];
4235             # $deep1 ~~ $deep1;
4236              
4237             # So we will use two thresholds.
4238 40         64 my $nmin_mono = $depth + 2;
4239 40         59 my $nmin_non_mono = $depth + 6;
4240 40 100       98 if ( $nmin_mono > $nlines_p - 1 ) {
4241 26         39 $nmin_mono = $nlines_p - 1;
4242             }
4243 40 100       98 if ( $nmin_non_mono > $nlines_p - 1 ) {
4244 36         58 $nmin_non_mono = $nlines_p - 1;
4245             }
4246              
4247             # loop to keep or delete each child node
4248 40         85 foreach my $nc ( $nc_beg_p .. $nc_end_p ) {
4249             my ( $jbeg_c, $jend_c, $np_c_uu, $lev_c_uu, $pat_c_uu,
4250             $nc_beg_c_uu, $nc_end_c_uu )
4251 55         79 = @{ $match_tree[ $depth + 1 ]->[$nc] };
  55         137  
4252 55         77 my $nlines_c = $jend_c - $jbeg_c + 1;
4253 55         78 my $is_monotonic = $rline_values->[$jbeg_c]->[5];
4254 55 100       103 my $nmin = $is_monotonic ? $nmin_mono : $nmin_non_mono;
4255 55 100       108 if ( $nlines_c < $nmin ) {
4256             ##print "deleting child, nlines=$nlines_c, nmin=$nmin\n";
4257 22         53 push @delete_list, [ $jbeg_c, $jend_c, $lev_p ];
4258             }
4259             else {
4260             ##print "keeping child, nlines=$nlines_c, nmin=$nmin\n";
4261 33         101 push @todo_next, $nc;
4262             }
4263             }
4264             }
4265 65         115 @todo_list = @todo_next;
4266             } ## end loop to mark nodes to delete
4267              
4268             #------------------------------------------------------------
4269             # Prune Tree Step 5. Loop to delete selected alignment tokens
4270             #------------------------------------------------------------
4271 36         61 foreach my $item (@delete_list) {
4272 22         28 my ( $jbeg, $jend, $level_keep ) = @{$item};
  22         38  
4273 22         43 foreach my $jj ( $jbeg .. $jend ) {
4274 28         36 my $line = $rlines->[$jj];
4275 28         33 my @idel;
4276 28         36 my $rtokens = $line->{'rtokens'};
4277 28         32 my $imax = @{$rtokens} - 2;
  28         35  
4278 28         36 foreach my $i ( 0 .. $imax ) {
4279 152         168 my $tok = $rtokens->[$i];
4280 152         196 my ( $raw_tok_uu, $lev, $tag_uu, $tok_count_uu ) =
4281             decode_alignment_token($tok);
4282 152 100       265 if ( $lev > $level_keep ) {
4283 83         97 push @idel, $i;
4284             }
4285             }
4286 28 50       57 if (@idel) {
4287 28         53 delete_selected_tokens( $line, \@idel );
4288             }
4289             }
4290             } ## end loop to delete selected alignment tokens
4291              
4292 36         363 return;
4293             } ## end sub prune_alignment_tree
4294              
4295             sub Dump_tree_groups {
4296              
4297 0     0 0 0 my ( $rgroup, $msg ) = @_;
4298              
4299             # Debug routine
4300 0         0 print "$msg\n";
4301 0         0 local $LIST_SEPARATOR = ')(';
4302 0         0 foreach my $item ( @{$rgroup} ) {
  0         0  
4303 0         0 my @fix = @{$item};
  0         0  
4304 0 0       0 foreach my $val (@fix) { $val = "undef" unless ( defined($val) ); }
  0         0  
4305 0         0 $fix[4] = "...";
4306 0         0 print "(@fix)\n";
4307             }
4308 0         0 return;
4309             } ## end sub Dump_tree_groups
4310              
4311             # This test did not give sufficiently better results to use as an update,
4312             # but the flag is kept as a starting point for future testing.
4313 44     44   301 use constant TEST_MARGINAL_EQ_ALIGNMENT => 0;
  44         69  
  44         79936  
4314              
4315             sub is_marginal_match {
4316              
4317 295     295 0 695 my ( $line_0, $line_1, $group_level, $imax_align, $imax_prev ) = @_;
4318              
4319             # Decide if we should undo some or all of the common alignments of a
4320             # group of just two lines.
4321              
4322             # Given:
4323             # $line_0 and $line_1 - the two lines
4324             # $group_level = the indentation level of the group being processed
4325             # $imax_align = the maximum index of the common alignment tokens
4326             # of the two lines
4327             # $imax_prev = the maximum index of the common alignment tokens
4328             # with the line before $line_0 (=-1 of does not exist)
4329              
4330             # Return:
4331             # $is_marginal = true if the two lines should NOT be fully aligned
4332             # = false if the two lines can remain fully aligned
4333             # $imax_align = the index of the highest alignment token shared by
4334             # these two lines to keep if the match is marginal.
4335              
4336             # When we have an alignment group of just two lines like this, we are
4337             # working in the twilight zone of what looks good and what looks bad.
4338             # This routine is a collection of rules which work have been found to
4339             # work fairly well, but it will need to be updated from time to time.
4340              
4341 295         446 my $is_marginal = 0;
4342              
4343             #---------------------------------------
4344             # Always align certain special cases ...
4345             #---------------------------------------
4346 295 100 100     2011 if (
      100        
4347              
4348             # always keep alignments of a terminal else or ternary
4349             defined( $line_1->{'j_terminal_match'} )
4350              
4351             # always align lists
4352             || $line_0->{'list_type'}
4353              
4354             # always align hanging side comments
4355             || $line_1->{'is_hanging_side_comment'}
4356              
4357             )
4358             {
4359 133         312 return ( $is_marginal, $imax_align );
4360             }
4361              
4362 162         311 my $jmax_0 = $line_0->{'jmax'};
4363 162         284 my $jmax_1 = $line_1->{'jmax'};
4364 162         273 my $rtokens_1 = $line_1->{'rtokens'};
4365             ## my $rtokens_0 = $line_0->{'rtokens'};
4366 162         263 my $rfield_lengths_0 = $line_0->{'rfield_lengths'};
4367 162         252 my $rfield_lengths_1 = $line_1->{'rfield_lengths'};
4368 162         280 my $rpatterns_0 = $line_0->{'rpatterns'};
4369 162         251 my $rpatterns_1 = $line_1->{'rpatterns'};
4370 162         238 my $imax_next = $line_1->{'imax_pair'};
4371              
4372             # We will scan the alignment tokens and set a flag '$is_marginal' if
4373             # it seems that the an alignment would look bad.
4374 162         216 my $max_pad = 0;
4375 162         223 my $saw_good_alignment = 0;
4376 162         266 my $saw_if_or; # if we saw an 'if' or 'or' at group level
4377 162         265 my $raw_tokb = EMPTY_STRING; # first token seen at group level
4378 162         362 my $jfirst_bad;
4379             my $line_ending_fat_comma; # is last token just a '=>' ?
4380 162         0 my $j0_eq_pad;
4381 162         253 my $j0_max_pad = 0;
4382              
4383 162         376 foreach my $j ( 0 .. $jmax_1 - 2 ) {
4384 215         541 my ( $raw_tok, $lev, $tag_uu, $tok_count_uu ) =
4385             decode_alignment_token( $rtokens_1->[$j] );
4386 215 100 66     845 if ( $raw_tok && $lev == $group_level ) {
4387 185 100       413 if ( !$raw_tokb ) { $raw_tokb = $raw_tok }
  149         248  
4388 185   100     643 $saw_if_or ||= $is_if_or{$raw_tok};
4389             }
4390              
4391             # When the first of the two lines ends in a bare '=>' this will
4392             # probably be marginal match. (For a bare =>, the next field length
4393             # will be 2 or 3, depending on side comment)
4394             $line_ending_fat_comma =
4395 215   100     831 $j == $jmax_1 - 2
4396             && $raw_tok eq '=>'
4397             && $rfield_lengths_0->[ $j + 1 ] <= 3;
4398              
4399 215         385 my $pad = $rfield_lengths_1->[$j] - $rfield_lengths_0->[$j];
4400 215 100       439 if ( $j == 0 ) {
4401             $pad += $line_1->{'leading_space_count'} -
4402 154         331 $line_0->{'leading_space_count'};
4403              
4404             # Remember the pad at a leading equals
4405 154 100 66     675 if ( $raw_tok eq '=' && $lev == $group_level ) {
4406 92         174 $j0_eq_pad = $pad;
4407 92         242 $j0_max_pad =
4408             0.5 * ( $rfield_lengths_1->[0] + $rfield_lengths_0->[0] );
4409 92 100       300 $j0_max_pad = 4 if ( $j0_max_pad < 4 );
4410             }
4411             }
4412              
4413 215 100       453 if ( $pad < 0 ) { $pad = -$pad }
  52         99  
4414 215 100       462 if ( $pad > $max_pad ) { $max_pad = $pad }
  116         177  
4415 215 100 100     841 if ( $is_good_marginal_alignment{$raw_tok}
4416             && !$line_ending_fat_comma )
4417             {
4418 157         224 $saw_good_alignment = 1;
4419             }
4420             else {
4421 58 100       137 $jfirst_bad = $j unless ( defined($jfirst_bad) );
4422             }
4423 215         336 my $pat_0 = $rpatterns_0->[$j];
4424 215         331 my $pat_1 = $rpatterns_1->[$j];
4425 215 100 100     703 if ( $pat_0 ne $pat_1 && length($pat_0) eq length($pat_1) ) {
4426 3         6 $pat_0 =~ tr/n/Q/;
4427 3         6 $pat_1 =~ tr/n/Q/;
4428             }
4429 215 100       521 if ( $pat_0 ne $pat_1 ) {
4430              
4431             # Flag this as a marginal match since patterns differ.
4432             # Normally, we will not allow just two lines to match if
4433             # marginal. But we can allow matching in some specific cases.
4434              
4435 45 100       139 $jfirst_bad = $j if ( !defined($jfirst_bad) );
4436 45 100       120 $is_marginal = 1 if ( $is_marginal == 0 );
4437 45 100       168 if ( $raw_tok eq '=' ) {
4438              
4439             # Here is an example of a marginal match:
4440             # $done{$$op} = 1;
4441             # $op = compile_bblock($op);
4442             # The left tokens are both identifiers, but
4443             # one accesses a hash and the other doesn't.
4444             # We'll let this be a tentative match and undo
4445             # it later if we don't find more than 2 lines
4446             # in the group.
4447 13         32 $is_marginal = 2;
4448             }
4449             }
4450             }
4451              
4452 162 50 66     653 $is_marginal = 1 if ( $is_marginal == 0 && $line_ending_fat_comma );
4453              
4454             # Turn off the "marginal match" flag in some cases...
4455             # A "marginal match" occurs when the alignment tokens agree
4456             # but there are differences in the other tokens (patterns).
4457             # If we leave the marginal match flag set, then the rule is that we
4458             # will align only if there are more than two lines in the group.
4459             # We will turn of the flag if we almost have a match
4460             # and either we have seen a good alignment token or we
4461             # just need a small pad (2 spaces) to fit. These rules are
4462             # the result of experimentation. Tokens which misaligned by just
4463             # one or two characters are annoying. On the other hand,
4464             # large gaps to less important alignment tokens are also annoying.
4465 162 100 100     535 if ( $is_marginal == 1
      100        
4466             && ( $saw_good_alignment || $max_pad < 3 ) )
4467             {
4468 25         39 $is_marginal = 0;
4469             }
4470              
4471             # We will use the line endings to help decide on alignments...
4472             # See if the lines end with semicolons...
4473 162         248 my $sc_term0;
4474             my $sc_term1;
4475 162 50 33     615 if ( $jmax_0 < 1 || $jmax_1 < 1 ) {
4476              
4477             # shouldn't happen
4478             }
4479             else {
4480 162         328 my $pat0 = $rpatterns_0->[ $jmax_0 - 1 ];
4481 162         288 my $pat1 = $rpatterns_1->[ $jmax_1 - 1 ];
4482 162         868 $sc_term0 = $pat0 =~ /;b?$/;
4483 162         475 $sc_term1 = $pat1 =~ /;b?$/;
4484             }
4485              
4486 162 100 100     622 if ( !$is_marginal && !$sc_term0 ) {
4487              
4488             # First line of assignment should be semicolon terminated.
4489             # For example, do not align here:
4490             # $$href{-NUM_TEXT_FILES} = $$href{-NUM_BINARY_FILES} =
4491             # $$href{-NUM_DIRS} = 0;
4492 39 100       129 if ( $is_assignment{$raw_tokb} ) {
4493 1         3 $is_marginal = 1;
4494             }
4495             }
4496              
4497             # Try to avoid some undesirable alignments of opening tokens
4498             # for example, the space between grep and { here:
4499             # return map { ( $_ => $_ ) }
4500             # grep { /$handles/ } $self->_get_delegate_method_list;
4501             $is_marginal ||=
4502 162   100     1140 ( $raw_tokb eq '(' || $raw_tokb eq '{' )
      100        
4503             && $jmax_1 == 2
4504             && $sc_term0 ne $sc_term1;
4505              
4506             #---------------------------------------
4507             # return if this is not a marginal match
4508             #---------------------------------------
4509 162 100       323 if ( !$is_marginal ) {
4510 141         473 return ( $is_marginal, $imax_align );
4511             }
4512              
4513             # Undo the marginal match flag in certain cases,
4514              
4515             # Two lines with a leading equals-like operator are allowed to
4516             # align if the patterns to the left of the equals are the same.
4517             # For example the following two lines are a marginal match but have
4518             # the same left side patterns, so we will align the equals.
4519             # my $orig = my $format = "^<<<<< ~~\n";
4520             # my $abc = "abc";
4521             # But these have a different left pattern so they will not be
4522             # aligned
4523             # $xmldoc .= $`;
4524             # $self->{'leftovers'} .= "<bx-seq:seq" . $';
4525              
4526             # First line semicolon terminated but second not, usually ok:
4527             # my $want = "'ab', 'a', 'b'";
4528             # my $got = join( ", ",
4529             # map { defined($_) ? "'$_'" : "undef" }
4530             # @got );
4531             # First line not semicolon terminated, Not OK to match:
4532             # $$href{-NUM_TEXT_FILES} = $$href{-NUM_BINARY_FILES} =
4533             # $$href{-NUM_DIRS} = 0;
4534 21         46 my $pat0 = $rpatterns_0->[0];
4535 21         44 my $pat1 = $rpatterns_1->[0];
4536              
4537             #---------------------------------------------------------
4538             # Turn off the marginal flag for some types of assignments
4539             #---------------------------------------------------------
4540 21 100       83 if ( $is_assignment{$raw_tokb} ) {
    50          
    50          
4541              
4542             # undo marginal flag if first line is semicolon terminated
4543             # and leading patterns match
4544 14 100       38 if ($sc_term0) {
4545 13         27 $is_marginal = $pat0 ne $pat1;
4546             }
4547             }
4548             elsif ( $raw_tokb eq '=>' ) {
4549              
4550             # undo marginal flag if patterns match
4551 0   0     0 $is_marginal = $pat0 ne $pat1 || $line_ending_fat_comma;
4552             }
4553             elsif ( $raw_tokb eq '=~' ) {
4554              
4555             # undo marginal flag if both lines are semicolon terminated
4556             # and leading patters match
4557 0 0 0     0 if ( $sc_term1 && $sc_term0 ) {
4558 0         0 $is_marginal = $pat0 ne $pat1;
4559             }
4560             }
4561             else {
4562             ## ok: (none of the above)
4563             }
4564              
4565             #-----------------------------------------------------
4566             # Turn off the marginal flag if we saw an 'if' or 'or'
4567             #-----------------------------------------------------
4568              
4569             # A trailing 'if' and 'or' often gives a good alignment
4570             # For example, we can align these:
4571             # return -1 if $_[0] =~ m/^CHAPT|APPENDIX/;
4572             # return $1 + 0 if $_[0] =~ m/^SECT(\d*)$/;
4573              
4574             # or
4575             # $d_in_m[2] = 29 if ( &Date_LeapYear($y) );
4576             # $d = $d_in_m[$m] if ( $d > $d_in_m[$m] );
4577              
4578 21 100       62 if ($saw_if_or) {
4579              
4580             # undo marginal flag if both lines are semicolon terminated
4581 5 50 33     29 if ( $sc_term0 && $sc_term1 ) {
4582 5         8 $is_marginal = 0;
4583             }
4584             }
4585              
4586             # For a marginal match, only keep matches before the first 'bad' match
4587 21 50 100     131 if ( $is_marginal
      66        
4588             && defined($jfirst_bad)
4589             && $imax_align > $jfirst_bad - 1 )
4590             {
4591 0         0 $imax_align = $jfirst_bad - 1;
4592             }
4593              
4594             #----------------------------------------------------------
4595             # Allow sweep to match lines with leading '=' in some cases
4596             #----------------------------------------------------------
4597 21 100 66     96 if ( $imax_align < 0 && defined($j0_eq_pad) ) {
4598              
4599 14 0 50     121 if (
      33        
      33        
4600              
4601             # If there is a following line with leading equals, or
4602             # preceding line with leading equals, then let the sweep align
4603             # them without restriction. For example, the first two lines
4604             # here are a marginal match, but they are followed by a line
4605             # with leading equals, so the sweep-lr logic can align all of
4606             # the lines:
4607              
4608             # $date[1] = $month_to_num{ $date[1] }; # <--line_0
4609             # @xdate = split( /[:\/\s]/, $log->field('t') ); # <--line_1
4610             # $day = sprintf( "%04d/%02d/%02d", @date[ 2, 1, 0 ] );
4611             # $time = sprintf( "%02d:%02d:%02d", @date[ 3 .. 5 ] );
4612              
4613             # Likewise, if we reverse the two pairs we want the same result
4614              
4615             # $day = sprintf( "%04d/%02d/%02d", @date[ 2, 1, 0 ] );
4616             # $time = sprintf( "%02d:%02d:%02d", @date[ 3 .. 5 ] );
4617             # $date[1] = $month_to_num{ $date[1] }; # <--line_0
4618             # @xdate = split( /[:\/\s]/, $log->field('t') ); # <--line_1
4619              
4620             (
4621             $imax_next >= 0
4622             || $imax_prev >= 0
4623             || TEST_MARGINAL_EQ_ALIGNMENT
4624             )
4625             && $j0_eq_pad >= -$j0_max_pad
4626             && $j0_eq_pad <= $j0_max_pad
4627             )
4628             {
4629              
4630             # But do not do this if there is a comma before the '='.
4631             # For example, the first two lines below have commas and
4632             # therefore are not allowed to align with lines 3 & 4:
4633              
4634             # my ( $x, $y ) = $self->Size(); #<--line_0
4635             # my ( $left, $top, $right, $bottom ) = $self->Window(); #<--l_1
4636             # my $vx = $right - $left;
4637             # my $vy = $bottom - $top;
4638              
4639 0 0 0     0 if ( $rpatterns_0->[0] !~ /,/ && $rpatterns_1->[0] !~ /,/ ) {
4640 0         0 $imax_align = 0;
4641             }
4642             }
4643             }
4644              
4645 21         95 return ( $is_marginal, $imax_align );
4646             } ## end sub is_marginal_match
4647              
4648             sub get_extra_leading_spaces {
4649              
4650 444     444 0 927 my ( $rlines, $rgroups ) = @_;
4651              
4652             #----------------------------------------------------------
4653             # Define any extra indentation space (for the -lp option).
4654             # Here is why:
4655             # If a list has side comments, sub scan_list must dump the
4656             # list before it sees everything. When this happens, it sets
4657             # the indentation to the standard scheme, but notes how
4658             # many spaces it would have liked to use. We may be able
4659             # to recover that space here in the event that all of the
4660             # lines of a list are back together again.
4661             #----------------------------------------------------------
4662              
4663 444 50 33     626 return 0 if ( !@{$rlines} || !@{$rgroups} );
  444         1232  
  444         1183  
4664              
4665 444         905 my $object = $rlines->[0]->{'indentation'};
4666 444 100       1211 return 0 if ( !ref($object) );
4667 59         97 my $extra_leading_spaces = 0;
4668 59         156 my $extra_indentation_spaces_wanted = get_recoverable_spaces($object);
4669 59 100       164 return ($extra_leading_spaces) if ( !$extra_indentation_spaces_wanted );
4670              
4671 13         32 my $min_spaces = $extra_indentation_spaces_wanted;
4672 13 50       34 if ( $min_spaces > 0 ) { $min_spaces = 0 }
  13         56  
4673              
4674             # loop over all groups
4675 13         23 my $ng = -1;
4676 13         19 my $ngroups = @{$rgroups};
  13         31  
4677 13         20 foreach my $item ( @{$rgroups} ) {
  13         26  
4678 32         36 $ng++;
4679 32         39 my ( $jbeg, $jend ) = @{$item};
  32         48  
4680 32         54 foreach my $j ( $jbeg .. $jend ) {
4681 44 100       76 next if ( $j == 0 );
4682              
4683             # all indentation objects must be the same
4684 31 100       74 if ( $object != $rlines->[$j]->{'indentation'} ) {
4685 1         3 return 0;
4686             }
4687             }
4688              
4689             # find the maximum space without exceeding the line length for this group
4690 31         97 my $avail = $rlines->[$jbeg]->get_available_space_on_right();
4691 31 100       58 my $spaces =
4692             ( $avail > $extra_indentation_spaces_wanted )
4693             ? $extra_indentation_spaces_wanted
4694             : $avail;
4695              
4696             #--------------------------------------------------------
4697             # Note: min spaces can be negative; for example with -gnu
4698             # f(
4699             # do { 1; !!(my $x = bless []); }
4700             # );
4701             #--------------------------------------------------------
4702             # The following rule is needed to match older formatting:
4703             # For multiple groups, we will keep spaces non-negative.
4704             # For a single group, we will allow a negative space.
4705 31 50 66     92 if ( $ngroups > 1 && $spaces < 0 ) { $spaces = 0 }
  0         0  
4706              
4707             # update the minimum spacing
4708 31 100 66     101 if ( $ng == 0 || $spaces < $extra_leading_spaces ) {
4709 13         23 $extra_leading_spaces = $spaces;
4710             }
4711             }
4712              
4713             # update the indentation object because with -icp the terminal
4714             # ');' will use the same adjustment.
4715 12         55 $object->permanently_decrease_available_spaces( -$extra_leading_spaces );
4716 12         23 return $extra_leading_spaces;
4717             } ## end sub get_extra_leading_spaces
4718              
4719             sub forget_side_comment {
4720 124     124 0 303 my ($self) = @_;
4721 124         254 $self->[_last_side_comment_column_] = 0;
4722 124         227 return;
4723             }
4724              
4725             sub is_good_side_comment_column {
4726              
4727 222     222 0 488 my ( $self, $line, $line_number, $level, $num5 ) = @_;
4728              
4729             # Upon encountering the first side comment of a group, decide if
4730             # a previous side comment should be forgotten. This involves
4731             # checking several rules.
4732              
4733             # Given:
4734             # $line = ref to info hash for the line of interest
4735             # $line_number = number of this line in the output stream
4736             # $level = indentation level of this line
4737             # $num5 = ..see comments below
4738              
4739             # Return:
4740             # true to KEEP old comment location
4741             # false to FORGET old comment location
4742 222         287 my $KEEP = 1;
4743 222         279 my $FORGET = 0;
4744              
4745 222         331 my $rfields = $line->{'rfields'};
4746 222         353 my $is_hanging_side_comment = $line->{'is_hanging_side_comment'};
4747              
4748             # RULE1: Never forget comment before a hanging side comment
4749 222 100       528 return $KEEP if ($is_hanging_side_comment);
4750              
4751             # RULE2: Forget a side comment after a short line difference,
4752             # where 'short line difference' is computed from a formula.
4753             # Using a smooth formula helps minimize sudden large changes.
4754 210         349 my $line_diff = $line_number - $self->[_last_side_comment_line_number_];
4755 210         390 my $alev_diff = abs( $level - $self->[_last_side_comment_level_] );
4756              
4757             # '$num5' is the number of comments in the first 5 lines after the first
4758             # comment. It is needed to keep a compact group of side comments from
4759             # being influenced by a more distant side comment.
4760 210 50       448 $num5 = 1 if ( !$num5 );
4761              
4762             # Some values:
4763              
4764             # $adiff $num5 $short_diff
4765             # 0 * 12
4766             # 1 1 6
4767             # 1 2 4
4768             # 1 3 3
4769             # 1 4 2
4770             # 2 1 4
4771             # 2 2 2
4772             # 2 3 1
4773             # 3 1 3
4774             # 3 2 1
4775              
4776 210         429 my $short_diff = SC_LONG_LINE_DIFF / ( 1 + $alev_diff * $num5 );
4777              
4778 210 100 100     856 return $FORGET
4779             if ( $line_diff > $short_diff
4780             || !$rOpts_valign_side_comments );
4781              
4782             # RULE3: Forget a side comment if this line is at lower level and
4783             # ends a block
4784 128         219 my $last_sc_level = $self->[_last_side_comment_level_];
4785             return $FORGET
4786             if ( $level < $last_sc_level
4787 128 100 100     456 && $is_closing_block_type{ substr( $rfields->[0], 0, 1 ) } );
4788              
4789             # RULE 4: Forget the last side comment if this comment might join a cached
4790             # line ...
4791 110 100       256 if ( my $cached_line_type = get_cached_line_type() ) {
4792              
4793             # ... otherwise side comment alignment will get messed up.
4794             # For example, in the following test script
4795             # with using 'perltidy -sct -act=2', the last comment would try to
4796             # align with the previous and then be in the wrong column when
4797             # the lines are combined:
4798              
4799             # foreach $line (
4800             # [0, 1, 2], [3, 4, 5], [6, 7, 8], # rows
4801             # [0, 3, 6], [1, 4, 7], [2, 5, 8], # columns
4802             # [0, 4, 8], [2, 4, 6]
4803             # ) # diagonals
4804 4 50 33     21 return $FORGET
4805             if ( $cached_line_type == 2 || $cached_line_type == 4 );
4806             }
4807              
4808             # Otherwise, keep it alive
4809 110         217 return $KEEP;
4810             } ## end sub is_good_side_comment_column
4811              
4812             sub align_side_comments {
4813              
4814 222     222 0 457 my ( $self, $rlines, $rgroups ) = @_;
4815              
4816             # Align any side comments in this batch of lines
4817              
4818             # Given:
4819             # $rlines - the lines
4820             # $rgroups - the partition of the lines into groups
4821             #
4822             # We will be working group-by-group because all side comments
4823             # (real or fake) in each group are already aligned. So we just have
4824             # to make alignments between groups wherever possible.
4825              
4826             # An unusual aspect is that within each group we have aligned both real
4827             # and fake side comments. This has the consequence that the lengths of
4828             # long lines without real side comments can cause 'push' all side comments
4829             # to the right. This seems unusual, but testing with and without this
4830             # feature shows that it is usually better this way. Otherwise, side
4831             # comments can be hidden between long lines without side comments and
4832             # thus be harder to read.
4833              
4834 222         392 my $group_level = $self->[_group_level_];
4835 222   100     711 my $continuing_sc_flow = $self->[_last_side_comment_length_] > 0
4836             && $group_level == $self->[_last_level_written_];
4837              
4838             # Find groups with side comments, and remember the first nonblank comment
4839 222         335 my $j_sc_beg;
4840             my @todo;
4841 222         317 my $ng = -1;
4842 222         297 foreach my $item ( @{$rgroups} ) {
  222         358  
4843 348         453 $ng++;
4844 348         429 my ( $jbeg, $jend ) = @{$item};
  348         554  
4845 348         658 foreach my $j ( $jbeg .. $jend ) {
4846 403         519 my $line = $rlines->[$j];
4847 403         534 my $jmax = $line->{'jmax'};
4848 403 100       862 if ( $line->{'rfield_lengths'}->[$jmax] ) {
4849              
4850             # this group has a line with a side comment
4851 251         392 push @todo, $ng;
4852 251 100       522 if ( !defined($j_sc_beg) ) {
4853 222         324 $j_sc_beg = $j;
4854             }
4855 251         502 last;
4856             }
4857             }
4858             }
4859              
4860             # done if no groups with side comments
4861 222 50       470 return unless (@todo);
4862              
4863             # Count $num5 = number of comments in the 5 lines after the first comment
4864             # This is an important factor in a decision formula
4865 222         335 my $num5 = 1;
4866 222         341 foreach my $jj ( $j_sc_beg + 1 .. @{$rlines} - 1 ) {
  222         449  
4867 210         250 my $ldiff = $jj - $j_sc_beg;
4868 210 100       329 last if ( $ldiff > 5 );
4869 204         238 my $line = $rlines->[$jj];
4870 204         2912 my $jmax = $line->{'jmax'};
4871 204         301 my $sc_len = $line->{'rfield_lengths'}->[$jmax];
4872 204 100       354 next if ( !$sc_len );
4873 121         164 $num5++;
4874             }
4875              
4876             # Forget the old side comment location if necessary
4877 222         340 my $line_0 = $rlines->[$j_sc_beg];
4878 222         1050 my $lnum =
4879             $j_sc_beg + $self->[_file_writer_object_]->get_output_line_number();
4880 222         702 my $keep_it =
4881             $self->is_good_side_comment_column( $line_0, $lnum, $group_level, $num5 );
4882 222 100       523 my $last_side_comment_column =
4883             $keep_it ? $self->[_last_side_comment_column_] : 0;
4884              
4885             # If there are multiple groups we will do two passes
4886             # so that we can find a common alignment for all groups.
4887 222 100       501 my $MAX_PASS = @todo > 1 ? 2 : 1;
4888              
4889             # Loop over passes
4890 222         341 my $max_comment_column = $last_side_comment_column;
4891 222         912 foreach my $PASS ( 1 .. $MAX_PASS ) {
4892              
4893             # If there are two passes, then on the last pass make the old column
4894             # equal to the largest of the group. This will result in the comments
4895             # being aligned if possible.
4896 246 100       536 if ( $PASS == $MAX_PASS ) {
4897 222         343 $last_side_comment_column = $max_comment_column;
4898             }
4899              
4900             # Loop over the groups with side comments
4901 246         347 my $column_limit;
4902 246         367 foreach my $ngr (@todo) {
4903 304         405 my ( $jbeg, $jend_uu ) = @{ $rgroups->[$ngr] };
  304         526  
4904              
4905             # Note that since all lines in a group have common alignments, we
4906             # just have to work on one of the lines (the first line).
4907 304         407 my $line = $rlines->[$jbeg];
4908 304         427 my $jmax = $line->{'jmax'};
4909 304         396 my $is_hanging_side_comment = $line->{'is_hanging_side_comment'};
4910             last
4911 304 100 100     771 if ( $PASS < $MAX_PASS && $is_hanging_side_comment );
4912              
4913             # the maximum space without exceeding the line length:
4914 300         865 my $avail = $line->get_available_space_on_right();
4915              
4916             # try to use the previous comment column
4917 300         632 my $side_comment_column = $line->get_column( $jmax - 1 );
4918 300         467 my $move = $last_side_comment_column - $side_comment_column;
4919              
4920             # Remember the maximum possible column of the first line with
4921             # side comment
4922 300 100       560 if ( !defined($column_limit) ) {
4923 246         379 $column_limit = $side_comment_column + $avail;
4924             }
4925              
4926 300 50       544 next if ( $jmax <= 0 );
4927              
4928             # but if this doesn't work, give up and use the minimum space
4929 300         374 my $min_move = $rOpts_minimum_space_to_comment - 1;
4930 300 100       621 if ( $move > $avail ) {
4931 13         16 $move = $min_move;
4932             }
4933              
4934             # but we want some minimum space to the comment
4935 300 100 100     829 if ( $move >= 0
      100        
4936             && $j_sc_beg == 0
4937             && $continuing_sc_flow )
4938             {
4939 5         10 $min_move = 0;
4940             }
4941              
4942             # remove constraints on hanging side comments
4943 300 100       571 if ($is_hanging_side_comment) { $min_move = 0 }
  16         20  
4944              
4945 300 100       584 if ( $move < $min_move ) {
4946 215         279 $move = $min_move;
4947             }
4948              
4949             # don't exceed the available space
4950 300 100       559 if ( $move > $avail ) { $move = $avail }
  11         14  
4951              
4952             # We can only increase space, never decrease.
4953 300 100       578 if ( $move < 0 ) { $move = 0 }
  8         9  
4954              
4955             # Discover the largest column on the preliminary pass
4956 300 100       534 if ( $PASS < $MAX_PASS ) {
4957 49         94 my $col = $line->get_column( $jmax - 1 ) + $move;
4958              
4959             # but ignore columns too large for the starting line
4960 49 100 66     171 if ( $col > $max_comment_column && $col < $column_limit ) {
4961 23         45 $max_comment_column = $col;
4962             }
4963             }
4964              
4965             # Make the changes on the final pass
4966             else {
4967 251         768 $line->increase_field_width( $jmax - 1, $move );
4968              
4969             # remember this column for the next group
4970 251         515 $last_side_comment_column = $line->get_column( $jmax - 1 );
4971             }
4972             } ## end loop over groups
4973             } ## end loop over passes
4974              
4975             # Find the last side comment
4976 222         305 my $j_sc_last;
4977 222         312 my $ng_last = $todo[-1];
4978 222         292 my ( $jbeg, $jend ) = @{ $rgroups->[$ng_last] };
  222         396  
4979 222         469 foreach my $jj ( reverse( $jbeg .. $jend ) ) {
4980 225         356 my $line = $rlines->[$jj];
4981 225         311 my $jmax = $line->{'jmax'};
4982 225 100       499 if ( $line->{'rfield_lengths'}->[$jmax] ) {
4983 222         306 $j_sc_last = $jj;
4984 222         369 last;
4985             }
4986             }
4987              
4988             # Save final side comment info for possible use by the next batch
4989 222 50       457 if ( defined($j_sc_last) ) {
4990 222         576 my $line_number =
4991             $self->[_file_writer_object_]->get_output_line_number() + $j_sc_last;
4992 222         363 $self->[_last_side_comment_column_] = $last_side_comment_column;
4993 222         324 $self->[_last_side_comment_line_number_] = $line_number;
4994 222         360 $self->[_last_side_comment_level_] = $group_level;
4995             }
4996 222         452 return;
4997             } ## end sub align_side_comments
4998              
4999             ###########################################
5000             # CODE SECTION 6: Pad Signed Number Columns
5001             ###########################################
5002              
5003 44     44   333 use constant DEBUG_VSN => 0;
  44         71  
  44         3830  
5004              
5005             my %is_leading_sign_pattern;
5006              
5007             BEGIN {
5008              
5009             # PATTERNS: A pattern is basically the concatenation of all token types in
5010             # the field, with keywords converted to their actual text. The formatter
5011             # has changed things like 'print' to 'priNt' so that all 'n's are numbers.
5012             # The following patterns 'n' can match a signed number of interest.
5013             # Thus 'n'=a signed or unsigned number, 'b'=a space, '}'=one of ) ] }
5014             $is_leading_sign_pattern{$_} = 1
5015 44     44   127331 for ( 'n,', 'n,b', 'nb', 'nb}', 'nb},', 'n},', 'n};' );
5016             }
5017              
5018             sub min_max_median {
5019              
5020 42     42 0 57 my ($rvalues) = @_;
5021              
5022             # Given: $rvalues = ref to an array of numbers
5023             # Return: the min, max, and median
5024 42         40 my $num = @{$rvalues};
  42         50  
5025 42 50       55 return unless ($num);
5026              
5027 42         43 my @sorted = sort { $a <=> $b } @{$rvalues};
  100         122  
  42         91  
5028              
5029 42         49 my $min = $sorted[0];
5030 42         42 my $max = $sorted[-1];
5031 42         58 my $imid = int( $num / 2 );
5032 42 100       78 my $median =
5033             @sorted % 2
5034             ? $sorted[$imid]
5035             : ( $sorted[ $imid - 1 ] + $sorted[$imid] ) / 2;
5036              
5037 42         86 return ( $min, $max, $median );
5038             } ## end sub min_max_median
5039              
5040             sub end_signed_number_column {
5041              
5042 30     30 0 47 my ( $rgroup_lines, $rcol_hash, $ix_last ) = @_;
5043              
5044             # Finish formatting a column of unsigned numbers
5045             # Given:
5046             # $rgroup_lines - the current vertical alignment group of lines
5047             # $rcol_hash - a hash of information about this vertical column
5048             # $ix_last - index of the last line of this vertical column
5049             # Task:
5050             # If this is a mixture of signed and unsigned numbers, then add a
5051             # single space before the unsigned numbers to improve appearance.
5052 30 50       53 return unless ($rcol_hash);
5053 30         38 my $jcol = $rcol_hash->{jcol};
5054 30         37 my $unsigned = $rcol_hash->{unsigned_count};
5055 30         39 my $signed = $rcol_hash->{signed_count};
5056 30         46 my $rsigned_lines = $rcol_hash->{rsigned_lines};
5057              
5058 30 50 33     61 if ( !$signed && $unsigned ) {
5059 0         0 DEVEL_MODE
5060             && Fault("avoid calling without mixed signed and unsigned\n");
5061 0         0 return;
5062             }
5063              
5064 30         35 my $pos_start_number = $rcol_hash->{pos_start_number};
5065 30         36 my $char_end_part1 = $rcol_hash->{char_end_part1};
5066 30         49 my $ix_first = $rcol_hash->{ix_first};
5067 30         38 my $nlines = $ix_last - $ix_first + 1;
5068              
5069             # check for skipped lines, shouldn't happen
5070 30 50       46 if ( $signed + $unsigned != $nlines ) {
5071 0         0 my $line = $rgroup_lines->[$ix_last];
5072 0         0 my $rfields = $line->{'rfields'};
5073 0         0 my $text = join EMPTY_STRING, @{$rfields};
  0         0  
5074 0         0 DEVEL_MODE && Fault(<<EOM);
5075             We seem to have miscounted lines, please check:
5076             signed=$signed
5077             j=$jcol
5078             unsigned=$unsigned
5079             ix_first=$ix_first
5080             ix_last=$ix_last
5081             nlines=$nlines
5082             text=$text
5083             EOM
5084 0         0 return;
5085             }
5086              
5087             #-----------------------------------------------------------------
5088             # Form groups of unsigned numbers from the list of signed numbers.
5089             #-----------------------------------------------------------------
5090 30         34 my @unsigned_subgroups;
5091 30         37 my $ix_last_negative = $ix_first - 1;
5092 30         31 my %is_signed;
5093 30         32 foreach my $ix ( @{$rsigned_lines} ) {
  30         40  
5094 45         68 $is_signed{$ix} = 1;
5095 45         48 my $Nu = $ix - $ix_last_negative - 1;
5096 45 100 100     92 if ( $Nu > 0 && $Nu <= $rOpts_valign_signed_numbers_limit ) {
5097 18         33 push @unsigned_subgroups, [ $ix_last_negative + 1, $ix - 1 ];
5098             }
5099 45         58 $ix_last_negative = $ix;
5100             }
5101              
5102             # Exclude groups with more than about 20 consecutive numbers. Little
5103             # visual improvement is gained by padding more than this, and this avoids
5104             # large numbers of differences in a file when a single line is changed.
5105 30         38 my $Nu = $ix_last - $ix_last_negative;
5106 30 100 100     67 if ( $Nu > 0 && $Nu <= $rOpts_valign_signed_numbers_limit ) {
5107 18         28 push @unsigned_subgroups, [ $ix_last_negative + 1, $ix_last ];
5108             }
5109              
5110 30 100       45 if ( !@unsigned_subgroups ) { return } # shouldn't happen
  9         16  
5111              
5112             #--------------------------------------------
5113             # Find number lengths for irregularity checks
5114             #--------------------------------------------
5115             # Padding signed numbers looks best when the numbers, excluding signs,
5116             # all have about the same length. When the lengths are irregular, with
5117             # mostly longer unsigned numbers, it doesn't look good to do this. So
5118             # we need to filter out these bad-looking cases.
5119              
5120             # The 'field_lengths' are unreliable because they may include some
5121             # arbitrary trailing text; see 'substr.t' So we must look for the end of
5122             # the number at a space, comma, or closing container token. Note that these
5123             # lengths include the length of any signs.
5124 21         40 my @len_unsigned;
5125             my @len_signed;
5126 21         0 my @lengths;
5127 21         31 foreach my $ix ( $ix_first .. $ix_last ) {
5128 102         121 my $line = $rgroup_lines->[$ix];
5129 102         116 my $rfield = $line->{'rfields'};
5130 102         124 my $str = substr( $rfield->[$jcol], $pos_start_number );
5131 102 50       249 if ( $str =~ /^([^\s\,\)\]\}]*)/ ) { $str = $1 }
  102         146  
5132 102         105 my $len = length($str);
5133 102 100       155 if ( $is_signed{$ix} ) { push @len_signed, $len }
  33         37  
5134 69         70 else { push @len_unsigned, $len }
5135 102         170 push @lengths, [ $len, $ix ];
5136             }
5137              
5138 21         40 my ( $min_unsigned_length, $max_unsigned_length, $median_unsigned_length )
5139             = min_max_median( \@len_unsigned );
5140 21         42 my ( $min_signed_length_uu, $max_signed_length, $median_signed_length ) =
5141             min_max_median( \@len_signed );
5142              
5143             # Skip padding if no signed numbers exceed unsigned numbers in length
5144 21 50       54 if ( $max_signed_length <= $min_unsigned_length ) {
    100          
5145 0         0 return;
5146             }
5147              
5148             # If max signed length is greatest - all unsigned values can be padded
5149             elsif ( $max_signed_length > $max_unsigned_length ) {
5150              
5151             # Example:
5152             # %wind_dir = (
5153             # 'n' => [ 1, 0 ],
5154             # 'ne' => [ 1, 1 ],
5155             # 'e' => [ 0, 1 ],
5156             # 'se' => [ -1, 1 ],
5157             # 's' => [ -1, 0 ],
5158             # 'sw' => [ -1, -1 ],
5159             # 'w' => [ 0, -1 ],
5160             # 'nw' => [ 1, -1 ],
5161             # '' => [ 0, 0 ],
5162             # );
5163              
5164             # This is the ideal case - ok to continue and pad
5165             }
5166              
5167             # intermediate case: some signed numbers cannot be padded ...
5168             else {
5169              
5170             # We have to take a closer look.
5171             # Here is an example which looks bad if we do padding like this:
5172             # my %hash = (
5173             # X0 => -12867.098241163,
5174             # X1 => 2.31694338671684, # unsigned w/ excess>0
5175             # X2 => 0.0597726714860419, # max length => excess=0
5176             # Y0 => 30043.1335503155, # unsigned w/ excess>0
5177             # Y1 => 0.0525784981597044, # max length => excess=0
5178             # Y2 => -2.32447131600783,
5179             # );
5180              
5181             # To decide what looks okay, we count 'good' and 'bad' line interfaces:
5182             # X0 - X1 = good (X0 is signed and X1 can move)
5183             # X1 - X2 = bad (x1 can move but x2 cannot)
5184             # X2 - Y0 = bad (x2 cannot move but Y0 can move)
5185             # Y0 - Y1 = bad (Y0 can move but Y1 cannot move)
5186             # Y1 - Y2 = bad (Y1 cannot move and Y2 is signed)
5187             # Result: 4 bad interfaces and 1 good => so we will skip this
5188 11         13 my $good_count = 0;
5189 11         14 my $bad_count = 0;
5190 11         15 foreach my $item (@lengths) {
5191 54         59 $item->[0] = $max_unsigned_length - $item->[0];
5192             }
5193 11         14 my $item0 = shift @lengths;
5194 11         15 my ( $excess, $ix ) = @{$item0};
  11         16  
5195 11 50       19 my $immobile_count = $excess ? 0 : 1;
5196 11         13 foreach my $item (@lengths) {
5197 43         41 my $excess_m = $excess;
5198 43         41 my $ix_m = $ix;
5199 43         43 ( $excess, $ix ) = @{$item};
  43         49  
5200 43 100       54 if ( !$excess ) { $immobile_count++ }
  13         13  
5201              
5202 43 100       54 if ( $is_signed{$ix_m} ) {
5203              
5204             # signed-unsigned interface
5205 15 100       27 if ( !$is_signed{$ix} ) {
5206 13 100       19 if ($excess) { $good_count++ }
  8         10  
5207 5         8 else { $bad_count++ }
5208             }
5209              
5210             # signed-signed: ok, not good or bad
5211             }
5212             else {
5213              
5214             # unsigned-signed interface
5215 28 100       38 if ( $is_signed{$ix} ) {
5216 9 50       18 if ($excess_m) { $good_count++ }
  9         14  
5217 0         0 else { $bad_count++ }
5218             }
5219              
5220             # unsigned-unsigned: bad if different
5221             else {
5222 19 100 100     72 if ( $excess_m xor $excess ) {
5223 7         11 $bad_count++;
5224             }
5225             }
5226             }
5227             }
5228              
5229             # Filter 1: skip if more interfaces are 'bad' than 'good'
5230 11 100       20 if ( $bad_count > $good_count ) {
5231 1         7 return;
5232             }
5233              
5234             # Filter 2: skip in a table with multiple 'bad' interfaces and where
5235             # 'most' of the unsigned lengths are shorter than the signed lengths.
5236             # Using the median value makes this insensitive to small changes.
5237 10 50 66     28 if ( $median_unsigned_length >= $median_signed_length
      33        
5238             && $bad_count > 1
5239             && $immobile_count > 1 )
5240             {
5241 0         0 return;
5242             }
5243              
5244             # Anything that gets past these filters should look ok if padded
5245             }
5246              
5247             #---------------------------------------------
5248             # Compute actual available space for each line
5249             #---------------------------------------------
5250 20         23 my %excess_space;
5251 20         24 my $movable_count = 0;
5252 20         26 foreach my $item (@unsigned_subgroups) {
5253 34         58 my ( $ix_min, $ix_max ) = @{$item};
  34         39  
5254 34         48 foreach my $ix ( $ix_min .. $ix_max ) {
5255 56         56 my $line = $rgroup_lines->[$ix];
5256 56         65 my $leading_space_count = $line->{'leading_space_count'};
5257 56         59 my $jmax = $line->{'jmax'};
5258 56         60 my $rfield_lengths = $line->{'rfield_lengths'};
5259 56 50       73 if ( $jcol >= $jmax ) {
5260              
5261             # shouldn't happen
5262 0         0 DEVEL_MODE && Fault("jcol=$jcol >= jmax=$jmax\n");
5263 0         0 return;
5264             }
5265 56         68 my @alignments = @{ $line->{'ralignments'} };
  56         81  
5266 56         72 my $col = $alignments[$jcol]->{'column'};
5267             my $col_start =
5268             $jcol == 0
5269             ? $leading_space_count
5270 56 100       81 : $alignments[ $jcol - 1 ]->{'column'};
5271 56         56 my $avail = $col - $col_start;
5272 56         60 my $field_length = $rfield_lengths->[$jcol];
5273 56         51 my $excess = $avail - $field_length;
5274 56         90 $excess_space{$ix} = $excess;
5275 56 50       71 if ( $excess > 0 ) { $movable_count++ }
  56         86  
5276             }
5277             }
5278              
5279 20 50       36 return unless ($movable_count);
5280              
5281             # Count the number of signed-unsigned interfaces that would change
5282             # if we do the padding
5283 20         22 my $Nc = 0;
5284 20         27 foreach my $item (@unsigned_subgroups) {
5285 34         33 my ( $ix_min, $ix_max ) = @{$item};
  34         41  
5286 34 100 66     78 $Nc++ if ( $excess_space{$ix_min} > 0 && $ix_min != $ix_first );
5287 34 100 66     80 $Nc++ if ( $excess_space{$ix_max} > 0 && $ix_max != $ix_last );
5288             }
5289              
5290             #--------------------------------------------------------------------
5291             # Sparsity check:
5292             # Give up if the number of interface changes will be below the cutoff
5293             #--------------------------------------------------------------------
5294 20 50       37 if ( $unsigned > $Nc * $rOpts_valign_signed_numbers_limit ) {
5295 0         0 return;
5296             }
5297              
5298             #------------------------------------------------------------------------
5299             # Insert an extra space before the unsigned numbers if space is available
5300             #------------------------------------------------------------------------
5301 20         21 foreach my $item (@unsigned_subgroups) {
5302 34         39 my ( $ix_min, $ix_max ) = @{$item};
  34         50  
5303              
5304 34         46 foreach my $ix ( $ix_min .. $ix_max ) {
5305 56 50       100 next if ( $excess_space{$ix} <= 0 );
5306 56         76 my $line = $rgroup_lines->[$ix];
5307 56         59 my $rfields = $line->{'rfields'};
5308 56         59 my $rfield_lengths = $line->{'rfield_lengths'};
5309 56         92 pad_signed_field(
5310             \$rfields->[$jcol], \$rfield_lengths->[$jcol],
5311             $pos_start_number, $char_end_part1
5312             );
5313             }
5314             }
5315 20         72 return;
5316             } ## end sub end_signed_number_column
5317              
5318             sub pad_signed_field {
5319              
5320 56     56 0 77 my ( $rstr, $rstr_len, $pos_start_number, $char_end_part1 ) = @_;
5321              
5322             # Insert an extra space before a number to highlight algebraic signs
5323             # in a column of numbers.
5324             # Given:
5325             # $rstr = ref to string
5326             # $rstr_len = ref to display width of string (could include wide chars)
5327             # $pos_start_number = string position of the leading digit
5328             # $char_end_part1 = character at $pos_start_number - 1
5329             # Task: update $rstr and $rstr_len with a single space
5330              
5331             # First partition the string into $part1 and $part2, so that the
5332             # number starts at the beginning of part2.
5333 56         56 my $part1 = EMPTY_STRING;
5334 56         54 my $part2 = ${$rstr};
  56         63  
5335 56         58 my $str = ${$rstr};
  56         59  
5336 56 100       99 if ( $pos_start_number > 0 ) {
5337 16         17 my $len = length($str);
5338 16 50       25 if ( $pos_start_number >= $len ) {
5339 0         0 DEVEL_MODE && Fault(<<EOM);
5340             Expection position '$pos_start_number' < length $len of string '$str'
5341             EOM
5342 0         0 return;
5343             }
5344 16         18 $part1 = substr( $str, 0, $pos_start_number );
5345 16         20 $part2 = substr( $str, $pos_start_number );
5346              
5347             # VERIFY that we are inserting a new space after either
5348             # (1) an existing space or
5349             # (2) an opening token.
5350             # Otherwise disaster can occur. An error here implies a programming
5351             # error in defining '$pos_start_number'.
5352              
5353 16         24 my $test_char1 = substr( $part1, -1, 1 );
5354 16 50       25 if ( $test_char1 ne $char_end_part1 ) {
5355 0         0 DEVEL_MODE && Fault(<<EOM);
5356             Expecting '$char_end_part1' but saw '$test_char1' in string '$str'
5357             Probably bad position '$pos_start_number'
5358             EOM
5359 0         0 return;
5360             }
5361             }
5362              
5363             # VERIFY we are inserting a space before a digit character
5364 56         70 my $test_char2 = substr( $part2, 0, 1 );
5365 56 50       72 if ( $is_digit_char{$test_char2} ) {
5366 56         58 ${$rstr} = $part1 . SPACE . $part2;
  56         62  
5367 56         54 ${$rstr_len} += 1;
  56         72  
5368             }
5369             else {
5370 0         0 DEVEL_MODE && Fault(<<EOM);
5371             Expecting test char2 as leading digit but saw '$test_char2' in string '$str'
5372             May be bad position '$pos_start_number'
5373             EOM
5374             }
5375 56         95 return;
5376             } ## end sub pad_signed_field
5377              
5378             sub split_field {
5379              
5380 124     124 0 208 my ( $pat1, $field, $pattern ) = @_;
5381              
5382             # Given;
5383             # $pat1 = first part of a pattern before a numeric type 'n'
5384             # $field = corresponding text field
5385             # $pattern = full pattern
5386             # Return:
5387             # $pos_start_number = position in $field where the number should start
5388             # = 0 if cannot find
5389             # $char_end_part1 = the character preceding $pos_start_number
5390             # $ch_opening = the preceding opening container character, if any
5391              
5392             # We have to find where the possible number starts in this field.
5393             # The safe thing to do is return @fail if anything does not look right.
5394              
5395 124         139 my $pos_start_number = 0;
5396 124         153 my $char_end_part1 = EMPTY_STRING;
5397 124         137 my $ch_opening = EMPTY_STRING;
5398 124         234 my @fail = ( $pos_start_number, $char_end_part1, $ch_opening );
5399              
5400             # Be sure there is just 'n' in the pattern. Multiple terms can occur
5401             # when fields are joined, but since we are jumping into the middle
5402             # of a field it is safest not to try to handle them.
5403 124         213 my $n_count = ( $pattern =~ tr/n/n/ );
5404 124 100 66     354 if ( $n_count && $n_count > 1 ) {
5405 20         60 return @fail;
5406             }
5407              
5408             # Same thing for commas
5409 104         146 my $comma_count = ( $pattern =~ tr/,/,/ );
5410 104 50 66     252 if ( $comma_count && $comma_count > 1 ) {
5411 0         0 return @fail;
5412             }
5413              
5414             # Require 0 or 1 braces
5415 104         137 my $len_field = length($field);
5416 104         122 my $len_pat1 = length($pat1);
5417 104 50 33     275 return @fail unless ( $len_pat1 && $len_field );
5418              
5419             # Look at the pattern ending
5420 104         117 my $ending_b = 0;
5421 104         149 my $ch = substr( $pat1, -1, 1 );
5422 104 100       224 if ( $ch eq 'b' ) {
5423 79         90 $ending_b = 1;
5424 79         120 $ch = substr( $pat1, -2, 1 );
5425 79         129 $char_end_part1 = SPACE;
5426             }
5427              
5428             # handle either '{b' or '{'
5429 104 100       202 if ( $ch eq '{' ) {
5430              
5431             # Only one brace
5432 56         71 my $brace_count = ( $pat1 =~ tr/\{/\{/ );
5433 56 50       107 return @fail if ( $brace_count != 1 );
5434              
5435 56         72 my $i_paren = index( $field, '(' );
5436 56         74 my $i_bracket = index( $field, '[' );
5437 56         61 my $i_brace = index( $field, '{' );
5438 56         65 my $i_opening = length($field);
5439 56 100       94 if ( $i_paren >= 0 ) {
5440 18         24 $i_opening = $i_paren;
5441 18         25 $ch_opening = '(';
5442             }
5443 56 100 66     132 if ( $i_bracket >= 0
5444             && $i_bracket < $i_opening )
5445             {
5446 38         43 $i_opening = $i_bracket;
5447 38         60 $ch_opening = '[';
5448             }
5449 56 50 33     106 if ( $i_brace >= 0 && $i_brace < $i_opening ) {
5450 0         0 $i_opening = $i_brace;
5451 0         0 $ch_opening = '{';
5452             }
5453 56 50 33     151 if ( $i_opening >= 0
5454             && $i_opening < length($field) - 1 )
5455             {
5456 56         74 $pos_start_number = $i_opening + 1 + $ending_b;
5457 56 100       113 $char_end_part1 = $ch_opening
5458             if ( !$ending_b );
5459             }
5460             else {
5461             ## strange - could not find the opening token
5462             }
5463             }
5464              
5465             # no braces: maybe '=>b'
5466             else {
5467              
5468             # looking for patterns ending in '=b' or '=>b'
5469 48 50       96 if ( !$ending_b ) { return @fail }
  0         0  
5470              
5471             # find the = in the text
5472 48         80 my $pos_equals = index( $field, '=' );
5473 48 100       157 return @fail if ( $pos_equals < 0 );
5474              
5475             # be sure there are no other '=' in the pattern
5476 37         47 my $equals_count = ( $pat1 =~ tr/=/=/ );
5477 37 100       100 return @fail if ( $equals_count != 1 );
5478              
5479 34 100 66     179 if ( $len_pat1 >= 2 && substr( $pat1, -2, 2 ) eq '=b' ) {
    100 66        
5480 8         10 $pos_start_number = $pos_equals + 2;
5481             }
5482             elsif ( $len_pat1 >= 3 && substr( $pat1, -3, 3 ) eq '=>b' ) {
5483 16         29 $pos_start_number = $pos_equals + 3;
5484             }
5485             else {
5486              
5487             # cannot handle this pattern
5488 10         39 return @fail;
5489             }
5490             }
5491              
5492 80 50 33     215 if ( $pos_start_number <= 0 || $pos_start_number >= $len_field ) {
5493 0         0 return @fail;
5494             }
5495              
5496 80         255 return ( $pos_start_number, $char_end_part1, $ch_opening );
5497             } ## end sub split_field
5498              
5499             sub field_matches_end_pattern {
5500              
5501 264     264 0 336 my ( $field2, $pat2 ) = @_;
5502              
5503             # Check that a possible numeric field matches the ending pattern
5504              
5505             # Given:
5506             # $field2 = the rest of the field after removing any sign
5507             # $pat2 = the end pattern of this field
5508             # Return:
5509             # false if field is definitely non-numeric
5510             # true otherwise
5511              
5512 264         322 my $next_char = substr( $pat2, 1, 1 );
5513 264         402 my $field2_trim = EMPTY_STRING;
5514              
5515             # if pattern is one of: 'n,', 'n,b'
5516 264 100       392 if ( $next_char eq COMMA ) {
    100          
    50          
5517 192         231 my $icomma = index( $field2, COMMA );
5518 192 50       273 if ( $icomma >= 0 ) {
5519 192         262 $field2_trim = substr( $field2, 0, $icomma );
5520             }
5521             }
5522              
5523             # if pattern is one of: 'nb', 'nb}', 'nb},'
5524             elsif ( $next_char eq 'b' ) {
5525 47         55 my $ispace = index( $field2, SPACE );
5526 47 50       81 if ( $ispace >= 0 ) {
5527 47         60 $field2_trim = substr( $field2, 0, $ispace );
5528             }
5529             }
5530              
5531             # if pattern is one of 'n},', 'n};'
5532             elsif ( $next_char eq '}' ) {
5533 25 50       83 if ( $field2 =~ /^([^\)\}\]]+)/ ) {
5534 25         49 $field2_trim = $1;
5535             }
5536             }
5537              
5538             # unrecognized pattern
5539             else {
5540 0         0 DEVEL_MODE && Fault(<<EOM);
5541             Unexpected ending pattern '$pat2' next='$next_char' field2='$field2'
5542             The hash 'is_leading_sign_pattern' seems to have changed but the code
5543             has not been updated to handle it. Please fix.
5544             EOM
5545 0         0 return;
5546             }
5547              
5548 264 50       430 if ( !length($field2_trim) ) {
5549 0         0 DEVEL_MODE
5550             && Fault(
5551             "STRANGE: cannot find end of field=$field2 for pat=$pat2 \n");
5552 0         0 return;
5553             }
5554              
5555             # Reject obviously non-numeric fields just to be sure we did not
5556             # jump into a quote of some kind
5557 264 100       695 if ( $field2_trim !~ /^[\d\.\+\-abcdefpx_]+$/i ) {
5558             DEBUG_VSN
5559 2         3 && print {*STDERR}
5560             "Rejecting match to pat2='$pat2' with next=$next_char field2=$field2 trimmed='$field2_trim'\n";
5561 2         3 return;
5562             }
5563 262         413 return 1;
5564             } ## end sub field_matches_end_pattern
5565              
5566             sub pad_signed_number_columns {
5567              
5568 70     70 0 162 my ($rgroup_lines) = @_;
5569              
5570             # Given:
5571             # $rgroup_lines = the current vertical alignment group of lines
5572             # Task:
5573             # Look for columns of aligned numeric values, some of whose numbers
5574             # have algebraic signs. Add a leading space to the unsigned
5575             # numbers, if possible, so that the just the signs appear as the first
5576             # character. Example of what we want to do:
5577              
5578             # my @correct = (
5579             # [ 123456.79, 86753090000.868, 11 ],
5580             # [ -123456.79, -86753090000.868, -11 ],
5581             # [ 123456.001, 80.080, 10 ],
5582             # [ -123456.001, -80.080, 0 ],
5583             # [ 10.9, 10.9, 11 ],
5584             # );
5585              
5586             # The logic here is complex because we are working with bits of text
5587             # which have been broken into patterns which are convenient for the
5588             # vertical aligner, but we no longer have the original tokenization
5589             # which would have indicated the precise bounds of numbers. So we
5590             # have to proceed very carefully with lots of checks. There are
5591             # more checks than really necessary now because originally numbers
5592             # and quotes were both indicated with pattern 'Q'. But now numbers are
5593             # uniquely marked as pattern 'n', so there is less risk of an error.
5594             # The extra checks take very little time so they are retained.
5595              
5596 70 50       182 return unless ($rOpts_valign_signed_numbers);
5597              
5598 70         158 my %column_info;
5599             my @columns;
5600              
5601             #----------------
5602             # loop over lines
5603             #----------------
5604 70         120 my $ix_line = -1;
5605 70         121 my $jmax = -1;
5606 70         1217 foreach my $line ( @{$rgroup_lines} ) {
  70         134  
5607 349         383 $ix_line++;
5608 349         419 my $jmax_last = $jmax;
5609 349         476 $jmax = $line->{'jmax'};
5610 349         504 my $jmax_change = $jmax ne $jmax_last;
5611              
5612 349         374 my @alignments = @{ $line->{'ralignments'} };
  349         562  
5613 349         516 my $rfields = $line->{'rfields'};
5614 349         463 my $rpatterns = $line->{'rpatterns'};
5615 349         403 my $rtokens = $line->{'rtokens'};
5616              
5617             #-----------------------------------------------
5618             # Check for a reduction in the number of columns
5619             #-----------------------------------------------
5620 349 100       619 if ( $jmax < $jmax_last ) {
5621              
5622 11         27 foreach my $jcol ( keys %column_info ) {
5623              
5624             # end any stranded columns on the right
5625 6 100       19 next if ( $jcol < $jmax );
5626 2         3 my $rcol_hash = $column_info{$jcol};
5627 2 50       6 next unless ($rcol_hash);
5628 2 0 33     4 if ( $rcol_hash->{signed_count}
5629             && $rcol_hash->{unsigned_count} )
5630             {
5631 0         0 end_signed_number_column( $rgroup_lines, $rcol_hash,
5632             $ix_line - 1 );
5633             }
5634 2         6 delete $column_info{$jcol};
5635             }
5636              
5637             # Try to keep the end data column running; test case 'rfc.in'
5638             # The last item in a list will still need a trailing comma.
5639 11         23 my $jcol = $jmax - 1;
5640 11 100 66     47 if ( $jcol >= 0 && $column_info{$jcol} ) {
5641 2         5 my $alignment = $alignments[$jcol];
5642 2         4 my $old_col = $columns[$jcol];
5643 2         6 my $col = $alignment->{column};
5644              
5645 2 0 33     11 if (
      0        
      33        
5646             $col < $old_col
5647              
5648             # only do this if the text has a leading digit
5649             && $rfields->[$jcol] =~ /^([+-]?)\d/
5650              
5651             # and a signed number has been seen - issue c375
5652             && ( $1 || $column_info{$jcol}->{signed_count} )
5653             )
5654             {
5655 0         0 my $spaces_needed = $old_col - $col;
5656 0         0 my $spaces_available =
5657             $line->get_available_space_on_right();
5658 0 0       0 if ( $spaces_available >= $spaces_needed ) {
5659 0         0 $line->increase_field_width( $jcol, $spaces_needed );
5660             }
5661             }
5662             }
5663             }
5664              
5665             #--------------------------------------------
5666             # Loop over fields except last (side comment)
5667             #--------------------------------------------
5668 349         542 for my $jcol ( 0 .. $jmax - 1 ) {
5669              
5670             #-----------------------------------------
5671             # Decide if this is a new alignment column
5672             #-----------------------------------------
5673 785         851 my $alignment = $alignments[$jcol];
5674 785         855 my $old_col = $columns[$jcol];
5675 785         862 my $col = $alignment->{column};
5676 785         850 $columns[$jcol] = $col;
5677 785 100 100     1694 if ( defined($old_col) && $old_col != $col ) {
5678 55         117 foreach my $jcol_old ( keys %column_info ) {
5679 17 100       49 next if ( $jcol_old < $jcol );
5680 11         22 my $rcol_hash = $column_info{$jcol_old};
5681 11 50 66     40 if ( $rcol_hash->{signed_count}
5682             && $rcol_hash->{unsigned_count} )
5683             {
5684 1         4 end_signed_number_column( $rgroup_lines, $rcol_hash,
5685             $ix_line - 1 );
5686             }
5687 11         44 delete $column_info{$jcol_old};
5688             }
5689             }
5690              
5691             # A new padded sign column can only start at an alignment change
5692 785         972 my $rcol_hash = $column_info{$jcol};
5693              
5694             #------------------------------------------------------------
5695             # Examine this field, looking for signed and unsigned numbers
5696             #------------------------------------------------------------
5697 785         994 my $field = $rfields->[$jcol];
5698 785         943 my $pattern = $rpatterns->[$jcol];
5699              
5700 785         755 my $is_signed_number = 0;
5701 785         774 my $is_unsigned_number = 0;
5702              
5703             #--------------------------------------------------------
5704             # set $pos_start_number = index in field of digit or sign
5705             #--------------------------------------------------------
5706 785         744 my $pos_start_number = 0;
5707 785         771 my $char_end_part1 = EMPTY_STRING;
5708 785         776 my $ch_opening = EMPTY_STRING;
5709              
5710             # Set $field_ok to false on encountering any problem
5711             # Do not pad signed and unsigned hash keys
5712 785   66     1861 my $field_ok = length($field) > 0
5713             && substr( $rtokens->[$jcol], 0, 2 ) ne '=>';
5714              
5715 785 100 66     1625 if ( $field_ok && $pattern ) {
5716              
5717             # Split the pattern at the first 'n'
5718             # $pat1 = pattern before the 'n' (if any)
5719             # $pat2 = pattern starting at the 'n'
5720 755         941 my ( $pat1, $pat2 );
5721 755         877 my $posq = index( $pattern, 'n' );
5722 755 100       944 if ( $posq < 0 ) {
5723 333         358 $field_ok = 0;
5724             }
5725             else {
5726             # Just look at up to 3 of the pattern characters
5727             # We require $pat2 to have one of the known patterns
5728 422         503 $pat1 = substr( $pattern, 0, $posq );
5729 422         530 $pat2 = substr( $pattern, $posq, 3 );
5730 422         638 $field_ok = $is_leading_sign_pattern{$pat2};
5731             }
5732              
5733 755 100       1012 if ($field_ok) {
5734              
5735             # If the number starts within the field then we must
5736             # find its offset position.
5737 310 100       441 if ($pat1) {
5738              
5739             # Note: an optimization would be to remember previous
5740             # calls for each column and use them if possible, but
5741             # benchmarking shows that this is not necessary.
5742             # See .ba54 for example coding.
5743 124         252 ( $pos_start_number, $char_end_part1, $ch_opening ) =
5744             split_field( $pat1, $field, $pattern );
5745              
5746 124   33     234 $field_ok ||= $pos_start_number;
5747             }
5748              
5749 310 50       453 if ($field_ok) {
5750              
5751             # look for an optional + or - sign
5752 310         362 my $test_char = substr( $field, $pos_start_number, 1 );
5753 310         304 my $sign;
5754 310 100       493 if ( $is_plus_or_minus{$test_char} ) {
5755 49         67 $sign = $test_char;
5756 49         68 $test_char =
5757             substr( $field, $pos_start_number + 1, 1 );
5758             }
5759              
5760             # and a digit
5761 310 100       480 if ( $is_digit_char{$test_char} ) {
5762 264         269 my $field2;
5763 264 100       329 if ($sign) {
5764 47         48 $is_signed_number = 1;
5765 47         58 $field2 =
5766             substr( $field, $pos_start_number + 1 );
5767             }
5768             else {
5769 217         217 $is_unsigned_number = 1;
5770 217 100       303 $field2 =
5771             $pos_start_number
5772             ? substr( $field, $pos_start_number )
5773             : $field;
5774             }
5775              
5776             # Check for match to ending pattern
5777 264         394 $field_ok =
5778             field_matches_end_pattern( $field2, $pat2 );
5779             }
5780             else {
5781 46         69 $field_ok = 0;
5782             }
5783             }
5784             }
5785             }
5786              
5787             #----------------------
5788             # Figure out what to do
5789             #----------------------
5790              
5791             # we require a signed or unsigned number field
5792             # which is not a hash key
5793 785   66     1585 $field_ok &&= ( $is_signed_number || $is_unsigned_number );
      66        
5794              
5795             # if a column has not started..
5796 785 100       1002 if ( !$rcol_hash ) {
5797              
5798             # give up if this is cannot start a new column
5799 584 100       1261 next if ( !$field_ok );
5800              
5801             # otherwise continue on to start a new column
5802              
5803             }
5804              
5805             # if a column has been started...
5806             else {
5807              
5808             # and this cannot be added to it
5809 201 50 100     805 if ( !$field_ok
      66        
      66        
5810             || $rcol_hash->{pos_start_number} ne $pos_start_number
5811             || $rcol_hash->{char_end_part1} ne $char_end_part1
5812             || $rcol_hash->{col} ne $col )
5813             {
5814              
5815             # then end the current column and start over
5816 34 0 33     57 if ( $rcol_hash->{signed_count}
5817             && $rcol_hash->{unsigned_count} )
5818             {
5819 0         0 end_signed_number_column( $rgroup_lines, $rcol_hash,
5820             $ix_line - 1 );
5821             }
5822 34         48 delete $column_info{$jcol};
5823 34         71 $rcol_hash = undef;
5824             }
5825             }
5826              
5827 285         260 if (DEBUG_VSN) {
5828             my $exists = defined($rcol_hash);
5829             print
5830             "VSN: line=$ix_line change=$jmax_change jcol=$jcol field=$field exists?=$exists unsigned?=$is_unsigned_number signed?=$is_signed_number\n";
5831             }
5832              
5833             #---------------------------------------
5834             # Either start a new column, if possible
5835             #---------------------------------------
5836 285 100       396 if ( !defined($rcol_hash) ) {
5837              
5838 118 100       207 next if ( !$field_ok );
5839              
5840 95 100       201 my $rsigned_lines = $is_signed_number ? [$ix_line] : [];
5841 95         640 $column_info{$jcol} = {
5842             unsigned_count => $is_unsigned_number,
5843             signed_count => $is_signed_number,
5844             pos_start_number => $pos_start_number,
5845             char_end_part1 => $char_end_part1,
5846             ix_first => $ix_line,
5847             col => $col,
5848             jcol => $jcol,
5849             rsigned_lines => $rsigned_lines,
5850             };
5851             }
5852              
5853             #------------------------------
5854             # or extend the existing column
5855             #------------------------------
5856             else {
5857 167         211 $rcol_hash->{unsigned_count} += $is_unsigned_number;
5858 167         180 $rcol_hash->{signed_count} += $is_signed_number;
5859 167 100       323 if ($is_signed_number) {
5860 35         40 push @{ $rcol_hash->{rsigned_lines} }, $ix_line;
  35         90  
5861             }
5862             }
5863             }
5864             }
5865              
5866             #-------------------------------------
5867             # Loop to finish any remaining columns
5868             #-------------------------------------
5869 70         186 foreach my $jcol ( keys %column_info ) {
5870 48         69 my $rcol_hash = $column_info{$jcol};
5871 48 100 100     154 if ( $rcol_hash->{signed_count} && $rcol_hash->{unsigned_count} ) {
5872 29         61 end_signed_number_column( $rgroup_lines, $rcol_hash, $ix_line );
5873             }
5874             }
5875 70         239 return;
5876             } ## end sub pad_signed_number_columns
5877              
5878             #########################################
5879             # CODE SECTION 7: Pad Wide Equals Columns
5880             #########################################
5881              
5882 44     44   333 use constant DEBUG_WEC => 0;
  44         80  
  44         180860  
5883              
5884             sub end_wide_equals_column {
5885              
5886 12     12 0 21 my ( $rgroup_lines, $rcol_hash, $ix_last ) = @_;
5887              
5888             # Finish formatting a column of wide equals
5889             # Given:
5890             # $rgroup_lines - the current vertical alignment group of lines
5891             # $rcol_hash - a hash of information about this vertical column
5892             # $ix_last - index of the last line of this vertical column
5893              
5894 12 50       26 return unless ($rcol_hash);
5895 12         20 my $jcol = $rcol_hash->{jcol};
5896 12         17 my $col = $rcol_hash->{col};
5897 12         21 my $min_width = $rcol_hash->{min_width};
5898 12         16 my $max_width = $rcol_hash->{max_width};
5899 12         28 my $rwidths = $rcol_hash->{rwidths};
5900 12         16 my $ix_first = $rcol_hash->{ix_first};
5901              
5902             # check for skipped lines, shouldn't happen
5903 12         18 my $nlines = $ix_last - $ix_first + 1;
5904 12         12 my $num = @{$rwidths};
  12         21  
5905 12 50       21 if ( $num != $nlines ) {
5906 0         0 my $line = $rgroup_lines->[$ix_last];
5907 0         0 my $rfields = $line->{'rfields'};
5908 0         0 my $text = join EMPTY_STRING, @{$rfields};
  0         0  
5909 0         0 DEVEL_MODE && Fault(<<EOM);
5910             We seem to have miscounted lines, please check:
5911             nlines=$nlines
5912             num saved=$num
5913             min width=$min_width
5914             max width=$max_width
5915             j=$jcol
5916             ix_first=$ix_first
5917             ix_last=$ix_last
5918             text=$text
5919             EOM
5920 0         0 return;
5921             }
5922              
5923             #------------------------------------------------------
5924             # loop over all lines of this vertical alignment column
5925             #------------------------------------------------------
5926              
5927             my (
5928 12         39 $current_alignment, $starting_colp,
5929             $current_line, @previous_linked_lines
5930             );
5931 12         15 foreach my $item ( @{$rwidths} ) {
  12         21  
5932 50         47 my ( $ix, $width ) = @{$item};
  50         73  
5933 50         56 my $line = $rgroup_lines->[$ix];
5934              
5935             # add leading spaces to the shorter equality tokens to get
5936             # vertical alignment of the '=' signs
5937 50         53 my $jmax = $line->{'jmax'};
5938 50         71 my $jcolp = $jcol + 1;
5939              
5940 50         51 my @alignments = @{ $line->{'ralignments'} };
  50         62  
5941 50         52 my $alignment = $alignments[$jcolp];
5942 50         52 my $colp = $alignment->{column};
5943              
5944             #------------------------------------------------------------
5945             # Transfer column width changes between equivalent alignments
5946             #------------------------------------------------------------
5947              
5948             # This step keeps alignments to the right correct in case the
5949             # alignment object changes but the actual alignment col does not.
5950             # It is extremely rare for this to occur. Issue c353.
5951              
5952             # nothing to do if no more real alignments on right
5953 50 100       101 if ( $jcolp >= $jmax - 1 ) {
    100          
    100          
5954 18         20 $current_alignment = undef;
5955 18         18 $current_line = undef;
5956 18         15 @previous_linked_lines = ();
5957             }
5958              
5959             # handle new rhs alignment
5960             elsif ( !$current_alignment ) {
5961 9         13 $current_alignment = $alignment;
5962 9         9 $current_line = $line;
5963 9         10 $starting_colp = $colp;
5964 9         11 @previous_linked_lines = ();
5965             }
5966              
5967             # handle change in existing alignment
5968             elsif ( refaddr($alignment) != refaddr($current_alignment) ) {
5969              
5970             # change rhs alignment column - new vertical group on right
5971 6 100       16 if ( $starting_colp != $colp ) {
5972 1         1 $starting_colp = $colp;
5973 1         2 @previous_linked_lines = ();
5974             }
5975             else {
5976              
5977             # Same starting alignment col on right, but different alignment
5978             # object. See if we must increase width of this new alignment
5979             # object.
5980 5         7 my $current_colp = $current_alignment->{column};
5981 5 100       8 if ( $current_colp > $colp ) {
5982 3         4 my $excess = $current_colp - $colp;
5983 3         6 my $padding_available =
5984             $line->get_available_space_on_right();
5985 3 50       6 if ( $excess <= $padding_available ) {
5986 3         5 $line->increase_field_width( $jcolp, $excess );
5987 3         4 $colp = $alignment->{column};
5988             }
5989             }
5990              
5991             # remember the previous line in case we have to go back and
5992             # increase its width
5993 5         8 push @previous_linked_lines, $current_line;
5994             }
5995 6         6 $current_alignment = $alignment;
5996 6         8 $current_line = $line;
5997             }
5998             else {
5999             ## continuing with same alignment
6000             }
6001              
6002             #-----------------------
6003             # add any needed padding
6004             #-----------------------
6005 50         55 my $pad = $max_width - $width;
6006 50 100       74 if ( $pad > 0 ) {
6007              
6008 33         40 my $rfields = $line->{'rfields'};
6009 33         38 my $rfield_lengths = $line->{'rfield_lengths'};
6010              
6011 33         38 my $lenp = $rfield_lengths->[$jcolp];
6012 33         39 my $avail = $colp - $col;
6013 33         45 my $excess = $lenp + $pad - $avail;
6014              
6015 33 100       48 if ( $excess > 0 ) {
6016              
6017 13         32 my $padding_available = $line->get_available_space_on_right();
6018 13 100       21 if ( $excess <= $padding_available ) {
6019 12         24 $line->increase_field_width( $jcolp, $excess );
6020              
6021             # Increase space of any previous linked lines
6022 12         18 foreach my $line_prev (@previous_linked_lines) {
6023 1         2 $padding_available =
6024             $line_prev->get_available_space_on_right();
6025 1 50       3 if ( $excess <= $padding_available ) {
6026 1         3 $line_prev->increase_field_width( $jcolp, $excess );
6027             }
6028             else {
6029 0         0 last;
6030             }
6031             }
6032             }
6033             else {
6034 1         2 $pad = 0;
6035             }
6036              
6037             }
6038              
6039             # Add spaces
6040 33         55 $rfields->[$jcolp] = ( SPACE x $pad ) . $rfields->[$jcolp];
6041 33         55 $rfield_lengths->[$jcolp] += $pad;
6042             }
6043             }
6044 12         26 return;
6045             } ## end sub end_wide_equals_column
6046              
6047             sub pad_wide_equals_columns {
6048              
6049 12     12 0 24 my ($rgroup_lines) = @_;
6050              
6051             # Given:
6052             # $rgroup_lines = the current vertical alignment group of lines
6053             # Task:
6054             # Look for columns of aligned equals tokens, some of which may be
6055             # things like '-=', '&&=', etc. Increase the field length of the
6056             # previous field by 1 or 2 spaces where necessary and possible so
6057             # that alignment of all '=' occurs. For example, given
6058              
6059             # $j /= 2;
6060             # $pow2 = $pow2 * $pow2;
6061              
6062             # In this case we want to add a leading space '=' term to get
6063             # $j /= 2;
6064             # $pow2 = $pow2 * $pow2;
6065              
6066             # The logic here is somewhat similar to sub pad_signed_number_columns
6067              
6068 12 50       22 return unless ($rOpts_valign_wide_equals);
6069              
6070 12         18 my %column_info;
6071             my @columns;
6072              
6073             #----------------
6074             # loop over lines
6075             #----------------
6076 12         14 my $ix_line = -1;
6077 12         16 my $jmax = -1;
6078 12         17 foreach my $line ( @{$rgroup_lines} ) {
  12         15  
6079 50         51 $ix_line++;
6080 50         56 my $jmax_last = $jmax;
6081 50         57 $jmax = $line->{'jmax'};
6082 50         68 my $jmax_change = $jmax ne $jmax_last;
6083              
6084 50         49 my @alignments = @{ $line->{'ralignments'} };
  50         95  
6085 50         62 my $rfields = $line->{'rfields'};
6086 50         74 my $rtokens = $line->{'rtokens'};
6087              
6088             #-----------------------------------------------
6089             # Check for a reduction in the number of columns
6090             #-----------------------------------------------
6091 50 100       75 if ( $jmax < $jmax_last ) {
6092              
6093 6         10 foreach my $jcol ( keys %column_info ) {
6094              
6095             # end any stranded columns on the right
6096 6 50       15 next if ( $jcol < $jmax );
6097 0         0 my $rcol_hash = $column_info{$jcol};
6098 0 0       0 next unless ($rcol_hash);
6099 0 0       0 if ( $rcol_hash->{max_width} > $rcol_hash->{min_width} ) {
6100 0         0 end_wide_equals_column( $rgroup_lines, $rcol_hash,
6101             $ix_line - 1 );
6102             }
6103 0         0 delete $column_info{$jcol};
6104             }
6105             }
6106              
6107             #--------------------------------------------------
6108             # Loop over fields except last field (side comment)
6109             #--------------------------------------------------
6110 50         82 for my $jcol ( 0 .. $jmax - 1 ) {
6111              
6112             #-----------------------------------------
6113             # Decide if this is a new alignment column
6114             #-----------------------------------------
6115 147         145 my $alignment = $alignments[$jcol];
6116 147         153 my $old_col = $columns[$jcol];
6117 147         157 my $col = $alignment->{column};
6118 147         147 $columns[$jcol] = $col;
6119 147 100 100     263 if ( defined($old_col) && $old_col != $col ) {
6120 14         25 foreach my $jcol_old ( keys %column_info ) {
6121 14 50       24 next if ( $jcol_old < $jcol );
6122 0         0 my $rcol_hash = $column_info{$jcol_old};
6123 0 0       0 if ( $rcol_hash->{max_width} > $rcol_hash->{min_width} ) {
6124 0         0 end_wide_equals_column( $rgroup_lines, $rcol_hash,
6125             $ix_line - 1 );
6126             }
6127 0         0 delete $column_info{$jcol_old};
6128             }
6129             }
6130              
6131             # A new wide equals column can only start at an alignment change
6132 147         166 my $rcol_hash = $column_info{$jcol};
6133              
6134             #------------------------------------------------------
6135             # Examine this field, looking for equals or wide equals
6136             #------------------------------------------------------
6137 147         171 my $field_next = $rfields->[ $jcol + 1 ];
6138 147         158 my $token = $rtokens->[$jcol];
6139              
6140             # See if this is an equals alignment group;
6141             # indicated by alignment token of '=' followed by a digit
6142 147         150 my $len_equals_symbol = 0;
6143 147 100 100     339 if ( length($token) > 1
      100        
6144             && substr( $token, 0, 1 ) eq '='
6145             && $is_digit_char{ substr( $token, 1, 1 ) } )
6146             {
6147              
6148             # find the actual equality symbol which starts the next field
6149             # i.e. '=' or '**=' or '-=' etc. We just need its length.
6150 50         61 my $pos = index( $field_next, '=' );
6151 50 50 33     115 if ( $pos >= 0 && $pos <= 2 ) {
6152 50         53 $len_equals_symbol = $pos + 1;
6153             }
6154             }
6155              
6156             # if a column has not started..
6157 147 100       176 if ( !$rcol_hash ) {
6158              
6159             # give up if this is cannot start a new column
6160 109 100       199 next if ( !$len_equals_symbol );
6161              
6162             # otherwise continue on to start a new column
6163              
6164             }
6165              
6166             # if a column has been started...
6167             else {
6168              
6169             # and this cannot be added to it
6170 38 50 33     89 if ( !$len_equals_symbol || $rcol_hash->{col} ne $col ) {
6171              
6172             # then end the current column and start over
6173 0 0       0 if ( $rcol_hash->{max_width} > $rcol_hash->{min_width} ) {
6174 0         0 end_wide_equals_column( $rgroup_lines, $rcol_hash,
6175             $ix_line - 1 );
6176             }
6177 0         0 delete $column_info{$jcol};
6178 0         0 $rcol_hash = undef;
6179             }
6180             }
6181              
6182 50         46 if (DEBUG_WEC) {
6183             my $exists = defined($rcol_hash);
6184             print
6185             "WEA: line=$ix_line change=$jmax_change jcol=$jcol field=$field_next exists?=$exists equals?=$len_equals_symbol\n";
6186             }
6187              
6188             #---------------------------------------
6189             # Either start a new column, if possible
6190             #---------------------------------------
6191 50 100       69 if ( !defined($rcol_hash) ) {
6192              
6193 12 50       17 next if ( !$len_equals_symbol );
6194              
6195 12         123 $column_info{$jcol} = {
6196             ix_first => $ix_line,
6197             col => $col,
6198             jcol => $jcol,
6199             min_width => $len_equals_symbol,
6200             max_width => $len_equals_symbol,
6201             rwidths => [ [ $ix_line, $len_equals_symbol ] ],
6202             };
6203             }
6204              
6205             #------------------------------
6206             # or extend the existing column
6207             #------------------------------
6208             else {
6209 38 100       58 if ( $len_equals_symbol > $rcol_hash->{max_width} ) {
6210 9         12 $rcol_hash->{max_width} = $len_equals_symbol;
6211             }
6212 38 100       58 if ( $len_equals_symbol < $rcol_hash->{min_width} ) {
6213 4         5 $rcol_hash->{min_width} = $len_equals_symbol;
6214             }
6215 38         36 push @{ $rcol_hash->{rwidths} },
  38         85  
6216             [ $ix_line, $len_equals_symbol ];
6217             }
6218             }
6219             }
6220              
6221             #-------------------------------------
6222             # Loop to finish any remaining columns
6223             #-------------------------------------
6224 12         23 foreach my $jcol ( keys %column_info ) {
6225 12         18 my $rcol_hash = $column_info{$jcol};
6226 12 50       17 if ( $rcol_hash->{max_width} > $rcol_hash->{min_width} ) {
6227 12         30 end_wide_equals_column( $rgroup_lines, $rcol_hash, $ix_line );
6228             }
6229             }
6230 12         49 return;
6231             } ## end sub pad_wide_equals_columns
6232              
6233             ###############################
6234             # CODE SECTION 8: Output Step A
6235             ###############################
6236              
6237             sub valign_output_step_A {
6238              
6239 3637     3637 0 5551 my ( $self, $rinput_hash ) = @_;
6240              
6241             #------------------------------------------------------------
6242             # This is Step A in writing vertically aligned lines.
6243             # The line is prepared according to the alignments which have
6244             # been found. Then it is shipped to the next step.
6245             #------------------------------------------------------------
6246              
6247             my (
6248              
6249             $line,
6250             $min_ci_gap,
6251             $do_not_align,
6252             $group_leader_length,
6253             $extra_leading_spaces,
6254             $level,
6255             $maximum_line_length,
6256              
6257             ) =
6258              
6259 3637         8139 @{$rinput_hash}{
6260 3637         5034 qw(
6261             line
6262             min_ci_gap
6263             do_not_align
6264             group_leader_length
6265             extra_leading_spaces
6266             level
6267             maximum_line_length
6268             )
6269             };
6270              
6271             my (
6272              
6273             $rfields,
6274             $rfield_lengths,
6275             $leading_space_count,
6276             $outdent_long_lines,
6277             $maximum_field_index,
6278             $rvertical_tightness_flags,
6279             $Kend,
6280             $level_end,
6281              
6282             ) =
6283              
6284 3637         9200 @{$line}{
6285 3637         5157 qw(
6286             rfields
6287             rfield_lengths
6288             leading_space_count
6289             outdent_long_lines
6290             jmax
6291             rvertical_tightness_flags
6292             Kend
6293             level_end
6294             )
6295             };
6296              
6297             # Check for valid hash keys at end of lifetime of $line during development
6298 3637         3872 DEVEL_MODE
6299             && check_keys( $line, \%valid_LINE_keys,
6300             "Checking line keys at valign_output_step_A", 1 );
6301              
6302             # add any extra spaces
6303 3637 100       5764 if ( $leading_space_count > $group_leader_length ) {
6304 49         99 $leading_space_count += $min_ci_gap;
6305             }
6306              
6307 3637         5477 my $str = $rfields->[0];
6308 3637         4465 my $str_len = $rfield_lengths->[0];
6309              
6310 3637         4031 my @alignments = @{ $line->{'ralignments'} };
  3637         6853  
6311 3637 50       6627 if ( @alignments != $maximum_field_index + 1 ) {
6312              
6313             # Shouldn't happen: sub install_new_alignments makes jmax alignments
6314 0         0 my $jmax_alignments = @alignments - 1;
6315 0         0 if (DEVEL_MODE) {
6316             Fault(
6317             "alignment jmax=$jmax_alignments should equal $maximum_field_index\n"
6318             );
6319             }
6320 0         0 $do_not_align = 1;
6321             }
6322              
6323             # loop to concatenate all fields of this line and needed padding
6324 3637         4626 my $total_pad_count = 0;
6325 3637         5709 for my $j ( 1 .. $maximum_field_index ) {
6326              
6327             # skip zero-length side comments
6328             last
6329             if (
6330 8749 100 66     20786 ( $j == $maximum_field_index )
      100        
6331             && ( !defined( $rfields->[$j] )
6332             || ( $rfield_lengths->[$j] == 0 ) )
6333             );
6334              
6335             # compute spaces of padding before this field
6336 5460         7709 my $col = $alignments[ $j - 1 ]->{'column'};
6337 5460         6240 my $pad = $col - ( $str_len + $leading_space_count );
6338              
6339 5460 50       7607 if ($do_not_align) {
6340 0 0       0 $pad =
6341             ( $j < $maximum_field_index )
6342             ? 0
6343             : $rOpts_minimum_space_to_comment - 1;
6344             }
6345              
6346             # if the -fpsc flag is set, move the side comment to the selected
6347             # column if and only if it is possible, ignoring constraints on
6348             # line length and minimum space to comment
6349 5460 100 100     8600 if ( $rOpts_fixed_position_side_comment
6350             && $j == $maximum_field_index )
6351             {
6352 9         12 my $newpad = $pad + $rOpts_fixed_position_side_comment - $col - 1;
6353 9 50       17 if ( $newpad >= 0 ) { $pad = $newpad; }
  9         11  
6354             }
6355              
6356             # accumulate the padding
6357 5460 100       7968 if ( $pad > 0 ) { $total_pad_count += $pad; }
  1604         1883  
6358              
6359             # only add padding when we have a finite field;
6360             # this avoids extra terminal spaces if we have empty fields
6361 5460 100       7593 if ( $rfield_lengths->[$j] > 0 ) {
6362 5449         8537 $str .= SPACE x $total_pad_count;
6363 5449         5722 $str_len += $total_pad_count;
6364 5449         5482 $total_pad_count = 0;
6365 5449         6773 $str .= $rfields->[$j];
6366 5449         6798 $str_len += $rfield_lengths->[$j];
6367             }
6368             else {
6369 11         21 $total_pad_count = 0;
6370             }
6371             }
6372              
6373 3637         4687 my $side_comment_length = $rfield_lengths->[$maximum_field_index];
6374              
6375             # ship this line off
6376 3637         23062 $self->valign_output_step_B(
6377             {
6378             leading_space_count => $leading_space_count + $extra_leading_spaces,
6379             line => $str,
6380             line_length => $str_len,
6381             side_comment_length => $side_comment_length,
6382             outdent_long_lines => $outdent_long_lines,
6383             rvertical_tightness_flags => $rvertical_tightness_flags,
6384             level => $level,
6385             level_end => $level_end,
6386             Kend => $Kend,
6387             maximum_line_length => $maximum_line_length,
6388             }
6389             );
6390 3637         14739 return;
6391             } ## end sub valign_output_step_A
6392              
6393             sub combine_fields {
6394              
6395 16     16 0 70 my ( $line_0, $line_1, $imax_align ) = @_;
6396              
6397             # Given:
6398             # $line_0, $line_1 = two adjacent lines
6399             # $imax_align = index of last alignment wanted
6400              
6401             # Task:
6402             # We have a group of two lines for which we do not want to align tokens
6403             # between index $imax_align and the side comment. So we will delete fields
6404             # between $imax_align and the side comment. Alignments have already
6405             # been set so we have to adjust them.
6406              
6407 16 50       65 if ( !defined($imax_align) ) { $imax_align = -1 }
  0         0  
6408              
6409             # First delete the unwanted tokens
6410 16         34 my $jmax_old = $line_0->{'jmax'};
6411 16         52 my @idel = ( $imax_align + 1 .. $jmax_old - 2 );
6412 16 50       48 return if ( !@idel );
6413              
6414             # Get old alignments before any changes are made
6415 16         23 my @old_alignments = @{ $line_0->{'ralignments'} };
  16         45  
6416              
6417 16         49 foreach my $line ( $line_0, $line_1 ) {
6418 32         79 delete_selected_tokens( $line, \@idel );
6419             }
6420              
6421             # Now adjust the alignments. Note that the side comment alignment
6422             # is always at jmax-1, and there is an ending alignment at jmax.
6423 16         28 my @new_alignments;
6424 16 50       47 if ( $imax_align >= 0 ) {
6425 0         0 @new_alignments[ 0 .. $imax_align ] =
6426             @old_alignments[ 0 .. $imax_align ];
6427             }
6428              
6429 16         34 my $jmax_new = $line_0->{'jmax'};
6430              
6431 16         42 $new_alignments[ $jmax_new - 1 ] = $old_alignments[ $jmax_old - 1 ];
6432 16         36 $new_alignments[$jmax_new] = $old_alignments[$jmax_old];
6433 16         50 $line_0->{'ralignments'} = \@new_alignments;
6434 16         40 $line_1->{'ralignments'} = \@new_alignments;
6435 16         63 return;
6436             } ## end sub combine_fields
6437              
6438             sub get_output_line_number {
6439              
6440             # Return the output line number to external modules.
6441             # The output line number reported to a caller =
6442             # the number of items still in the buffer +
6443             # the number of items written.
6444 70     70 0 104 my $self = shift;
6445 70         168 return $self->group_line_count() +
6446             $self->[_file_writer_object_]->get_output_line_number();
6447             } ## end sub get_output_line_number
6448              
6449             ###############################
6450             # CODE SECTION 9: Output Step B
6451             ###############################
6452              
6453             { ## closure for sub valign_output_step_B
6454              
6455             # These are values for a cache used by valign_output_step_B.
6456             my $cached_line_text;
6457             my $cached_line_text_length;
6458             my $cached_line_type;
6459             my $cached_line_opening_flag;
6460             my $cached_line_closing_flag;
6461             my $cached_seqno;
6462             my $cached_line_valid;
6463             my $cached_line_leading_space_count;
6464             my $cached_seqno_string;
6465             my $cached_line_Kend;
6466             my $cached_line_maximum_length;
6467              
6468             # These are passed to step_C:
6469             my $seqno_string;
6470             my $last_nonblank_seqno_string;
6471              
6472             sub set_last_nonblank_seqno_string {
6473 398     398 0 620 my ($val) = @_;
6474 398         515 $last_nonblank_seqno_string = $val;
6475 398         491 return;
6476             }
6477              
6478             sub get_cached_line_opening_flag {
6479 227     227 0 342 return $cached_line_opening_flag;
6480             }
6481              
6482             sub get_cached_line_type {
6483 8621     8621 0 12299 return $cached_line_type;
6484             }
6485              
6486             sub set_cached_line_valid {
6487 3     3 0 8 my ($val) = @_;
6488 3         6 $cached_line_valid = $val;
6489 3         6 return;
6490             }
6491              
6492             sub get_cached_seqno {
6493 157     157 0 285 return $cached_seqno;
6494             }
6495              
6496             sub initialize_step_B_cache {
6497              
6498             # valign_output_step_B cache:
6499 658     658 0 1452 $cached_line_text = EMPTY_STRING;
6500 658         1047 $cached_line_text_length = 0;
6501 658         1009 $cached_line_type = 0;
6502 658         1015 $cached_line_opening_flag = 0;
6503 658         954 $cached_line_closing_flag = 0;
6504 658         965 $cached_seqno = 0;
6505 658         881 $cached_line_valid = 0;
6506 658         889 $cached_line_leading_space_count = 0;
6507 658         930 $cached_seqno_string = EMPTY_STRING;
6508 658         896 $cached_line_Kend = undef;
6509 658         937 $cached_line_maximum_length = undef;
6510              
6511             # These vars hold a string of sequence numbers joined together used by
6512             # the cache
6513 658         1149 $seqno_string = EMPTY_STRING;
6514 658         1009 $last_nonblank_seqno_string = EMPTY_STRING;
6515 658         888 return;
6516             } ## end sub initialize_step_B_cache
6517              
6518             sub _flush_step_B_cache {
6519              
6520 2311     2311   3482 my ($self) = @_;
6521              
6522             # Send any text in the step_B cache on to step_C
6523 2311 100       3931 if ($cached_line_type) {
6524 1         1 $seqno_string = $cached_seqno_string;
6525 1         5 $self->valign_output_step_C(
6526             $seqno_string,
6527             $last_nonblank_seqno_string,
6528              
6529             $cached_line_text,
6530             $cached_line_leading_space_count,
6531             $self->[_last_level_written_],
6532             $cached_line_Kend,
6533             );
6534 1         1 $cached_line_type = 0;
6535 1         4 $cached_line_text = EMPTY_STRING;
6536 1         2 $cached_line_text_length = 0;
6537 1         2 $cached_seqno_string = EMPTY_STRING;
6538 1         2 $cached_line_Kend = undef;
6539 1         3 $cached_line_maximum_length = undef;
6540             }
6541 2311         2960 return;
6542             } ## end sub _flush_step_B_cache
6543              
6544             sub handle_cached_line {
6545              
6546 162     162 0 332 my ( $self, $rinput, $leading_string, $leading_string_length ) = @_;
6547              
6548             # handle a cached line ..
6549             # either append the current line to it or write it out
6550              
6551             # The cached line will either be:
6552             # - passed along to step_C, or
6553             # - or combined with the current line
6554              
6555 162         304 my $last_level_written = $self->[_last_level_written_];
6556              
6557 162         250 my $leading_space_count = $rinput->{leading_space_count};
6558 162         268 my $str = $rinput->{line};
6559 162         257 my $str_length = $rinput->{line_length};
6560 162         221 my $rvertical_tightness_flags = $rinput->{rvertical_tightness_flags};
6561 162         257 my $level = $rinput->{level};
6562 162         213 my $level_end = $rinput->{level_end};
6563 162         217 my $maximum_line_length = $rinput->{maximum_line_length};
6564              
6565 162         232 my ( $open_or_close, $seqno_beg );
6566 162 50       340 if ($rvertical_tightness_flags) {
6567              
6568 162         222 $open_or_close = $rvertical_tightness_flags->{_vt_type};
6569 162         264 $seqno_beg = $rvertical_tightness_flags->{_vt_seqno_beg};
6570             }
6571              
6572             # Dump an invalid cached line
6573 162 100 100     448 if ( !$cached_line_valid ) {
    100          
6574 93         260 $self->valign_output_step_C(
6575             $seqno_string,
6576             $last_nonblank_seqno_string,
6577              
6578             $cached_line_text,
6579             $cached_line_leading_space_count,
6580             $last_level_written,
6581             $cached_line_Kend,
6582             );
6583             }
6584              
6585             # Handle cached line ending in OPENING tokens
6586             elsif ( $cached_line_type == 1 || $cached_line_type == 3 ) {
6587              
6588 32         56 my $gap = $leading_space_count - $cached_line_text_length;
6589              
6590             # handle option of just one tight opening per line:
6591 32 100       77 if ( $cached_line_opening_flag == 1 ) {
6592 14 50 33     59 if ( defined($open_or_close) && $open_or_close == 1 ) {
6593 0         0 $gap = -1;
6594             }
6595             }
6596              
6597             # Do not join the lines if this might produce a one-line
6598             # container which exceeds the maximum line length. This is
6599             # necessary prevent blinking, particularly with the combination
6600             # -xci -pvt=2. In that case a one-line block alternately forms
6601             # and breaks, causing -xci to alternately turn on and off (case
6602             # b765).
6603             # Patched to fix cases b656 b862 b971 b972: always do the check
6604             # if the maximum line length changes (due to -vmll).
6605 32 50 33     165 if (
      66        
6606             $gap >= 0
6607             && ( $maximum_line_length != $cached_line_maximum_length
6608             || ( defined($level_end) && $level > $level_end ) )
6609             )
6610             {
6611 0         0 my $test_line_length =
6612             $cached_line_text_length + $gap + $str_length;
6613              
6614             # Add a small tolerance in the length test (fixes case b862)
6615 0 0       0 if ( $test_line_length > $cached_line_maximum_length - 2 ) {
6616 0         0 $gap = -1;
6617             }
6618             }
6619              
6620             # NOTE: using defined() since $seqno_beg can be 0 for -bbvt
6621 32 100 66     130 if ( $gap >= 0 && defined($seqno_beg) ) {
6622 20         27 $maximum_line_length = $cached_line_maximum_length;
6623 20         51 $leading_string = $cached_line_text . SPACE x $gap;
6624 20         28 $leading_string_length = $cached_line_text_length + $gap;
6625 20         26 $leading_space_count = $cached_line_leading_space_count;
6626 20         43 $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
6627 20         32 $level = $last_level_written;
6628             }
6629             else {
6630 12         39 $self->valign_output_step_C(
6631             $seqno_string,
6632             $last_nonblank_seqno_string,
6633              
6634             $cached_line_text,
6635             $cached_line_leading_space_count,
6636             $last_level_written,
6637             $cached_line_Kend,
6638             );
6639             }
6640             }
6641              
6642             # Handle cached line ending in CLOSING tokens
6643             else {
6644 37         114 my $test_line =
6645             $cached_line_text . SPACE x $cached_line_closing_flag . $str;
6646 37         60 my $test_line_length =
6647             $cached_line_text_length +
6648             $cached_line_closing_flag +
6649             $str_length;
6650 37 100 66     366 if (
      66        
      100        
6651              
6652             # The new line must start with container
6653             $seqno_beg
6654              
6655             # The container combination must be okay..
6656             && (
6657              
6658             # okay to combine like types
6659             ( $open_or_close == $cached_line_type )
6660              
6661             # closing block brace may append to non-block
6662             || ( $cached_line_type == 2 && $open_or_close == 4 )
6663              
6664             # something like ');'
6665             || ( !$open_or_close && $cached_line_type == 2 )
6666              
6667             )
6668              
6669             # The combined line must fit
6670             && ( $test_line_length <= $cached_line_maximum_length )
6671             )
6672             {
6673              
6674 33         76 $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
6675              
6676             # Patch to outdent closing tokens ending # in ');' If we
6677             # are joining a line like ');' to a previous stacked set of
6678             # closing tokens, then decide if we may outdent the
6679             # combined stack to the indentation of the ');'. Since we
6680             # should not normally outdent any of the other tokens more
6681             # than the indentation of the lines that contained them, we
6682             # will only do this if all of the corresponding opening
6683             # tokens were on the same line. This can happen with -sot
6684             # and -sct.
6685              
6686             # For example, it is ok here:
6687             # __PACKAGE__->load_components( qw(
6688             # PK::Auto
6689             # Core
6690             # ));
6691             #
6692             # But, for example, we do not outdent in this example
6693             # because that would put the closing sub brace out farther
6694             # than the opening sub brace:
6695             #
6696             # perltidy -sot -sct
6697             # $c->Tk::bind(
6698             # '<Control-f>' => sub {
6699             # my ($c) = @_;
6700             # my $e = $c->XEvent;
6701             # itemsUnderArea $c;
6702             # } );
6703             #
6704 33 100 100     209 if ( $str =~ /^\);/
6705             && $cached_line_text =~ /^[\)\}\]\s]*$/ )
6706             {
6707              
6708             # The way to tell this is if the stacked sequence
6709             # numbers of this output line are the reverse of the
6710             # stacked sequence numbers of the previous non-blank
6711             # line of sequence numbers. So we can join if the
6712             # previous nonblank string of tokens is the mirror
6713             # image. For example if stack )}] is 13:8:6 then we
6714             # are looking for a leading stack like [{( which
6715             # is 6:8:13. We only need to check the two ends,
6716             # because the intermediate tokens must fall in order.
6717             # Note on speed: having to split on colons and
6718             # eliminate multiple colons might appear to be slow,
6719             # but it's not an issue because we almost never come
6720             # through here. In a typical file we don't.
6721              
6722 4         10 $seqno_string =~ s/^:+//;
6723 4         8 $last_nonblank_seqno_string =~ s/^:+//;
6724 4         20 $seqno_string =~ s/:+/:/g;
6725 4         12 $last_nonblank_seqno_string =~ s/:+/:/g;
6726              
6727             # how many spaces can we outdent?
6728 4         6 my $diff =
6729             $cached_line_leading_space_count - $leading_space_count;
6730 4 100 33     24 if ( $diff > 0
      66        
6731             && length($seqno_string)
6732             && length($last_nonblank_seqno_string) ==
6733             length($seqno_string) )
6734             {
6735 3         16 my @seqno_last =
6736             ( split /:/, $last_nonblank_seqno_string );
6737 3         7 my @seqno_now = ( split /:/, $seqno_string );
6738 3 50 33     29 if ( @seqno_now
      33        
      33        
6739             && @seqno_last
6740             && $seqno_now[-1] == $seqno_last[0]
6741             && $seqno_now[0] == $seqno_last[-1] )
6742             {
6743              
6744             # OK to outdent ..
6745             # for absolute safety, be sure we only remove
6746             # whitespace
6747 3         8 my $ws = substr( $test_line, 0, $diff );
6748 3 50 33     20 if ( ( length($ws) == $diff )
6749             && $ws =~ /^\s+$/ )
6750             {
6751              
6752 3         6 $test_line = substr( $test_line, $diff );
6753 3         5 $cached_line_leading_space_count -= $diff;
6754 3         12 $last_level_written =
6755             $self->level_change(
6756             $cached_line_leading_space_count,
6757             $diff, $last_level_written );
6758 3         13 $self->reduce_valign_buffer_indentation($diff);
6759             }
6760              
6761             # shouldn't happen, but not critical:
6762             ##else {
6763             ## ERROR transferring indentation here
6764             ##}
6765             }
6766             }
6767             }
6768              
6769             # Change the args to look like we received the combined line
6770 33         67 $str = $test_line;
6771 33         51 $str_length = $test_line_length;
6772 33         51 $leading_string = EMPTY_STRING;
6773 33         39 $leading_string_length = 0;
6774 33         50 $leading_space_count = $cached_line_leading_space_count;
6775 33         48 $level = $last_level_written;
6776 33         61 $maximum_line_length = $cached_line_maximum_length;
6777             }
6778             else {
6779 4         13 $self->valign_output_step_C(
6780             $seqno_string,
6781             $last_nonblank_seqno_string,
6782              
6783             $cached_line_text,
6784             $cached_line_leading_space_count,
6785             $last_level_written,
6786             $cached_line_Kend,
6787             );
6788             }
6789             }
6790 162         650 return ( $str, $str_length, $leading_string, $leading_string_length,
6791             $leading_space_count, $level, $maximum_line_length );
6792              
6793             } ## end sub handle_cached_line
6794              
6795             sub valign_output_step_B {
6796              
6797 8510     8510 0 12598 my ( $self, $rinput ) = @_;
6798              
6799             #---------------------------------------------------------
6800             # This is Step B in writing vertically aligned lines.
6801             # Vertical tightness is applied according to preset flags.
6802             # In particular this routine handles stacking of opening
6803             # and closing tokens.
6804             #---------------------------------------------------------
6805              
6806             ##Note: key 'level_end' not needed
6807             my (
6808              
6809             $leading_space_count,
6810             $str,
6811             $str_length,
6812             $side_comment_length,
6813             $outdent_long_lines,
6814             $rvertical_tightness_flags,
6815             $level,
6816             $Kend,
6817             $maximum_line_length,
6818              
6819             ) =
6820              
6821 8510         20681 @{$rinput}{
6822 8510         11271 qw(
6823             leading_space_count
6824             line
6825             line_length
6826             side_comment_length
6827             outdent_long_lines
6828             rvertical_tightness_flags
6829             level
6830             Kend
6831             maximum_line_length
6832             )
6833             };
6834              
6835             # Useful -gcs test cases for wide characters are
6836             # perl527/(method.t.2, reg_mesg.t, mime-header.t)
6837              
6838             # handle outdenting of long lines:
6839 8510         9925 my $is_outdented_line;
6840 8510 100       12787 if ($outdent_long_lines) {
6841 372         596 my $excess =
6842             $str_length -
6843             $side_comment_length +
6844             $leading_space_count -
6845             $maximum_line_length;
6846 372 100       785 if ( $excess > 0 ) {
6847 11         18 $leading_space_count = 0;
6848 11         16 my $file_writer_object = $self->[_file_writer_object_];
6849 11         37 my $last_outdented_line_at =
6850             $file_writer_object->get_output_line_number();
6851 11         19 $self->[_last_outdented_line_at_] = $last_outdented_line_at;
6852              
6853 11         19 my $outdented_line_count = $self->[_outdented_line_count_];
6854 11 100       51 if ( !$outdented_line_count ) {
6855 4         8 $self->[_first_outdented_line_at_] =
6856             $last_outdented_line_at;
6857             }
6858 11         16 $outdented_line_count++;
6859 11         18 $self->[_outdented_line_count_] = $outdented_line_count;
6860 11         20 $is_outdented_line = 1;
6861             }
6862             }
6863              
6864             # Make preliminary leading whitespace. It could get changed
6865             # later by entabbing, so we have to keep track of any changes
6866             # to the leading_space_count from here on.
6867 8510 100       16590 my $leading_string =
6868             $leading_space_count > 0
6869             ? ( SPACE x $leading_space_count )
6870             : EMPTY_STRING;
6871 8510         10328 my $leading_string_length = length($leading_string);
6872              
6873             # Unpack any recombination data; it was packed by
6874             # sub 'Formatter::set_vertical_tightness_flags'
6875              
6876             # old hash Meaning
6877             # index key
6878             #
6879             # 0 _vt_type: 1=opening non-block 2=closing non-block
6880             # 3=opening block brace 4=closing block brace
6881             #
6882             # 1a _vt_opening_flag: 1=no multiple steps, 2=multiple steps ok
6883             # 1b _vt_closing_flag: spaces of padding to use if closing
6884             # 2 _vt_seqno: sequence number of container
6885             # 3 _vt_valid flag: do not append if this flag is false. Will be
6886             # true if appropriate -vt flag is set. Otherwise, Will be
6887             # made true only for 2 line container in parens with -lp
6888             # 4 _vt_seqno_beg: sequence number of first token of line
6889             # 5 _vt_seqno_end: sequence number of last token of line
6890             # 6 _vt_min_lines: min number of lines for joining opening cache,
6891             # 0=no constraint
6892             # 7 _vt_max_lines: max number of lines for joining opening cache,
6893             # 0=no constraint
6894              
6895 8510         11004 my ( $open_or_close, $opening_flag, $closing_flag, $seqno, $valid,
6896             $seqno_beg, $seqno_end );
6897 8510 100       12184 if ($rvertical_tightness_flags) {
6898              
6899 796         1233 $open_or_close = $rvertical_tightness_flags->{_vt_type};
6900 796         1097 $opening_flag = $rvertical_tightness_flags->{_vt_opening_flag};
6901 796         1052 $closing_flag = $rvertical_tightness_flags->{_vt_closing_flag};
6902 796         1093 $seqno = $rvertical_tightness_flags->{_vt_seqno};
6903 796         2008 $valid = $rvertical_tightness_flags->{_vt_valid_flag};
6904 796         1089 $seqno_beg = $rvertical_tightness_flags->{_vt_seqno_beg};
6905 796         1151 $seqno_end = $rvertical_tightness_flags->{_vt_seqno_end};
6906             }
6907              
6908 8510 100       13292 $seqno_string = defined($seqno_end) ? $seqno_end : EMPTY_STRING;
6909              
6910             # handle any cached line ..
6911             # either append this line to it or write it out
6912             # Note: the function length() is used in this next test out of caution.
6913             # All testing has shown that the variable $cached_line_text_length is
6914             # correct, but its calculation is complex and a loss of cached text
6915             # would be a disaster.
6916 8510 100       13492 if ( length($cached_line_text) ) {
6917              
6918             (
6919 162         487 $str,
6920             $str_length,
6921             $leading_string,
6922             $leading_string_length,
6923             $leading_space_count,
6924             $level,
6925             $maximum_line_length
6926              
6927             ) = $self->handle_cached_line( $rinput, $leading_string,
6928             $leading_string_length );
6929              
6930 162         295 $cached_line_type = 0;
6931 162         232 $cached_line_text = EMPTY_STRING;
6932 162         210 $cached_line_text_length = 0;
6933 162         224 $cached_line_Kend = undef;
6934 162         208 $cached_line_maximum_length = undef;
6935              
6936             }
6937              
6938             # make the line to be written
6939 8510         11220 my $line = $leading_string . $str;
6940 8510         9869 my $line_length = $leading_string_length + $str_length;
6941              
6942             # Safety check: be sure that a line to be cached as a stacked block
6943             # brace line ends in the appropriate opening or closing block brace.
6944             # This should always be the case if the caller set flags correctly.
6945             # Code '3' is for -sobb, code '4' is for -scbb.
6946 8510 100       12582 if ($open_or_close) {
6947 163 50 66     888 if ( $open_or_close == 3 && $line !~ /\{\s*$/
      66        
      33        
6948             || $open_or_close == 4 && $line !~ /\}\s*$/ )
6949             {
6950 0         0 $open_or_close = 0;
6951             }
6952             }
6953              
6954             # write or cache this line ...
6955             # fix for case b999: do not cache an outdented line
6956             # fix for b1378: do not cache an empty line
6957 8510 100 66     17402 if ( !$open_or_close
      66        
      33        
6958             || $side_comment_length > 0
6959             || $is_outdented_line
6960             || !$line_length )
6961             {
6962 8347         17459 $self->valign_output_step_C(
6963             $seqno_string,
6964             $last_nonblank_seqno_string,
6965              
6966             $line,
6967             $leading_space_count,
6968             $level,
6969             $Kend,
6970             );
6971             }
6972             else {
6973 163         256 $cached_line_text = $line;
6974 163         204 $cached_line_text_length = $line_length;
6975 163         260 $cached_line_type = $open_or_close;
6976 163         200 $cached_line_opening_flag = $opening_flag;
6977 163         191 $cached_line_closing_flag = $closing_flag;
6978 163         237 $cached_seqno = $seqno;
6979 163         249 $cached_line_valid = $valid;
6980 163         199 $cached_line_leading_space_count = $leading_space_count;
6981 163         232 $cached_seqno_string = $seqno_string;
6982 163         200 $cached_line_Kend = $Kend;
6983 163         228 $cached_line_maximum_length = $maximum_line_length;
6984             }
6985              
6986 8510         10578 $self->[_last_level_written_] = $level;
6987 8510         9389 $self->[_last_side_comment_length_] = $side_comment_length;
6988 8510         14284 return;
6989             } ## end sub valign_output_step_B
6990             }
6991              
6992             ################################
6993             # CODE SECTION 10: Output Step C
6994             ################################
6995              
6996             { ## closure for sub valign_output_step_C
6997              
6998             # Vertical alignment buffer used by valign_output_step_C
6999             my $valign_buffer_filling;
7000             my @valign_buffer;
7001              
7002             sub initialize_valign_buffer {
7003 658     658 0 1278 @valign_buffer = ();
7004 658         1030 $valign_buffer_filling = EMPTY_STRING;
7005 658         918 return;
7006             }
7007              
7008             sub dump_valign_buffer {
7009              
7010 2313     2313 0 3530 my ($self) = @_;
7011              
7012             # Send all lines in the current buffer on to step_D
7013 2313 100       4044 if (@valign_buffer) {
7014 2         5 foreach (@valign_buffer) {
7015 7         15 $self->valign_output_step_D( @{$_} );
  7         13  
7016             }
7017 2         6 @valign_buffer = ();
7018             }
7019 2313         3205 $valign_buffer_filling = EMPTY_STRING;
7020 2313         2686 return;
7021             } ## end sub dump_valign_buffer
7022              
7023             sub reduce_valign_buffer_indentation {
7024              
7025 3     3 0 18 my ( $self, $diff ) = @_;
7026              
7027             # Reduce the leading indentation of lines in the current
7028             # buffer by $diff spaces
7029 3 100 66     13 if ( $valign_buffer_filling && $diff ) {
7030 2         4 my $max_valign_buffer = @valign_buffer;
7031 2         8 foreach my $i ( 0 .. $max_valign_buffer - 1 ) {
7032             my ( $line, $leading_space_count, $level, $Kend ) =
7033 7         9 @{ $valign_buffer[$i] };
  7         13  
7034 7         10 my $ws = substr( $line, 0, $diff );
7035 7 50 33     27 if ( ( length($ws) == $diff ) && $ws =~ /^\s+$/ ) {
7036 7         13 $line = substr( $line, $diff );
7037             }
7038 7 50       11 if ( $leading_space_count >= $diff ) {
7039 7         8 $leading_space_count -= $diff;
7040 7         10 $level =
7041             $self->level_change( $leading_space_count, $diff,
7042             $level );
7043             }
7044 7         19 $valign_buffer[$i] =
7045             [ $line, $leading_space_count, $level, $Kend ];
7046             }
7047             }
7048 3         9 return;
7049             } ## end sub reduce_valign_buffer_indentation
7050              
7051             sub valign_output_step_C {
7052              
7053             my (
7054 8457     8457 0 19098 $self,
7055             $seqno_string,
7056             $last_nonblank_seqno_string,
7057              
7058             @args_to_D,
7059             ) = @_;
7060              
7061             #-----------------------------------------------------------------------
7062             # This is Step C in writing vertically aligned lines.
7063             # Lines are either stored in a buffer or passed along to the next step.
7064             # The reason for storing lines is that we may later want to reduce their
7065             # indentation when -sot and -sct are both used.
7066             #-----------------------------------------------------------------------
7067              
7068             # Dump any saved lines if we see a line with an unbalanced opening or
7069             # closing token.
7070 8457 100 100     15447 $self->dump_valign_buffer()
7071             if ( $seqno_string && $valign_buffer_filling );
7072              
7073             # Either store or write this line
7074 8457 100       11338 if ($valign_buffer_filling) {
7075 7         13 push @valign_buffer, [@args_to_D];
7076             }
7077             else {
7078 8450         16152 $self->valign_output_step_D(@args_to_D);
7079             }
7080              
7081             # For lines starting or ending with opening or closing tokens..
7082 8457 100       12882 if ($seqno_string) {
7083 398         550 $last_nonblank_seqno_string = $seqno_string;
7084 398         951 set_last_nonblank_seqno_string($seqno_string);
7085              
7086             # Start storing lines when we see a line with multiple stacked
7087             # opening tokens.
7088             # patch for RT #94354, requested by Colin Williams
7089 398 100 100     1687 if ( index( $seqno_string, ':' ) >= 0
      100        
7090             && $seqno_string =~ /^\d+(\:+\d+)+$/
7091             && $args_to_D[0] !~ /^[\}\)\]\:\?]/ )
7092             {
7093              
7094             # This test is efficient but a little subtle: The first test
7095             # says that we have multiple sequence numbers and hence
7096             # multiple opening or closing tokens in this line. The second
7097             # part of the test rejects stacked closing and ternary tokens.
7098             # So if we get here then we should have stacked unbalanced
7099             # opening tokens.
7100              
7101             # Here is a complex example:
7102              
7103             # Foo($Bar[0], { # (side comment)
7104             # baz => 1,
7105             # });
7106              
7107             # The first line has sequence 6::4. It does not begin with
7108             # a closing token or ternary, so it passes the test and must be
7109             # stacked opening tokens.
7110              
7111             # The last line has sequence 4:6 but is a stack of closing
7112             # tokens, so it gets rejected.
7113              
7114             # Note that the sequence number of an opening token for a qw
7115             # quote is a negative number and will be rejected. For
7116             # example, for the following line: skip_symbols([qw(
7117             # $seqno_string='10:5:-1'. It would be okay to accept it but I
7118             # decided not to do this after testing.
7119              
7120 8         20 $valign_buffer_filling = $seqno_string;
7121              
7122             }
7123             }
7124 8457         13309 return;
7125             } ## end sub valign_output_step_C
7126             }
7127              
7128             ###############################
7129             # CODE SECTION 11: Output Step D
7130             ###############################
7131              
7132             sub add_leading_tabs {
7133              
7134 45     45 0 71 my ( $self, $line, $leading_space_count, $level ) = @_;
7135              
7136             # Convert leading whitespace to use tabs if -et or -t are set
7137              
7138             # Given:
7139             # $line = the line of text to be written, without any tabs
7140             # $leading_whitespace = expected number of leading blank spaces
7141             # $level = indentation level (needed for -t)
7142              
7143             # Return:
7144             # $line = the line with possible leading tabs
7145              
7146 45         55 my $trimmed_line = $line;
7147 45         198 $trimmed_line =~ s/^ [^\S\n]+ //gxm;
7148              
7149             # Check for discrepancy in actual leading white spaces with estimate
7150 45 50       93 if ( length($line) != length($trimmed_line) + $leading_space_count ) {
7151              
7152             # If $leading_space_count is zero, then this routine must not
7153             # be called because we might be in a quote of some kind
7154 0 0       0 if ( $leading_space_count <= 0 ) {
7155 0         0 DEVEL_MODE && Fault(<<EOM);
7156             should not be here with leading space count = $leading_space_count
7157             EOM
7158 0         0 return $line;
7159             }
7160              
7161 0         0 my $leading_space_count_test = length($line) - length($trimmed_line);
7162              
7163             # Skip tabbing if actual whitespace is less than expected
7164 0 0       0 if ( $leading_space_count_test < $leading_space_count ) {
7165 0         0 DEBUG_TABS
7166             && $self->warning(<<EOM);
7167             Error entabbing: expected count=$leading_space_count but only found $leading_space_count_test for line:
7168             '$line'
7169             EOM
7170 0         0 return $line;
7171             }
7172              
7173             # Use actual whitespace if it exceeds prediction. This mainly
7174             # occurs at hanging side comments.
7175 0         0 $leading_space_count = $leading_space_count_test;
7176             }
7177              
7178             #----------------------------------
7179             # Handle --entab-leading-whitespace
7180             #----------------------------------
7181 45 50 0     65 if ($rOpts_entab_leading_whitespace) {
    0          
7182              
7183 45         59 my $space_count =
7184             $leading_space_count % $rOpts_entab_leading_whitespace;
7185 45         76 my $tab_count =
7186             int( $leading_space_count / $rOpts_entab_leading_whitespace );
7187 45         79 my $leading_string = "\t" x $tab_count . SPACE x $space_count;
7188 45         75 $line = $leading_string . $trimmed_line;
7189             }
7190              
7191             #-----------------------------------------------
7192             # Handle -t (one tab per level; not recommended)
7193             #-----------------------------------------------
7194             elsif ( $rOpts_tabs && $level ) {
7195              
7196 0         0 my $leading_string = ( "\t" x $level );
7197 0         0 my $space_count = $leading_space_count - $level * $rOpts_indent_columns;
7198              
7199             # shouldn't happen:
7200 0 0       0 if ( $space_count < 0 ) {
7201              
7202             # But it could be an outdented comment
7203 0 0       0 if ( $line !~ /^\s*#/ ) {
7204 0         0 DEBUG_TABS
7205             && $self->warning(
7206             "Error entabbing in valign_output_step_D: for level=$level count=$leading_space_count\n"
7207             );
7208             }
7209 0         0 $leading_string = ( SPACE x $leading_space_count );
7210             }
7211             else {
7212 0         0 $leading_string .= ( SPACE x $space_count );
7213             }
7214 0         0 $line = $leading_string . $trimmed_line;
7215             }
7216              
7217             # nothing to do; we should have skipped a call to this sub
7218             else {
7219 0         0 if (DEVEL_MODE) {
7220             Fault(
7221             "in tab sub but neither -t nor -et set: check flag 'require_tabs'\n"
7222             );
7223             }
7224             }
7225 45         74 return $line;
7226             } ## end sub add_leading_tabs
7227              
7228             sub valign_output_step_D {
7229              
7230 8457     8457 0 13929 my ( $self, $line, $leading_space_count, $level, $Kend ) = @_;
7231              
7232             #----------------------------------------------------------------
7233             # This is Step D in writing vertically aligned lines.
7234             # It is the end of the vertical alignment pipeline.
7235             # Write one vertically aligned line of code to the output object.
7236             #----------------------------------------------------------------
7237              
7238             # Convert leading whitespace to use tabs if requested.
7239 8457 100 100     14587 if ( $require_tabs && $leading_space_count > 0 ) {
7240 45         83 $line = $self->add_leading_tabs( $line, $leading_space_count, $level );
7241             }
7242              
7243 8457         10562 my $file_writer_object = $self->[_file_writer_object_];
7244 8457         30547 $file_writer_object->write_code_line( $line . "\n", $Kend );
7245              
7246 8457         13441 return;
7247             } ## end sub valign_output_step_D
7248              
7249             ##########################
7250             # CODE SECTION 12: Summary
7251             ##########################
7252              
7253             sub report_anything_unusual {
7254 658     658 0 985 my $self = shift;
7255 658         1087 my $logger_object = $self->[_logger_object_];
7256 658 100       1555 return if ( !$logger_object );
7257              
7258 656         1104 my $outdented_line_count = $self->[_outdented_line_count_];
7259 656 100       1583 if ( $outdented_line_count > 0 ) {
7260 23         90 $logger_object->write_logfile_entry(
7261             "$outdented_line_count long lines were outdented:\n");
7262 23         67 my $first_outdented_line_at = $self->[_first_outdented_line_at_];
7263 23         84 $logger_object->write_logfile_entry(
7264             " First at output line $first_outdented_line_at\n");
7265              
7266 23 100       75 if ( $outdented_line_count > 1 ) {
7267 7         17 my $last_outdented_line_at = $self->[_last_outdented_line_at_];
7268 7         22 $logger_object->write_logfile_entry(
7269             " Last at output line $last_outdented_line_at\n");
7270             }
7271             $logger_object->write_logfile_entry(
7272 23         72 " use -noll to prevent outdenting, -l=n to increase line length\n"
7273             );
7274 23         51 $logger_object->write_logfile_entry("\n");
7275             }
7276 656         1043 return;
7277             } ## end sub report_anything_unusual
7278              
7279             } ## end package Perl::Tidy::VerticalAligner
7280             1;