]> git.donarmstrong.com Git - perltidy.git/blob - lib/Perl/Tidy/VerticalAligner.pm
New upstream version 20190601
[perltidy.git] / lib / Perl / Tidy / VerticalAligner.pm
1 package Perl::Tidy::VerticalAligner;
2 use strict;
3 use warnings;
4 our $VERSION = '20190601';
5
6 use Perl::Tidy::VerticalAligner::Alignment;
7 use Perl::Tidy::VerticalAligner::Line;
8
9 # The Perl::Tidy::VerticalAligner package collects output lines and
10 # attempts to line up certain common tokens, such as => and #, which are
11 # identified by the calling routine.
12 #
13 # There are two main routines: valign_input and flush.  Append acts as a
14 # storage buffer, collecting lines into a group which can be vertically
15 # aligned.  When alignment is no longer possible or desirable, it dumps
16 # the group to flush.
17 #
18 #     valign_input -----> flush
19 #
20 #     collects          writes
21 #     vertical          one
22 #     groups            group
23
24 BEGIN {
25
26     # Caution: these debug flags produce a lot of output
27     # They should all be 0 except when debugging small scripts
28
29     use constant VALIGN_DEBUG_FLAG_APPEND  => 0;
30     use constant VALIGN_DEBUG_FLAG_APPEND0 => 0;
31     use constant VALIGN_DEBUG_FLAG_TERNARY => 0;
32     use constant VALIGN_DEBUG_FLAG_TABS    => 0;
33
34     my $debug_warning = sub {
35         print STDOUT "VALIGN_DEBUGGING with key $_[0]\n";
36         return;
37     };
38
39     VALIGN_DEBUG_FLAG_APPEND  && $debug_warning->('APPEND');
40     VALIGN_DEBUG_FLAG_APPEND0 && $debug_warning->('APPEND0');
41     VALIGN_DEBUG_FLAG_TERNARY && $debug_warning->('TERNARY');
42     VALIGN_DEBUG_FLAG_TABS    && $debug_warning->('TABS');
43
44 }
45
46 use vars qw(
47   $vertical_aligner_self
48   $maximum_alignment_index
49   $ralignment_list
50   $maximum_jmax_seen
51   $minimum_jmax_seen
52   $previous_minimum_jmax_seen
53   $previous_maximum_jmax_seen
54   @group_lines
55   $group_level
56   $group_type
57   $group_maximum_gap
58   $marginal_match
59   $last_level_written
60   $last_leading_space_count
61   $extra_indent_ok
62   $zero_count
63   $last_comment_column
64   $last_side_comment_line_number
65   $last_side_comment_length
66   $last_side_comment_level
67   $outdented_line_count
68   $first_outdented_line_at
69   $last_outdented_line_at
70   $diagnostics_object
71   $logger_object
72   $file_writer_object
73   @side_comment_history
74   $comment_leading_space_count
75   $is_matching_terminal_line
76   $consecutive_block_comments
77
78   $cached_line_text
79   $cached_line_type
80   $cached_line_flag
81   $cached_seqno
82   $cached_line_valid
83   $cached_line_leading_space_count
84   $cached_seqno_string
85
86   $valign_buffer_filling
87   @valign_buffer
88
89   $seqno_string
90   $last_nonblank_seqno_string
91
92   $rOpts
93
94   $rOpts_maximum_line_length
95   $rOpts_variable_maximum_line_length
96   $rOpts_continuation_indentation
97   $rOpts_indent_columns
98   $rOpts_tabs
99   $rOpts_entab_leading_whitespace
100   $rOpts_valign
101
102   $rOpts_fixed_position_side_comment
103   $rOpts_minimum_space_to_comment
104
105 );
106
107 sub initialize {
108
109     (
110         my $class, $rOpts, $file_writer_object, $logger_object,
111         $diagnostics_object
112     ) = @_;
113
114     # variables describing the entire space group:
115     $ralignment_list            = [];
116     $group_level                = 0;
117     $last_level_written         = -1;
118     $extra_indent_ok            = 0;    # can we move all lines to the right?
119     $last_side_comment_length   = 0;
120     $maximum_jmax_seen          = 0;
121     $minimum_jmax_seen          = 0;
122     $previous_minimum_jmax_seen = 0;
123     $previous_maximum_jmax_seen = 0;
124
125     # variables describing each line of the group
126     @group_lines = ();                  # list of all lines in group
127
128     $outdented_line_count          = 0;
129     $first_outdented_line_at       = 0;
130     $last_outdented_line_at        = 0;
131     $last_side_comment_line_number = 0;
132     $last_side_comment_level       = -1;
133     $is_matching_terminal_line     = 0;
134
135     # most recent 3 side comments; [ line number, column ]
136     $side_comment_history[0] = [ -300, 0 ];
137     $side_comment_history[1] = [ -200, 0 ];
138     $side_comment_history[2] = [ -100, 0 ];
139
140     # valign_output_step_B cache:
141     $cached_line_text                = "";
142     $cached_line_type                = 0;
143     $cached_line_flag                = 0;
144     $cached_seqno                    = 0;
145     $cached_line_valid               = 0;
146     $cached_line_leading_space_count = 0;
147     $cached_seqno_string             = "";
148
149     # string of sequence numbers joined together
150     $seqno_string               = "";
151     $last_nonblank_seqno_string = "";
152
153     # frequently used parameters
154     $rOpts_indent_columns           = $rOpts->{'indent-columns'};
155     $rOpts_tabs                     = $rOpts->{'tabs'};
156     $rOpts_entab_leading_whitespace = $rOpts->{'entab-leading-whitespace'};
157     $rOpts_fixed_position_side_comment =
158       $rOpts->{'fixed-position-side-comment'};
159     $rOpts_minimum_space_to_comment = $rOpts->{'minimum-space-to-comment'};
160     $rOpts_maximum_line_length      = $rOpts->{'maximum-line-length'};
161     $rOpts_variable_maximum_line_length =
162       $rOpts->{'variable-maximum-line-length'};
163     $rOpts_valign = $rOpts->{'valign'};
164
165     $consecutive_block_comments = 0;
166     forget_side_comment();
167
168     initialize_for_new_group();
169
170     $vertical_aligner_self = {};
171     bless $vertical_aligner_self, $class;
172     return $vertical_aligner_self;
173 }
174
175 sub initialize_for_new_group {
176     @group_lines                 = ();
177     $maximum_alignment_index     = -1;  # alignments in current group
178     $zero_count                  = 0;   # count consecutive lines without tokens
179     $group_maximum_gap           = 0;   # largest gap introduced
180     $group_type                  = "";
181     $marginal_match              = 0;
182     $comment_leading_space_count = 0;
183     $last_leading_space_count    = 0;
184     return;
185 }
186
187 # interface to Perl::Tidy::Diagnostics routines
188 sub write_diagnostics {
189     my $msg = shift;
190     if ($diagnostics_object) {
191         $diagnostics_object->write_diagnostics($msg);
192     }
193     return;
194 }
195
196 # interface to Perl::Tidy::Logger routines
197 sub warning {
198     my ($msg) = @_;
199     if ($logger_object) {
200         $logger_object->warning($msg);
201     }
202     return;
203 }
204
205 sub write_logfile_entry {
206     my ($msg) = @_;
207     if ($logger_object) {
208         $logger_object->write_logfile_entry($msg);
209     }
210     return;
211 }
212
213 sub report_definite_bug {
214     if ($logger_object) {
215         $logger_object->report_definite_bug();
216     }
217     return;
218 }
219
220 sub get_cached_line_count {
221     my $self = shift;
222     return @group_lines + ( $cached_line_type ? 1 : 0 );
223 }
224
225 sub get_spaces {
226
227     # return the number of leading spaces associated with an indentation
228     # variable $indentation is either a constant number of spaces or an
229     # object with a get_spaces method.
230     my $indentation = shift;
231     return ref($indentation) ? $indentation->get_spaces() : $indentation;
232 }
233
234 sub get_recoverable_spaces {
235
236     # return the number of spaces (+ means shift right, - means shift left)
237     # that we would like to shift a group of lines with the same indentation
238     # to get them to line up with their opening parens
239     my $indentation = shift;
240     return ref($indentation) ? $indentation->get_recoverable_spaces() : 0;
241 }
242
243 sub get_stack_depth {
244
245     my $indentation = shift;
246     return ref($indentation) ? $indentation->get_stack_depth() : 0;
247 }
248
249 sub make_alignment {
250     my ( $col, $token ) = @_;
251
252     # make one new alignment at column $col which aligns token $token
253     ++$maximum_alignment_index;
254
255     #my $alignment = new Perl::Tidy::VerticalAligner::Alignment(
256     my $nlines    = @group_lines;
257     my $alignment = Perl::Tidy::VerticalAligner::Alignment->new(
258         column          => $col,
259         starting_column => $col,
260         matching_token  => $token,
261         starting_line   => $nlines - 1,
262         ending_line     => $nlines - 1,
263         serial_number   => $maximum_alignment_index,
264     );
265     $ralignment_list->[$maximum_alignment_index] = $alignment;
266     return $alignment;
267 }
268
269 sub dump_alignments {
270     print STDOUT
271 "Current Alignments:\ni\ttoken\tstarting_column\tcolumn\tstarting_line\tending_line\n";
272     for my $i ( 0 .. $maximum_alignment_index ) {
273         my $column          = $ralignment_list->[$i]->get_column();
274         my $starting_column = $ralignment_list->[$i]->get_starting_column();
275         my $matching_token  = $ralignment_list->[$i]->get_matching_token();
276         my $starting_line   = $ralignment_list->[$i]->get_starting_line();
277         my $ending_line     = $ralignment_list->[$i]->get_ending_line();
278         print STDOUT
279 "$i\t$matching_token\t$starting_column\t$column\t$starting_line\t$ending_line\n";
280     }
281     return;
282 }
283
284 sub save_alignment_columns {
285     for my $i ( 0 .. $maximum_alignment_index ) {
286         $ralignment_list->[$i]->save_column();
287     }
288     return;
289 }
290
291 sub restore_alignment_columns {
292     for my $i ( 0 .. $maximum_alignment_index ) {
293         $ralignment_list->[$i]->restore_column();
294     }
295     return;
296 }
297
298 sub forget_side_comment {
299     $last_comment_column = 0;
300     return;
301 }
302
303 sub maximum_line_length_for_level {
304
305     # return maximum line length for line starting with a given level
306     my $maximum_line_length = $rOpts_maximum_line_length;
307     if ($rOpts_variable_maximum_line_length) {
308         my $level = shift;
309         if ( $level < 0 ) { $level = 0 }
310         $maximum_line_length += $level * $rOpts_indent_columns;
311     }
312     return $maximum_line_length;
313 }
314
315 sub push_group_line {
316
317     my ($new_line) = @_;
318     push @group_lines, $new_line;
319     return;
320 }
321
322 sub valign_input {
323
324     # Place one line in the current vertical group.
325     #
326     # The input parameters are:
327     #     $level = indentation level of this line
328     #     $rfields = reference to array of fields
329     #     $rpatterns = reference to array of patterns, one per field
330     #     $rtokens   = reference to array of tokens starting fields 1,2,..
331     #
332     # Here is an example of what this package does.  In this example,
333     # we are trying to line up both the '=>' and the '#'.
334     #
335     #         '18' => 'grave',    #   \`
336     #         '19' => 'acute',    #   `'
337     #         '20' => 'caron',    #   \v
338     # <-tabs-><f1-><--field 2 ---><-f3->
339     # |            |              |    |
340     # |            |              |    |
341     # col1        col2         col3 col4
342     #
343     # The calling routine has already broken the entire line into 3 fields as
344     # indicated.  (So the work of identifying promising common tokens has
345     # already been done).
346     #
347     # In this example, there will be 2 tokens being matched: '=>' and '#'.
348     # They are the leading parts of fields 2 and 3, but we do need to know
349     # what they are so that we can dump a group of lines when these tokens
350     # change.
351     #
352     # The fields contain the actual characters of each field.  The patterns
353     # are like the fields, but they contain mainly token types instead
354     # of tokens, so they have fewer characters.  They are used to be
355     # sure we are matching fields of similar type.
356     #
357     # In this example, there will be 4 column indexes being adjusted.  The
358     # first one is always at zero.  The interior columns are at the start of
359     # the matching tokens, and the last one tracks the maximum line length.
360     #
361     # Each time a new line comes in, it joins the current vertical
362     # group if possible.  Otherwise it causes the current group to be dumped
363     # and a new group is started.
364     #
365     # For each new group member, the column locations are increased, as
366     # necessary, to make room for the new fields.  When the group is finally
367     # output, these column numbers are used to compute the amount of spaces of
368     # padding needed for each field.
369     #
370     # Programming note: the fields are assumed not to have any tab characters.
371     # Tabs have been previously removed except for tabs in quoted strings and
372     # side comments.  Tabs in these fields can mess up the column counting.
373     # The log file warns the user if there are any such tabs.
374
375     my ( $rline_hash, $rfields, $rtokens, $rpatterns ) = @_;
376     my $level                     = $rline_hash->{level};
377     my $level_end                 = $rline_hash->{level_end};
378     my $indentation               = $rline_hash->{indentation};
379     my $is_forced_break           = $rline_hash->{is_forced_break};
380     my $outdent_long_lines        = $rline_hash->{outdent_long_lines};
381     my $is_terminal_ternary       = $rline_hash->{is_terminal_ternary};
382     my $is_terminal_statement     = $rline_hash->{is_terminal_statement};
383     my $do_not_pad                = $rline_hash->{do_not_pad};
384     my $rvertical_tightness_flags = $rline_hash->{rvertical_tightness_flags};
385     my $level_jump                = $rline_hash->{level_jump};
386
387     # number of fields is $jmax
388     # number of tokens between fields is $jmax-1
389     my $jmax = @{$rfields} - 1;
390
391     my $leading_space_count = get_spaces($indentation);
392
393     # set outdented flag to be sure we either align within statements or
394     # across statement boundaries, but not both.
395     my $is_outdented = $last_leading_space_count > $leading_space_count;
396     $last_leading_space_count = $leading_space_count;
397
398     # Patch: undo for hanging side comment
399     my $is_hanging_side_comment =
400       ( $jmax == 1 && $rtokens->[0] eq '#' && $rfields->[0] =~ /^\s*$/ );
401     $is_outdented = 0 if $is_hanging_side_comment;
402
403     # Forget side comment alignment after seeing 2 or more block comments
404     my $is_block_comment = ( $jmax == 0 && $rfields->[0] =~ /^#/ );
405     if ($is_block_comment) {
406         $consecutive_block_comments++;
407     }
408     else {
409         if ( $consecutive_block_comments > 1 ) { forget_side_comment() }
410         $consecutive_block_comments = 0;
411     }
412
413     VALIGN_DEBUG_FLAG_APPEND0 && do {
414         my $nlines = @group_lines;
415         print STDOUT
416 "APPEND0: entering lines=$nlines new #fields= $jmax, leading_count=$leading_space_count last_cmt=$last_comment_column force=$is_forced_break, level_jump=$level_jump, level=$level, group_level=$group_level, level_jump=$level_jump\n";
417     };
418
419     # Validate cached line if necessary: If we can produce a container
420     # with just 2 lines total by combining an existing cached opening
421     # token with the closing token to follow, then we will mark both
422     # cached flags as valid.
423     if ($rvertical_tightness_flags) {
424         if (   @group_lines <= 1
425             && $cached_line_type
426             && $cached_seqno
427             && $rvertical_tightness_flags->[2]
428             && $rvertical_tightness_flags->[2] == $cached_seqno )
429         {
430             $rvertical_tightness_flags->[3] ||= 1;
431             $cached_line_valid ||= 1;
432         }
433     }
434
435     # do not join an opening block brace with an unbalanced line
436     # unless requested with a flag value of 2
437     if (   $cached_line_type == 3
438         && !@group_lines
439         && $cached_line_flag < 2
440         && $level_jump != 0 )
441     {
442         $cached_line_valid = 0;
443     }
444
445     # patch until new aligner is finished
446     if ($do_not_pad) { my_flush() }
447
448     # shouldn't happen:
449     if ( $level < 0 ) { $level = 0 }
450
451     # do not align code across indentation level changes
452     # or if vertical alignment is turned off for debugging
453     if ( $level != $group_level || $is_outdented || !$rOpts_valign ) {
454
455         # we are allowed to shift a group of lines to the right if its
456         # level is greater than the previous and next group
457         $extra_indent_ok =
458           ( $level < $group_level && $last_level_written < $group_level );
459
460         my_flush();
461
462         # If we know that this line will get flushed out by itself because
463         # of level changes, we can leave the extra_indent_ok flag set.
464         # That way, if we get an external flush call, we will still be
465         # able to do some -lp alignment if necessary.
466         $extra_indent_ok = ( $is_terminal_statement && $level > $group_level );
467
468         $group_level = $level;
469
470         # wait until after the above flush to get the leading space
471         # count because it may have been changed if the -icp flag is in
472         # effect
473         $leading_space_count = get_spaces($indentation);
474
475     }
476
477     # --------------------------------------------------------------------
478     # Collect outdentable block COMMENTS
479     # --------------------------------------------------------------------
480     my $is_blank_line = "";
481     if ( $group_type eq 'COMMENT' ) {
482         if (
483             (
484                    $is_block_comment
485                 && $outdent_long_lines
486                 && $leading_space_count == $comment_leading_space_count
487             )
488             || $is_blank_line
489           )
490         {
491             push_group_line( $rfields->[0] );
492             return;
493         }
494         else {
495             my_flush();
496         }
497     }
498
499     # --------------------------------------------------------------------
500     # add dummy fields for terminal ternary
501     # --------------------------------------------------------------------
502     my $j_terminal_match;
503
504     if ( $is_terminal_ternary && @group_lines ) {
505         $j_terminal_match =
506           fix_terminal_ternary( $group_lines[-1], $rfields, $rtokens,
507             $rpatterns );
508         $jmax = @{$rfields} - 1;
509     }
510
511     # --------------------------------------------------------------------
512     # add dummy fields for else statement
513     # --------------------------------------------------------------------
514
515     if (   $rfields->[0] =~ /^else\s*$/
516         && @group_lines
517         && $level_jump == 0 )
518     {
519
520         $j_terminal_match =
521           fix_terminal_else( $group_lines[-1], $rfields, $rtokens, $rpatterns );
522         $jmax = @{$rfields} - 1;
523     }
524
525     # --------------------------------------------------------------------
526     # Handle simple line of code with no fields to match.
527     # --------------------------------------------------------------------
528     if ( $jmax <= 0 ) {
529         $zero_count++;
530
531         if ( @group_lines
532             && !get_recoverable_spaces( $group_lines[0]->get_indentation() ) )
533         {
534
535             # flush the current group if it has some aligned columns..
536             if ( $group_lines[0]->get_jmax() > 1 ) { my_flush() }
537
538             # flush current group if we are just collecting side comments..
539             elsif (
540
541                 # ...and we haven't seen a comment lately
542                 ( $zero_count > 3 )
543
544                 # ..or if this new line doesn't fit to the left of the comments
545                 || ( ( $leading_space_count + length( $rfields->[0] ) ) >
546                     $group_lines[0]->get_column(0) )
547               )
548             {
549                 my_flush();
550             }
551         }
552
553         # start new COMMENT group if this comment may be outdented
554         if (   $is_block_comment
555             && $outdent_long_lines
556             && !@group_lines )
557         {
558             $group_type                  = 'COMMENT';
559             $comment_leading_space_count = $leading_space_count;
560             push_group_line( $rfields->[0] );
561             return;
562         }
563
564         # just write this line directly if no current group, no side comment,
565         # and no space recovery is needed.
566         if ( !@group_lines && !get_recoverable_spaces($indentation) ) {
567             valign_output_step_B( $leading_space_count, $rfields->[0], 0,
568                 $outdent_long_lines, $rvertical_tightness_flags, $level );
569             return;
570         }
571     }
572     else {
573         $zero_count = 0;
574     }
575
576     # programming check: (shouldn't happen)
577     # an error here implies an incorrect call was made
578     if ( @{$rfields} && ( @{$rtokens} != ( @{$rfields} - 1 ) ) ) {
579         my $nt = @{$rtokens};
580         my $nf = @{$rfields};
581         warning(
582 "Program bug in Perl::Tidy::VerticalAligner - number of tokens = $nt should be one less than number of fields: $nf)\n"
583         );
584         report_definite_bug();
585     }
586     my $maximum_line_length_for_level = maximum_line_length_for_level($level);
587
588     # --------------------------------------------------------------------
589     # create an object to hold this line
590     # --------------------------------------------------------------------
591     my $new_line = Perl::Tidy::VerticalAligner::Line->new(
592         jmax                      => $jmax,
593         jmax_original_line        => $jmax,
594         rtokens                   => $rtokens,
595         rfields                   => $rfields,
596         rpatterns                 => $rpatterns,
597         indentation               => $indentation,
598         leading_space_count       => $leading_space_count,
599         outdent_long_lines        => $outdent_long_lines,
600         list_type                 => "",
601         is_hanging_side_comment   => $is_hanging_side_comment,
602         maximum_line_length       => $maximum_line_length_for_level,
603         rvertical_tightness_flags => $rvertical_tightness_flags,
604         is_terminal_ternary       => $is_terminal_ternary,
605         j_terminal_match          => $j_terminal_match,
606     );
607
608     # --------------------------------------------------------------------
609     # It simplifies things to create a zero length side comment
610     # if none exists.
611     # --------------------------------------------------------------------
612     make_side_comment( $new_line, $level_end );
613
614     # --------------------------------------------------------------------
615     # Decide if this is a simple list of items.
616     # There are 3 list types: none, comma, comma-arrow.
617     # We use this below to be less restrictive in deciding what to align.
618     # --------------------------------------------------------------------
619     if ($is_forced_break) {
620         decide_if_list($new_line);
621     }
622
623     # --------------------------------------------------------------------
624     # Append this line to the current group (or start new group)
625     # --------------------------------------------------------------------
626     if ( !@group_lines ) {
627         add_to_group($new_line);
628     }
629     else {
630         push_group_line($new_line);
631     }
632
633     # output this group if it ends in a terminal else or ternary line
634     if ( defined($j_terminal_match) ) {
635         my_flush();
636     }
637
638     # Force break after jump to lower level
639     if ( $level_jump < 0 ) {
640         my_flush();
641     }
642
643     # --------------------------------------------------------------------
644     # Some old debugging stuff
645     # --------------------------------------------------------------------
646     VALIGN_DEBUG_FLAG_APPEND && do {
647         print STDOUT "APPEND fields:";
648         dump_array( @{$rfields} );
649         print STDOUT "APPEND tokens:";
650         dump_array( @{$rtokens} );
651         print STDOUT "APPEND patterns:";
652         dump_array( @{$rpatterns} );
653         dump_alignments();
654     };
655
656     return;
657 }
658
659 sub join_hanging_comment {
660
661     my $line = shift;
662     my $jmax = $line->get_jmax();
663     return 0 unless $jmax == 1;    # must be 2 fields
664     my $rtokens = $line->get_rtokens();
665     return 0 unless $rtokens->[0] eq '#';    # the second field is a comment..
666     my $rfields = $line->get_rfields();
667     return 0 unless $rfields->[0] =~ /^\s*$/;    # the first field is empty...
668     my $old_line            = shift;
669     my $maximum_field_index = $old_line->get_jmax();
670     return 0
671       unless $maximum_field_index > $jmax;    # the current line has more fields
672     my $rpatterns = $line->get_rpatterns();
673
674     $line->set_is_hanging_side_comment(1);
675     $jmax = $maximum_field_index;
676     $line->set_jmax($jmax);
677     $rfields->[$jmax]         = $rfields->[1];
678     $rtokens->[ $jmax - 1 ]   = $rtokens->[0];
679     $rpatterns->[ $jmax - 1 ] = $rpatterns->[0];
680     foreach my $j ( 1 .. $jmax - 1 ) {
681         $rfields->[$j]         = " "; # NOTE: caused glitch unless 1 blank, why?
682         $rtokens->[ $j - 1 ]   = "";
683         $rpatterns->[ $j - 1 ] = "";
684     }
685     return 1;
686 }
687
688 sub eliminate_old_fields {
689
690     my $new_line = shift;
691     my $jmax     = $new_line->get_jmax();
692     if ( $jmax > $maximum_jmax_seen ) { $maximum_jmax_seen = $jmax }
693     if ( $jmax < $minimum_jmax_seen ) { $minimum_jmax_seen = $jmax }
694
695     # there must be one previous line
696     return unless ( @group_lines == 1 );
697
698     my $old_line            = shift;
699     my $maximum_field_index = $old_line->get_jmax();
700
701     ###############################################
702     # Moved below to allow new coding for => matches
703     # return unless $maximum_field_index > $jmax;
704     ###############################################
705
706     # Identify specific cases where field elimination is allowed:
707     # case=1: both lines have comma-separated lists, and the first
708     #         line has an equals
709     # case=2: both lines have leading equals
710
711     # case 1 is the default
712     my $case = 1;
713
714     # See if case 2: both lines have leading '='
715     # We'll require similar leading patterns in this case
716     my $old_rtokens   = $old_line->get_rtokens();
717     my $rtokens       = $new_line->get_rtokens();
718     my $rpatterns     = $new_line->get_rpatterns();
719     my $old_rpatterns = $old_line->get_rpatterns();
720     if (   $rtokens->[0] =~ /^=>?\d*$/
721         && $old_rtokens->[0] eq $rtokens->[0]
722         && $old_rpatterns->[0] eq $rpatterns->[0] )
723     {
724         $case = 2;
725     }
726
727     # not too many fewer fields in new line for case 1
728     return unless ( $case != 1 || $maximum_field_index - 2 <= $jmax );
729
730     # case 1 must have side comment
731     my $old_rfields = $old_line->get_rfields();
732     return
733       if ( $case == 1
734         && length( $old_rfields->[$maximum_field_index] ) == 0 );
735
736     my $rfields = $new_line->get_rfields();
737
738     my $hid_equals = 0;
739
740     my @new_alignments        = ();
741     my @new_fields            = ();
742     my @new_matching_patterns = ();
743     my @new_matching_tokens   = ();
744
745     my $j               = 0;
746     my $current_field   = '';
747     my $current_pattern = '';
748
749     # loop over all old tokens
750     my $in_match = 0;
751     foreach my $k ( 0 .. $maximum_field_index - 1 ) {
752         $current_field   .= $old_rfields->[$k];
753         $current_pattern .= $old_rpatterns->[$k];
754         last if ( $j > $jmax - 1 );
755
756         if ( $old_rtokens->[$k] eq $rtokens->[$j] ) {
757             $in_match                  = 1;
758             $new_fields[$j]            = $current_field;
759             $new_matching_patterns[$j] = $current_pattern;
760             $current_field             = '';
761             $current_pattern           = '';
762             $new_matching_tokens[$j]   = $old_rtokens->[$k];
763             $new_alignments[$j]        = $old_line->get_alignment($k);
764             $j++;
765         }
766         else {
767
768             if ( $old_rtokens->[$k] =~ /^\=\d*$/ ) {
769                 last if ( $case == 2 );    # avoid problems with stuff
770                                            # like:   $a=$b=$c=$d;
771                 $hid_equals = 1;
772             }
773             last
774               if ( $in_match && $case == 1 )
775               ;    # disallow gaps in matching field types in case 1
776         }
777     }
778
779     # Modify the current state if we are successful.
780     # We must exactly reach the ends of the new list for success, and the old
781     # pattern must have more fields. Here is an example where the first and
782     # second lines have the same number, and we should not align:
783     #  my @a = map chr, 0 .. 255;
784     #  my @b = grep /\W/,    @a;
785     #  my @c = grep /[^\w]/, @a;
786
787     # Otherwise, we would get all of the commas aligned, which doesn't work as
788     # well:
789     #  my @a = map chr,      0 .. 255;
790     #  my @b = grep /\W/,    @a;
791     #  my @c = grep /[^\w]/, @a;
792
793     if (   ( $j == $jmax )
794         && ( $current_field eq '' )
795         && ( $case != 1 || $hid_equals )
796         && ( $maximum_field_index > $jmax ) )
797     {
798         my $k = $maximum_field_index;
799         $current_field   .= $old_rfields->[$k];
800         $current_pattern .= $old_rpatterns->[$k];
801         $new_fields[$j]            = $current_field;
802         $new_matching_patterns[$j] = $current_pattern;
803
804         $new_alignments[$j] = $old_line->get_alignment($k);
805         $maximum_field_index = $j;
806
807         $old_line->set_alignments(@new_alignments);
808         $old_line->set_jmax($jmax);
809         $old_line->set_rtokens( \@new_matching_tokens );
810         $old_line->set_rfields( \@new_fields );
811         $old_line->set_rpatterns( \@{$rpatterns} );
812     }
813
814     # Dumb Down starting match if necessary:
815     #
816     # Consider the following two lines:
817     #
818     #  {
819     #   $a => 20 > 3 ? 1 : 0,
820     #   $xyz => 5,
821     #  }
822
823     # We would like to get alignment regardless of the order of the two lines.
824     # If the lines come in in this order, then we will simplify the patterns of
825     # the first line in sub eliminate_new_fields.  If the lines come in reverse
826     # order, then we achieve this with eliminate_new_fields.
827
828     # This update is currently restricted to leading '=>' matches. Although we
829     # could do this for both '=' and '=>', overall the results for '=' come out
830     # better without this step because this step can eliminate some other good
831     # matches.  For example, with the '=' we get:
832
833 #  my @disilva = ( "di Silva", "diSilva", "di Si\x{301}lva", "diSi\x{301}lva" );
834 #  my @dsf     = map "$_\x{FFFE}Fred", @disilva;
835 #  my @dsj     = map "$_\x{FFFE}John", @disilva;
836 #  my @dsJ     = map "$_ John", @disilva;
837
838     # without including '=' we get:
839
840 #  my @disilva = ( "di Silva", "diSilva", "di Si\x{301}lva", "diSi\x{301}lva" );
841 #  my @dsf = map "$_\x{FFFE}Fred", @disilva;
842 #  my @dsj = map "$_\x{FFFE}John", @disilva;
843 #  my @dsJ = map "$_ John",        @disilva;
844     elsif (
845         $case == 2
846
847         && @new_matching_tokens == 1
848         ##&& $new_matching_tokens[0] =~ /^=/   # see note above
849         && $new_matching_tokens[0] =~ /^=>/
850         && $maximum_field_index > 2
851       )
852     {
853         my $jmaxm             = $jmax - 1;
854         my $kmaxm             = $maximum_field_index - 1;
855         my $have_side_comment = $old_rtokens->[$kmaxm] eq '#';
856
857         # We need to reduce the group pattern to be just two tokens,
858         # the leading equality or => and the final side comment
859
860         my $mid_field = join "",
861           @{$old_rfields}[ 1 .. $maximum_field_index - 1 ];
862         my $mid_patterns = join "",
863           @{$old_rpatterns}[ 1 .. $maximum_field_index - 1 ];
864         my @new_alignments = (
865             $old_line->get_alignment(0),
866             $old_line->get_alignment( $maximum_field_index - 1 )
867         );
868         my @new_tokens =
869           ( $old_rtokens->[0], $old_rtokens->[ $maximum_field_index - 1 ] );
870         my @new_fields = (
871             $old_rfields->[0], $mid_field, $old_rfields->[$maximum_field_index]
872         );
873         my @new_patterns = (
874             $old_rpatterns->[0], $mid_patterns,
875             $old_rpatterns->[$maximum_field_index]
876         );
877
878         $maximum_field_index = 2;
879         $old_line->set_jmax($maximum_field_index);
880         $old_line->set_rtokens( \@new_tokens );
881         $old_line->set_rfields( \@new_fields );
882         $old_line->set_rpatterns( \@new_patterns );
883
884         initialize_for_new_group();
885         add_to_group($old_line);
886     }
887     return;
888 }
889
890 # create an empty side comment if none exists
891 sub make_side_comment {
892     my ( $new_line, $level_end ) = @_;
893     my $jmax    = $new_line->get_jmax();
894     my $rtokens = $new_line->get_rtokens();
895
896     # if line does not have a side comment...
897     if ( ( $jmax == 0 ) || ( $rtokens->[ $jmax - 1 ] ne '#' ) ) {
898         my $rfields   = $new_line->get_rfields();
899         my $rpatterns = $new_line->get_rpatterns();
900         $rtokens->[$jmax]     = '#';
901         $rfields->[ ++$jmax ] = '';
902         $rpatterns->[$jmax]   = '#';
903         $new_line->set_jmax($jmax);
904         $new_line->set_jmax_original_line($jmax);
905     }
906
907     # line has a side comment..
908     else {
909
910         # don't remember old side comment location for very long
911         my $line_number = $vertical_aligner_self->get_output_line_number();
912         my $rfields     = $new_line->get_rfields();
913         if (
914             $line_number - $last_side_comment_line_number > 12
915
916             # and don't remember comment location across block level changes
917             || (   $level_end < $last_side_comment_level
918                 && $rfields->[0] =~ /^}/ )
919           )
920         {
921             forget_side_comment();
922         }
923         $last_side_comment_line_number = $line_number;
924         $last_side_comment_level       = $level_end;
925     }
926     return;
927 }
928
929 sub decide_if_list {
930
931     my $line = shift;
932
933     # A list will be taken to be a line with a forced break in which all
934     # of the field separators are commas or comma-arrows (except for the
935     # trailing #)
936
937     # List separator tokens are things like ',3'   or '=>2',
938     # where the trailing digit is the nesting depth.  Allow braces
939     # to allow nested list items.
940     my $rtokens    = $line->get_rtokens();
941     my $test_token = $rtokens->[0];
942     if ( $test_token =~ /^(\,|=>)/ ) {
943         my $list_type = $test_token;
944         my $jmax      = $line->get_jmax();
945
946         foreach ( 1 .. $jmax - 2 ) {
947             if ( $rtokens->[$_] !~ /^(\,|=>|\{)/ ) {
948                 $list_type = "";
949                 last;
950             }
951         }
952         $line->set_list_type($list_type);
953     }
954     return;
955 }
956
957 sub eliminate_new_fields {
958
959     my ( $new_line, $old_line ) = @_;
960     return unless (@group_lines);
961     my $jmax = $new_line->get_jmax();
962
963     my $old_rtokens = $old_line->get_rtokens();
964     my $rtokens     = $new_line->get_rtokens();
965     my $is_assignment =
966       ( $rtokens->[0] =~ /^=>?\d*$/ && ( $old_rtokens->[0] eq $rtokens->[0] ) );
967
968     # must be monotonic variation
969     return unless ( $is_assignment || $previous_maximum_jmax_seen <= $jmax );
970
971     # must be more fields in the new line
972     my $maximum_field_index = $old_line->get_jmax();
973     return unless ( $maximum_field_index < $jmax );
974
975     unless ($is_assignment) {
976         return
977           unless ( $old_line->get_jmax_original_line() == $minimum_jmax_seen )
978           ;    # only if monotonic
979
980         # never combine fields of a comma list
981         return
982           unless ( $maximum_field_index > 1 )
983           && ( $new_line->get_list_type() !~ /^,/ );
984     }
985
986     my $rfields       = $new_line->get_rfields();
987     my $rpatterns     = $new_line->get_rpatterns();
988     my $old_rpatterns = $old_line->get_rpatterns();
989
990     # loop over all OLD tokens except comment and check match
991     my $match = 1;
992     foreach my $k ( 0 .. $maximum_field_index - 2 ) {
993         if (   ( $old_rtokens->[$k] ne $rtokens->[$k] )
994             || ( $old_rpatterns->[$k] ne $rpatterns->[$k] ) )
995         {
996             $match = 0;
997             last;
998         }
999     }
1000
1001     # first tokens agree, so combine extra new tokens
1002     if ($match) {
1003         foreach my $k ( $maximum_field_index .. $jmax - 1 ) {
1004
1005             $rfields->[ $maximum_field_index - 1 ] .= $rfields->[$k];
1006             $rfields->[$k] = "";
1007             $rpatterns->[ $maximum_field_index - 1 ] .= $rpatterns->[$k];
1008             $rpatterns->[$k] = "";
1009         }
1010
1011         $rtokens->[ $maximum_field_index - 1 ] = '#';
1012         $rfields->[$maximum_field_index]       = $rfields->[$jmax];
1013         $rpatterns->[$maximum_field_index]     = $rpatterns->[$jmax];
1014         $jmax                                  = $maximum_field_index;
1015     }
1016     $new_line->set_jmax($jmax);
1017     return;
1018 }
1019
1020 sub fix_terminal_ternary {
1021
1022     # Add empty fields as necessary to align a ternary term
1023     # like this:
1024     #
1025     #  my $leapyear =
1026     #      $year % 4   ? 0
1027     #    : $year % 100 ? 1
1028     #    : $year % 400 ? 0
1029     #    :               1;
1030     #
1031     # returns 1 if the terminal item should be indented
1032
1033     my ( $old_line, $rfields, $rtokens, $rpatterns ) = @_;
1034     return unless ($old_line);
1035
1036 ## FUTURE CODING
1037 ##     my ( $old_line, $end_line ) = @_;
1038 ##     return unless ( $old_line && $end_line );
1039 ##
1040 ##     my $rfields   = $end_line->get_rfields();
1041 ##     my $rpatterns = $end_line->get_rpatterns();
1042 ##     my $rtokens   = $end_line->get_rtokens();
1043
1044     my $jmax        = @{$rfields} - 1;
1045     my $rfields_old = $old_line->get_rfields();
1046
1047     my $rpatterns_old       = $old_line->get_rpatterns();
1048     my $rtokens_old         = $old_line->get_rtokens();
1049     my $maximum_field_index = $old_line->get_jmax();
1050
1051     # look for the question mark after the :
1052     my ($jquestion);
1053     my $depth_question;
1054     my $pad = "";
1055     foreach my $j ( 0 .. $maximum_field_index - 1 ) {
1056         my $tok = $rtokens_old->[$j];
1057         if ( $tok =~ /^\?(\d+)$/ ) {
1058             $depth_question = $1;
1059
1060             # depth must be correct
1061             next unless ( $depth_question eq $group_level );
1062
1063             $jquestion = $j;
1064             if ( $rfields_old->[ $j + 1 ] =~ /^(\?\s*)/ ) {
1065                 $pad = " " x length($1);
1066             }
1067             else {
1068                 return;    # shouldn't happen
1069             }
1070             last;
1071         }
1072     }
1073     return unless ( defined($jquestion) );    # shouldn't happen
1074
1075     # Now splice the tokens and patterns of the previous line
1076     # into the else line to insure a match.  Add empty fields
1077     # as necessary.
1078     my $jadd = $jquestion;
1079
1080     # Work on copies of the actual arrays in case we have
1081     # to return due to an error
1082     my @fields   = @{$rfields};
1083     my @patterns = @{$rpatterns};
1084     my @tokens   = @{$rtokens};
1085
1086     VALIGN_DEBUG_FLAG_TERNARY && do {
1087         local $" = '><';
1088         print STDOUT "CURRENT FIELDS=<@{$rfields_old}>\n";
1089         print STDOUT "CURRENT TOKENS=<@{$rtokens_old}>\n";
1090         print STDOUT "CURRENT PATTERNS=<@{$rpatterns_old}>\n";
1091         print STDOUT "UNMODIFIED FIELDS=<@{$rfields}>\n";
1092         print STDOUT "UNMODIFIED TOKENS=<@{$rtokens}>\n";
1093         print STDOUT "UNMODIFIED PATTERNS=<@{$rpatterns}>\n";
1094     };
1095
1096     # handle cases of leading colon on this line
1097     if ( $fields[0] =~ /^(:\s*)(.*)$/ ) {
1098
1099         my ( $colon, $therest ) = ( $1, $2 );
1100
1101         # Handle sub-case of first field with leading colon plus additional code
1102         # This is the usual situation as at the '1' below:
1103         #  ...
1104         #  : $year % 400 ? 0
1105         #  :               1;
1106         if ($therest) {
1107
1108             # Split the first field after the leading colon and insert padding.
1109             # Note that this padding will remain even if the terminal value goes
1110             # out on a separate line.  This does not seem to look to bad, so no
1111             # mechanism has been included to undo it.
1112             my $field1 = shift @fields;
1113             unshift @fields, ( $colon, $pad . $therest );
1114
1115             # change the leading pattern from : to ?
1116             return unless ( $patterns[0] =~ s/^\:/?/ );
1117
1118             # install leading tokens and patterns of existing line
1119             unshift( @tokens,   @{$rtokens_old}[ 0 .. $jquestion ] );
1120             unshift( @patterns, @{$rpatterns_old}[ 0 .. $jquestion ] );
1121
1122             # insert appropriate number of empty fields
1123             splice( @fields, 1, 0, ('') x $jadd ) if $jadd;
1124         }
1125
1126         # handle sub-case of first field just equal to leading colon.
1127         # This can happen for example in the example below where
1128         # the leading '(' would create a new alignment token
1129         # : ( $name =~ /[]}]$/ ) ? ( $mname = $name )
1130         # :                        ( $mname = $name . '->' );
1131         else {
1132
1133             return unless ( $jmax > 0 && $tokens[0] ne '#' ); # shouldn't happen
1134
1135             # prepend a leading ? onto the second pattern
1136             $patterns[1] = "?b" . $patterns[1];
1137
1138             # pad the second field
1139             $fields[1] = $pad . $fields[1];
1140
1141             # install leading tokens and patterns of existing line, replacing
1142             # leading token and inserting appropriate number of empty fields
1143             splice( @tokens,   0, 1, @{$rtokens_old}[ 0 .. $jquestion ] );
1144             splice( @patterns, 1, 0, @{$rpatterns_old}[ 1 .. $jquestion ] );
1145             splice( @fields, 1, 0, ('') x $jadd ) if $jadd;
1146         }
1147     }
1148
1149     # Handle case of no leading colon on this line.  This will
1150     # be the case when -wba=':' is used.  For example,
1151     #  $year % 400 ? 0 :
1152     #                1;
1153     else {
1154
1155         # install leading tokens and patterns of existing line
1156         $patterns[0] = '?' . 'b' . $patterns[0];
1157         unshift( @tokens,   @{$rtokens_old}[ 0 .. $jquestion ] );
1158         unshift( @patterns, @{$rpatterns_old}[ 0 .. $jquestion ] );
1159
1160         # insert appropriate number of empty fields
1161         $jadd = $jquestion + 1;
1162         $fields[0] = $pad . $fields[0];
1163         splice( @fields, 0, 0, ('') x $jadd ) if $jadd;
1164     }
1165
1166     VALIGN_DEBUG_FLAG_TERNARY && do {
1167         local $" = '><';
1168         print STDOUT "MODIFIED TOKENS=<@tokens>\n";
1169         print STDOUT "MODIFIED PATTERNS=<@patterns>\n";
1170         print STDOUT "MODIFIED FIELDS=<@fields>\n";
1171     };
1172
1173     # all ok .. update the arrays
1174     @{$rfields}   = @fields;
1175     @{$rtokens}   = @tokens;
1176     @{$rpatterns} = @patterns;
1177 ## FUTURE CODING
1178 ##     $end_line->set_rfields( \@fields );
1179 ##     $end_line->set_rtokens( \@tokens );
1180 ##     $end_line->set_rpatterns( \@patterns );
1181
1182     # force a flush after this line
1183     return $jquestion;
1184 }
1185
1186 sub fix_terminal_else {
1187
1188     # Add empty fields as necessary to align a balanced terminal
1189     # else block to a previous if/elsif/unless block,
1190     # like this:
1191     #
1192     #  if   ( 1 || $x ) { print "ok 13\n"; }
1193     #  else             { print "not ok 13\n"; }
1194     #
1195     # returns a positive value if the else block should be indented
1196     #
1197     my ( $old_line, $rfields, $rtokens, $rpatterns ) = @_;
1198     return unless ($old_line);
1199     my $jmax = @{$rfields} - 1;
1200     return unless ( $jmax > 0 );
1201
1202     #my $old_line    = $group_lines[-1];
1203
1204     # check for balanced else block following if/elsif/unless
1205     my $rfields_old = $old_line->get_rfields();
1206
1207     # TBD: add handling for 'case'
1208     return unless ( $rfields_old->[0] =~ /^(if|elsif|unless)\s*$/ );
1209
1210     # look for the opening brace after the else, and extract the depth
1211     my $tok_brace = $rtokens->[0];
1212     my $depth_brace;
1213     if ( $tok_brace =~ /^\{(\d+)/ ) { $depth_brace = $1; }
1214
1215     # probably:  "else # side_comment"
1216     else { return }
1217
1218     my $rpatterns_old       = $old_line->get_rpatterns();
1219     my $rtokens_old         = $old_line->get_rtokens();
1220     my $maximum_field_index = $old_line->get_jmax();
1221
1222     # be sure the previous if/elsif is followed by an opening paren
1223     my $jparen    = 0;
1224     my $tok_paren = '(' . $depth_brace;
1225     my $tok_test  = $rtokens_old->[$jparen];
1226     return unless ( $tok_test eq $tok_paren );    # shouldn't happen
1227
1228     # Now find the opening block brace
1229     my ($jbrace);
1230     foreach my $j ( 1 .. $maximum_field_index - 1 ) {
1231         my $tok = $rtokens_old->[$j];
1232         if ( $tok eq $tok_brace ) {
1233             $jbrace = $j;
1234             last;
1235         }
1236     }
1237     return unless ( defined($jbrace) );           # shouldn't happen
1238
1239     # Now splice the tokens and patterns of the previous line
1240     # into the else line to insure a match.  Add empty fields
1241     # as necessary.
1242     my $jadd = $jbrace - $jparen;
1243     splice( @{$rtokens},   0, 0, @{$rtokens_old}[ $jparen .. $jbrace - 1 ] );
1244     splice( @{$rpatterns}, 1, 0, @{$rpatterns_old}[ $jparen + 1 .. $jbrace ] );
1245     splice( @{$rfields}, 1, 0, ('') x $jadd );
1246
1247     # force a flush after this line if it does not follow a case
1248     if   ( $rfields_old->[0] =~ /^case\s*$/ ) { return }
1249     else                                      { return $jbrace }
1250 }
1251
1252 {    # sub check_match
1253     my %is_good_alignment;
1254
1255     BEGIN {
1256
1257         # Vertically aligning on certain "good" tokens is usually okay
1258         # so we can be less restrictive in marginal cases.
1259         my @q = qw( { ? => = );
1260         push @q, (',');
1261         @is_good_alignment{@q} = (1) x scalar(@q);
1262     }
1263
1264     sub check_match {
1265
1266         # See if the current line matches the current vertical alignment group.
1267         # If not, flush the current group.
1268         my ( $new_line, $old_line ) = @_;
1269
1270         # uses global variables:
1271         #  $previous_minimum_jmax_seen
1272         #  $maximum_jmax_seen
1273         #  $marginal_match
1274         my $jmax                = $new_line->get_jmax();
1275         my $maximum_field_index = $old_line->get_jmax();
1276
1277         # flush if this line has too many fields
1278         # variable $GoToLoc indicates goto branch point, for debugging
1279         my $GoToLoc = 1;
1280         if ( $jmax > $maximum_field_index ) { goto NO_MATCH }
1281
1282         # flush if adding this line would make a non-monotonic field count
1283         if (
1284             ( $maximum_field_index > $jmax )    # this has too few fields
1285             && (
1286                 ( $previous_minimum_jmax_seen <
1287                     $jmax )                     # and wouldn't be monotonic
1288                 || ( $old_line->get_jmax_original_line() != $maximum_jmax_seen )
1289             )
1290           )
1291         {
1292             $GoToLoc = 2;
1293             goto NO_MATCH;
1294         }
1295
1296         # otherwise see if this line matches the current group
1297         my $jmax_original_line      = $new_line->get_jmax_original_line();
1298         my $is_hanging_side_comment = $new_line->get_is_hanging_side_comment();
1299         my $rtokens                 = $new_line->get_rtokens();
1300         my $rfields                 = $new_line->get_rfields();
1301         my $rpatterns               = $new_line->get_rpatterns();
1302         my $list_type               = $new_line->get_list_type();
1303
1304         my $group_list_type = $old_line->get_list_type();
1305         my $old_rpatterns   = $old_line->get_rpatterns();
1306         my $old_rtokens     = $old_line->get_rtokens();
1307
1308         my $jlimit = $jmax - 1;
1309         if ( $maximum_field_index > $jmax ) {
1310             $jlimit = $jmax_original_line;
1311             --$jlimit unless ( length( $new_line->get_rfields()->[$jmax] ) );
1312         }
1313
1314         # handle comma-separated lists ..
1315         if ( $group_list_type && ( $list_type eq $group_list_type ) ) {
1316             for my $j ( 0 .. $jlimit ) {
1317                 my $old_tok = $old_rtokens->[$j];
1318                 next unless $old_tok;
1319                 my $new_tok = $rtokens->[$j];
1320                 next unless $new_tok;
1321
1322                 # lists always match ...
1323                 # unless they would align any '=>'s with ','s
1324                 $GoToLoc = 3;
1325                 goto NO_MATCH
1326                   if ( $old_tok =~ /^=>/ && $new_tok =~ /^,/
1327                     || $new_tok =~ /^=>/ && $old_tok =~ /^,/ );
1328             }
1329         }
1330
1331         # do detailed check for everything else except hanging side comments
1332         elsif ( !$is_hanging_side_comment ) {
1333
1334             my $leading_space_count = $new_line->get_leading_space_count();
1335
1336             my $max_pad = 0;
1337             my $min_pad = 0;
1338             my $saw_good_alignment;
1339
1340             for my $j ( 0 .. $jlimit ) {
1341
1342                 my $old_tok = $old_rtokens->[$j];
1343                 my $new_tok = $rtokens->[$j];
1344
1345                 # Note on encoding used for alignment tokens:
1346                 # -------------------------------------------
1347                 # Tokens are "decorated" with information which can help
1348                 # prevent unwanted alignments.  Consider for example the
1349                 # following two lines:
1350                 #   local ( $xn, $xd ) = split( '/', &'rnorm(@_) );
1351                 #   local ( $i, $f ) = &'bdiv( $xn, $xd );
1352                 # There are three alignment tokens in each line, a comma,
1353                 # an =, and a comma.  In the first line these three tokens
1354                 # are encoded as:
1355                 #    ,4+local-18     =3      ,4+split-7
1356                 # and in the second line they are encoded as
1357                 #    ,4+local-18     =3      ,4+&'bdiv-8
1358                 # Tokens always at least have token name and nesting
1359                 # depth.  So in this example the ='s are at depth 3 and
1360                 # the ,'s are at depth 4.  This prevents aligning tokens
1361                 # of different depths.  Commas contain additional
1362                 # information, as follows:
1363                 # ,  {depth} + {container name} - {spaces to opening paren}
1364                 # This allows us to reject matching the rightmost commas
1365                 # in the above two lines, since they are for different
1366                 # function calls.  This encoding is done in
1367                 # 'sub send_lines_to_vertical_aligner'.
1368
1369                 # Pick off actual token.
1370                 # Everything up to the first digit is the actual token.
1371                 my $alignment_token = $new_tok;
1372                 if ( $alignment_token =~ /^([^\d]+)/ ) { $alignment_token = $1 }
1373
1374                 # see if the decorated tokens match
1375                 my $tokens_match = $new_tok eq $old_tok
1376
1377                   # Exception for matching terminal : of ternary statement..
1378                   # consider containers prefixed by ? and : a match
1379                   || ( $new_tok =~ /^,\d*\+\:/ && $old_tok =~ /^,\d*\+\?/ );
1380
1381                 # No match if the alignment tokens differ...
1382                 if ( !$tokens_match ) {
1383
1384                     # ...Unless this is a side comment
1385                     if (
1386                         $j == $jlimit
1387
1388                         # and there is either at least one alignment token
1389                         # or this is a single item following a list.  This
1390                         # latter rule is required for 'December' to join
1391                         # the following list:
1392                         # my (@months) = (
1393                         #     '',       'January',   'February', 'March',
1394                         #     'April',  'May',       'June',     'July',
1395                         #     'August', 'September', 'October',  'November',
1396                         #     'December'
1397                         # );
1398                         # If it doesn't then the -lp formatting will fail.
1399                         && ( $j > 0 || $old_tok =~ /^,/ )
1400                       )
1401                     {
1402                         $marginal_match = 1
1403                           if ( $marginal_match == 0
1404                             && @group_lines == 1 );
1405                         last;
1406                     }
1407
1408                     $GoToLoc = 4;
1409                     goto NO_MATCH;
1410                 }
1411
1412                 # Calculate amount of padding required to fit this in.
1413                 # $pad is the number of spaces by which we must increase
1414                 # the current field to squeeze in this field.
1415                 my $pad =
1416                   length( $rfields->[$j] ) - $old_line->current_field_width($j);
1417                 if ( $j == 0 ) { $pad += $leading_space_count; }
1418
1419                 # remember max pads to limit marginal cases
1420                 if ( $alignment_token ne '#' ) {
1421                     if ( $pad > $max_pad ) { $max_pad = $pad }
1422                     if ( $pad < $min_pad ) { $min_pad = $pad }
1423                 }
1424                 if ( $is_good_alignment{$alignment_token} ) {
1425                     $saw_good_alignment = 1;
1426                 }
1427
1428                 # If patterns don't match, we have to be careful...
1429                 if ( $old_rpatterns->[$j] ne $rpatterns->[$j] ) {
1430
1431                     # flag this as a marginal match since patterns differ
1432                     $marginal_match = 1
1433                       if ( $marginal_match == 0 && @group_lines == 1 );
1434
1435                     # We have to be very careful about aligning commas
1436                     # when the pattern's don't match, because it can be
1437                     # worse to create an alignment where none is needed
1438                     # than to omit one.  Here's an example where the ','s
1439                     # are not in named containers.  The first line below
1440                     # should not match the next two:
1441                     #   ( $a, $b ) = ( $b, $r );
1442                     #   ( $x1, $x2 ) = ( $x2 - $q * $x1, $x1 );
1443                     #   ( $y1, $y2 ) = ( $y2 - $q * $y1, $y1 );
1444                     if ( $alignment_token eq ',' ) {
1445
1446                        # do not align commas unless they are in named containers
1447                         $GoToLoc = 5;
1448                         goto NO_MATCH unless ( $new_tok =~ /[A-Za-z]/ );
1449                     }
1450
1451                     # do not align parens unless patterns match;
1452                     # large ugly spaces can occur in math expressions.
1453                     elsif ( $alignment_token eq '(' ) {
1454
1455                         # But we can allow a match if the parens don't
1456                         # require any padding.
1457                         $GoToLoc = 6;
1458                         if ( $pad != 0 ) { goto NO_MATCH }
1459                     }
1460
1461                     # Handle an '=' alignment with different patterns to
1462                     # the left.
1463                     elsif ( $alignment_token eq '=' ) {
1464
1465                         # It is best to be a little restrictive when
1466                         # aligning '=' tokens.  Here is an example of
1467                         # two lines that we will not align:
1468                         #       my $variable=6;
1469                         #       $bb=4;
1470                         # The problem is that one is a 'my' declaration,
1471                         # and the other isn't, so they're not very similar.
1472                         # We will filter these out by comparing the first
1473                         # letter of the pattern.  This is crude, but works
1474                         # well enough.
1475                         if (
1476                             substr( $old_rpatterns->[$j], 0, 1 ) ne
1477                             substr( $rpatterns->[$j],     0, 1 ) )
1478                         {
1479                             $GoToLoc = 7;
1480                             goto NO_MATCH;
1481                         }
1482
1483                         # If we pass that test, we'll call it a marginal match.
1484                         # Here is an example of a marginal match:
1485                         #       $done{$$op} = 1;
1486                         #       $op         = compile_bblock($op);
1487                         # The left tokens are both identifiers, but
1488                         # one accesses a hash and the other doesn't.
1489                         # We'll let this be a tentative match and undo
1490                         # it later if we don't find more than 2 lines
1491                         # in the group.
1492                         elsif ( @group_lines == 1 ) {
1493                             $marginal_match =
1494                               2;    # =2 prevents being undone below
1495                         }
1496                     }
1497                 }
1498
1499                 # Don't let line with fewer fields increase column widths
1500                 # ( align3.t )
1501                 if ( $maximum_field_index > $jmax ) {
1502
1503                     # Exception: suspend this rule to allow last lines to join
1504                     $GoToLoc = 8;
1505                     if ( $pad > 0 ) { goto NO_MATCH; }
1506                 }
1507             } ## end for my $j ( 0 .. $jlimit)
1508
1509             # Turn off the "marginal match" flag in some cases...
1510             # A "marginal match" occurs when the alignment tokens agree
1511             # but there are differences in the other tokens (patterns).
1512             # If we leave the marginal match flag set, then the rule is that we
1513             # will align only if there are more than two lines in the group.
1514             # We will turn of the flag if we almost have a match
1515             # and either we have seen a good alignment token or we
1516             # just need a small pad (2 spaces) to fit.  These rules are
1517             # the result of experimentation.  Tokens which misaligned by just
1518             # one or two characters are annoying.  On the other hand,
1519             # large gaps to less important alignment tokens are also annoying.
1520             if (   $marginal_match == 1
1521                 && $jmax == $maximum_field_index
1522                 && ( $saw_good_alignment || ( $max_pad < 3 && $min_pad > -3 ) )
1523               )
1524             {
1525                 $marginal_match = 0;
1526             }
1527             ##print "marginal=$marginal_match saw=$saw_good_alignment jmax=$jmax max=$maximum_field_index maxpad=$max_pad minpad=$min_pad\n";
1528         }
1529
1530         # We have a match (even if marginal).
1531         # If the current line has fewer fields than the current group
1532         # but otherwise matches, copy the remaining group fields to
1533         # make it a perfect match.
1534         if ( $maximum_field_index > $jmax ) {
1535
1536             ##########################################################
1537             # FIXME: The previous version had a bug which made side comments
1538             # become regular fields, so for now the program does not allow a
1539             # line with side comment to match.  This should eventually be done.
1540             # The best test file for experimenting is 'lista.t'
1541             ##########################################################
1542
1543             my $comment = $rfields->[$jmax];
1544             $GoToLoc = 9;
1545             goto NO_MATCH if ($comment);
1546
1547             # Corrected loop
1548             for my $jj ( $jlimit .. $maximum_field_index ) {
1549                 $rtokens->[$jj]         = $old_rtokens->[$jj];
1550                 $rfields->[ $jj + 1 ]   = '';
1551                 $rpatterns->[ $jj + 1 ] = $old_rpatterns->[ $jj + 1 ];
1552             }
1553
1554 ##          THESE DO NOT GIVE CORRECT RESULTS
1555 ##          $rfields->[$jmax] = $comment;
1556 ##          $new_line->set_jmax($jmax);
1557
1558         }
1559         return;
1560
1561       NO_MATCH:
1562
1563         # variable $GoToLoc is for debugging
1564         #print "no match from $GoToLoc\n";
1565
1566         # Make one last effort to retain a match of certain statements
1567         my $match = salvage_equality_matches( $new_line, $old_line );
1568         my_flush_code() unless ($match);
1569         return;
1570     }
1571 }
1572
1573 sub salvage_equality_matches {
1574     my ( $new_line, $old_line ) = @_;
1575
1576     # Reduce the complexity of the two lines if it will allow us to retain
1577     # alignment of some common alignments, including '=' and '=>'.  We will
1578     # convert both lines to have just two matching tokens, the equality and the
1579     # side comment.
1580
1581     # return 0 or undef if unsuccessful
1582     # return 1 if successful
1583
1584     # Here is a very simple example of two lines where we could at least
1585     # align the equals:
1586     #  $x = $class->_sub( $x, $delta );
1587     #  $xpownm1 = $class->_pow( $class->_copy($x), $nm1 );    # x(i)^(n-1)
1588
1589     # We will only do this if there is one old line (and one new line)
1590     return unless ( @group_lines == 1 );
1591     return if ($is_matching_terminal_line);
1592
1593     # We are only looking for equality type statements
1594     my $old_rtokens = $old_line->get_rtokens();
1595     my $rtokens     = $new_line->get_rtokens();
1596     my $is_equals =
1597       ( $rtokens->[0] =~ /=/ && ( $old_rtokens->[0] eq $rtokens->[0] ) );
1598     return unless ($is_equals);
1599
1600     # The leading patterns must match
1601     my $old_rpatterns = $old_line->get_rpatterns();
1602     my $rpatterns     = $new_line->get_rpatterns();
1603     return if ( $old_rpatterns->[0] ne $rpatterns->[0] );
1604
1605     # Both should have side comment fields (should always be true)
1606     my $jmax_old    = $old_line->get_jmax();
1607     my $jmax_new    = $new_line->get_jmax();
1608     my $end_tok_old = $old_rtokens->[ $jmax_old - 1 ];
1609     my $end_tok_new = $rtokens->[ $jmax_new - 1 ];
1610     my $have_side_comments =
1611          defined($end_tok_old)
1612       && $end_tok_old eq '#'
1613       && defined($end_tok_new)
1614       && $end_tok_new eq '#';
1615     if ( !$have_side_comments ) { return; }
1616
1617     # Do not match if any remaining tokens in new line include '?', 'if',
1618     # 'unless','||', '&&'. The reason is that (1) this isn't a great match, and
1619     # (2) we will prevent possibly better matchs to follow.  Here is an
1620     # example.  The match of the first two lines is rejected, and this allows
1621     # the second and third lines to match.
1622     #   my $type = shift || "o";
1623     #   my $fname  = ( $type eq 'oo'               ? 'orte_city' : 'orte' );
1624     #   my $suffix = ( $coord_system eq 'standard' ? ''          : '-orig' );
1625     # This logic can cause some unwanted losses of alignments, but it can retain
1626     # long runs of multiple-token alignments, so overall it is worthwhile.
1627     # If we had a peek at the subsequent line we could make a much better
1628     # decision here, but for now this is not available.
1629     for ( my $j = 1 ; $j < $jmax_new - 1 ; $j++ ) {
1630         my $new_tok           = $rtokens->[$j];
1631         my $is_good_alignment = ( $new_tok =~ /^(=|\?|if|unless|\|\||\&\&)/ );
1632         return if ($is_good_alignment);
1633     }
1634
1635     my $squeeze_line = sub {
1636         my ($line_obj) = @_;
1637
1638         # reduce a line down to the three fields surrounding
1639         # the two tokens, an '=' of some sort and a '#' at the end
1640
1641         my $jmax     = $line_obj->get_jmax();
1642         my $jmax_new = 2;
1643         return unless $jmax > $jmax_new;
1644         my $rfields     = $line_obj->get_rfields();
1645         my $rpatterns   = $line_obj->get_rpatterns();
1646         my $rtokens     = $line_obj->get_rtokens();
1647         my $rfields_new = [
1648             $rfields->[0], join( '', @{$rfields}[ 1 .. $jmax - 1 ] ),
1649             $rfields->[$jmax]
1650         ];
1651         my $rpatterns_new = [
1652             $rpatterns->[0], join( '', @{$rpatterns}[ 1 .. $jmax - 1 ] ),
1653             $rpatterns->[$jmax]
1654         ];
1655         my $rtokens_new = [ $rtokens->[0], $rtokens->[ $jmax - 1 ] ];
1656         $line_obj->{_rfields}   = $rfields_new;
1657         $line_obj->{_rpatterns} = $rpatterns_new;
1658         $line_obj->{_rtokens}   = $rtokens_new;
1659         $line_obj->set_jmax($jmax_new);
1660     };
1661
1662     # Okay, we will force a match at the equals-like token.  We will fix both
1663     # lines to have just 2 tokens and 3 fields:
1664     $squeeze_line->($new_line);
1665     $squeeze_line->($old_line);
1666
1667     # start over with a new group
1668     initialize_for_new_group();
1669     add_to_group($old_line);
1670     return 1;
1671 }
1672
1673 sub check_fit {
1674
1675     my ( $new_line, $old_line ) = @_;
1676     return unless (@group_lines);
1677
1678     my $jmax                    = $new_line->get_jmax();
1679     my $leading_space_count     = $new_line->get_leading_space_count();
1680     my $is_hanging_side_comment = $new_line->get_is_hanging_side_comment();
1681     my $rtokens                 = $new_line->get_rtokens();
1682     my $rfields                 = $new_line->get_rfields();
1683     my $rpatterns               = $new_line->get_rpatterns();
1684
1685     my $group_list_type = $group_lines[0]->get_list_type();
1686
1687     my $padding_so_far    = 0;
1688     my $padding_available = $old_line->get_available_space_on_right();
1689
1690     # save current columns in case this doesn't work
1691     save_alignment_columns();
1692
1693     my $maximum_field_index = $old_line->get_jmax();
1694     for my $j ( 0 .. $jmax ) {
1695
1696         my $pad = length( $rfields->[$j] ) - $old_line->current_field_width($j);
1697
1698         if ( $j == 0 ) {
1699             $pad += $leading_space_count;
1700         }
1701
1702         # remember largest gap of the group, excluding gap to side comment
1703         if (   $pad < 0
1704             && $group_maximum_gap < -$pad
1705             && $j > 0
1706             && $j < $jmax - 1 )
1707         {
1708             $group_maximum_gap = -$pad;
1709         }
1710
1711         next if $pad < 0;
1712
1713         ## OLD NOTES:
1714         ## This patch helps sometimes, but it doesn't check to see if
1715         ## the line is too long even without the side comment.  It needs
1716         ## to be reworked.
1717         ##don't let a long token with no trailing side comment push
1718         ##side comments out, or end a group.  (sidecmt1.t)
1719         ##next if ($j==$jmax-1 && length($rfields->[$jmax])==0);
1720
1721         # BEGIN PATCH for keith1.txt.
1722         # If the group began matching multiple tokens but later this got
1723         # reduced to a fewer number of matching tokens, then the fields
1724         # of the later lines will still have to fit into their corresponding
1725         # fields.  So a large later field will "push" the other fields to
1726         # the right, including previous side comments, and if there is no room
1727         # then there is no match.
1728         # For example, look at the last line in the following snippet:
1729
1730  # my $b_prod_db = ( $ENV{ORACLE_SID} =~ m/p$/ && !$testing ) ? true    : false;
1731  # my $env       = ($b_prod_db)                               ? "prd"   : "val";
1732  # my $plant     = ( $OPT{p} )                                ? $OPT{p} : "STL";
1733  # my $task      = $OPT{t};
1734  # my $fnam      = "longggggggggggggggg.$record_created.$env.$plant.idash";
1735
1736         # The long term will push the '?' to the right to fit in, and in this
1737         # case there is not enough room so it will not match the equals unless
1738         # we do something special.
1739
1740         # Usually it looks good to keep an initial alignment of '=' going, and
1741         # we can do this if the long term can fit in the space taken up by the
1742         # remaining fields (the ? : fields here).
1743
1744         # Allowing any matching token for now, but it could be restricted
1745         # to an '='-like token if necessary.
1746
1747         if (
1748                $pad > $padding_available
1749             && $jmax == 2                        # matching one thing (plus #)
1750             && $j == $jmax - 1                   # at last field
1751             && @group_lines > 1                  # more than 1 line in group now
1752             && $jmax < $maximum_field_index      # other lines have more fields
1753             && length( $rfields->[$jmax] ) == 0  # no side comment
1754
1755             # Uncomment to match only equals (but this does not seem necessary)
1756             # && $rtokens->[0] =~ /^=\d/           # matching an equals
1757           )
1758         {
1759             my $extra_padding = 0;
1760             foreach my $jj ( $j + 1 .. $maximum_field_index - 1 ) {
1761                 $extra_padding += $old_line->current_field_width($jj);
1762             }
1763
1764             next if ( $pad <= $padding_available + $extra_padding );
1765         }
1766
1767         # END PATCH for keith1.pl
1768
1769         # This line will need space; lets see if we want to accept it..
1770         if (
1771
1772             # not if this won't fit
1773             ( $pad > $padding_available )
1774
1775             # previously, there were upper bounds placed on padding here
1776             # (maximum_whitespace_columns), but they were not really helpful
1777
1778           )
1779         {
1780
1781             # revert to starting state then flush; things didn't work out
1782             restore_alignment_columns();
1783             my_flush_code();
1784             last;
1785         }
1786
1787         # patch to avoid excessive gaps in previous lines,
1788         # due to a line of fewer fields.
1789         #   return join( ".",
1790         #       $self->{"dfi"},  $self->{"aa"}, $self->rsvd,     $self->{"rd"},
1791         #       $self->{"area"}, $self->{"id"}, $self->{"sel"} );
1792         next if ( $jmax < $maximum_field_index && $j == $jmax - 1 );
1793
1794         # looks ok, squeeze this field in
1795         $old_line->increase_field_width( $j, $pad );
1796         $padding_available -= $pad;
1797
1798         # remember largest gap of the group, excluding gap to side comment
1799         if ( $pad > $group_maximum_gap && $j > 0 && $j < $jmax - 1 ) {
1800             $group_maximum_gap = $pad;
1801         }
1802     }
1803     return;
1804 }
1805
1806 sub add_to_group {
1807
1808     # The current line either starts a new alignment group or is
1809     # accepted into the current alignment group.
1810     my ($new_line) = @_;
1811     push_group_line($new_line);
1812
1813     # initialize field lengths if starting new group
1814     if ( @group_lines == 1 ) {
1815
1816         my $jmax    = $new_line->get_jmax();
1817         my $rfields = $new_line->get_rfields();
1818         my $rtokens = $new_line->get_rtokens();
1819         my $col     = $new_line->get_leading_space_count();
1820
1821         for my $j ( 0 .. $jmax ) {
1822             $col += length( $rfields->[$j] );
1823
1824             # create initial alignments for the new group
1825             my $token = "";
1826             if ( $j < $jmax ) { $token = $rtokens->[$j] }
1827             my $alignment = make_alignment( $col, $token );
1828             $new_line->set_alignment( $j, $alignment );
1829         }
1830
1831         $maximum_jmax_seen = $jmax;
1832         $minimum_jmax_seen = $jmax;
1833     }
1834
1835     # use previous alignments otherwise
1836     else {
1837         my @new_alignments = $group_lines[-2]->get_alignments();
1838         $new_line->set_alignments(@new_alignments);
1839     }
1840
1841     # remember group jmax extremes for next call to valign_input
1842     $previous_minimum_jmax_seen = $minimum_jmax_seen;
1843     $previous_maximum_jmax_seen = $maximum_jmax_seen;
1844     return;
1845 }
1846
1847 sub dump_array {
1848
1849     # debug routine to dump array contents
1850     local $" = ')(';
1851     print STDOUT "(@_)\n";
1852     return;
1853 }
1854
1855 # flush() sends the current Perl::Tidy::VerticalAligner group down the
1856 # pipeline to Perl::Tidy::FileWriter.
1857
1858 # This is the external flush, which also empties the buffer and cache
1859 sub flush {
1860
1861     # the buffer must be emptied first, then any cached text
1862     dump_valign_buffer();
1863
1864     if (@group_lines) {
1865         my_flush();
1866     }
1867     else {
1868         if ($cached_line_type) {
1869             $seqno_string = $cached_seqno_string;
1870             valign_output_step_C( $cached_line_text,
1871                 $cached_line_leading_space_count,
1872                 $last_level_written );
1873             $cached_line_type    = 0;
1874             $cached_line_text    = "";
1875             $cached_seqno_string = "";
1876         }
1877     }
1878     return;
1879 }
1880
1881 sub reduce_valign_buffer_indentation {
1882
1883     my ($diff) = @_;
1884     if ( $valign_buffer_filling && $diff ) {
1885         my $max_valign_buffer = @valign_buffer;
1886         foreach my $i ( 0 .. $max_valign_buffer - 1 ) {
1887             my ( $line, $leading_space_count, $level ) =
1888               @{ $valign_buffer[$i] };
1889             my $ws = substr( $line, 0, $diff );
1890             if ( ( length($ws) == $diff ) && $ws =~ /^\s+$/ ) {
1891                 $line = substr( $line, $diff );
1892             }
1893             if ( $leading_space_count >= $diff ) {
1894                 $leading_space_count -= $diff;
1895                 $level = level_change( $leading_space_count, $diff, $level );
1896             }
1897             $valign_buffer[$i] = [ $line, $leading_space_count, $level ];
1898         }
1899     }
1900     return;
1901 }
1902
1903 sub level_change {
1904
1905     # compute decrease in level when we remove $diff spaces from the
1906     # leading spaces
1907     my ( $leading_space_count, $diff, $level ) = @_;
1908     if ($rOpts_indent_columns) {
1909         my $olev =
1910           int( ( $leading_space_count + $diff ) / $rOpts_indent_columns );
1911         my $nlev = int( $leading_space_count / $rOpts_indent_columns );
1912         $level -= ( $olev - $nlev );
1913         if ( $level < 0 ) { $level = 0 }
1914     }
1915     return $level;
1916 }
1917
1918 sub dump_valign_buffer {
1919     if (@valign_buffer) {
1920         foreach (@valign_buffer) {
1921             valign_output_step_D( @{$_} );
1922         }
1923         @valign_buffer = ();
1924     }
1925     $valign_buffer_filling = "";
1926     return;
1927 }
1928
1929 sub my_flush_comment {
1930
1931     # Output a group of COMMENT lines
1932
1933     return unless (@group_lines);
1934     my $leading_space_count = $comment_leading_space_count;
1935     my $leading_string      = get_leading_string($leading_space_count);
1936
1937     # look for excessively long lines
1938     my $max_excess = 0;
1939     foreach my $str (@group_lines) {
1940         my $excess =
1941           length($str) +
1942           $leading_space_count -
1943           maximum_line_length_for_level($group_level);
1944         if ( $excess > $max_excess ) {
1945             $max_excess = $excess;
1946         }
1947     }
1948
1949     # zero leading space count if any lines are too long
1950     if ( $max_excess > 0 ) {
1951         $leading_space_count -= $max_excess;
1952         if ( $leading_space_count < 0 ) { $leading_space_count = 0 }
1953         $last_outdented_line_at = $file_writer_object->get_output_line_number();
1954         unless ($outdented_line_count) {
1955             $first_outdented_line_at = $last_outdented_line_at;
1956         }
1957         my $nlines = @group_lines;
1958         $outdented_line_count += $nlines;
1959     }
1960
1961     # write the lines
1962     my $outdent_long_lines = 0;
1963     foreach my $line (@group_lines) {
1964         valign_output_step_B( $leading_space_count, $line, 0,
1965             $outdent_long_lines, "", $group_level );
1966     }
1967
1968     initialize_for_new_group();
1969     return;
1970 }
1971
1972 sub my_flush_code {
1973
1974     # Output a group of CODE lines
1975
1976     return unless (@group_lines);
1977
1978     VALIGN_DEBUG_FLAG_APPEND0
1979       && do {
1980         my $group_list_type = $group_lines[0]->get_list_type();
1981         my ( $a, $b, $c ) = caller();
1982         my $nlines              = @group_lines;
1983         my $maximum_field_index = $group_lines[0]->get_jmax();
1984         my $rfields_old         = $group_lines[0]->get_rfields();
1985         my $tok                 = $rfields_old->[0];
1986         print STDOUT
1987 "APPEND0: my_flush_code called from $a $b $c fields=$maximum_field_index list=$group_list_type lines=$nlines extra=$extra_indent_ok first tok=$tok;\n";
1988
1989       };
1990
1991     # some small groups are best left unaligned
1992     my $do_not_align = decide_if_aligned_pair();
1993
1994     # optimize side comment location
1995     $do_not_align = adjust_side_comment($do_not_align);
1996
1997     # recover spaces for -lp option if possible
1998     my $extra_leading_spaces = get_extra_leading_spaces();
1999
2000     # all lines of this group have the same basic leading spacing
2001     my $group_leader_length = $group_lines[0]->get_leading_space_count();
2002
2003     # add extra leading spaces if helpful
2004     # NOTE: Use zero; this did not work well
2005     my $min_ci_gap = 0;
2006
2007     # output the lines
2008     foreach my $line (@group_lines) {
2009         valign_output_step_A( $line, $min_ci_gap, $do_not_align,
2010             $group_leader_length, $extra_leading_spaces );
2011     }
2012
2013     initialize_for_new_group();
2014     return;
2015 }
2016
2017 sub my_flush {
2018
2019     # This is the vertical aligner internal flush, which leaves the cache
2020     # intact
2021     return unless (@group_lines);
2022
2023     VALIGN_DEBUG_FLAG_APPEND0 && do {
2024         my ( $a, $b, $c ) = caller();
2025         my $nlines = @group_lines;
2026         print STDOUT
2027 "APPEND0: my_flush called from $a $b $c lines=$nlines, type=$group_type \n";
2028     };
2029
2030     # handle a group of COMMENT lines
2031     if ( $group_type eq 'COMMENT' ) { my_flush_comment() }
2032
2033     # handle a single line of CODE
2034     elsif ( @group_lines == 1 ) { my_flush_code() }
2035
2036     # handle group(s) of CODE lines
2037     else {
2038
2039         # LP FIX PART 1
2040         # If we are trying to add extra indentation for -lp formatting,
2041         # then we need to try to keep the group intact.  But we have
2042         # to set the $extra_indent_ok flag to zero in case some lines
2043         # are output separately.  We fix things up at the bottom.
2044         # NOTE: this is a workaround but is tentative; we should really look to
2045         # see if if extra indentation is possible.
2046         my $rOpt_lp              = $rOpts->{'line-up-parentheses'};
2047         my $keep_group_intact    = $rOpt_lp && $extra_indent_ok;
2048         my $extra_indent_ok_save = $extra_indent_ok;
2049         $extra_indent_ok = 0;
2050
2051         # we will rebuild alignment line group(s);
2052         my @new_lines = @group_lines;
2053         initialize_for_new_group();
2054
2055         ##my $has_terminal_ternary = $new_lines[-1]->{_is_terminal_ternary};
2056
2057         # remove unmatched tokens in all lines
2058         delete_unmatched_tokens( \@new_lines );
2059
2060         foreach my $new_line (@new_lines) {
2061
2062             # Start a new group if necessary
2063             if ( !@group_lines ) {
2064                 add_to_group($new_line);
2065
2066                 next;
2067             }
2068
2069             my $j_terminal_match = $new_line->get_j_terminal_match();
2070             my $base_line        = $group_lines[0];
2071
2072             # Initialize a global flag saying if the last line of the group
2073             # should match end of group and also terminate the group.  There
2074             # should be no returns between here and where the flag is handled
2075             # at the bottom.
2076             my $col_matching_terminal = 0;
2077             if ( defined($j_terminal_match) ) {
2078
2079                 # remember the column of the terminal ? or { to match with
2080                 $col_matching_terminal =
2081                   $base_line->get_column($j_terminal_match);
2082
2083                 # set global flag for sub decide_if_aligned_pair
2084                 $is_matching_terminal_line = 1;
2085             }
2086
2087             # -------------------------------------------------------------
2088             # Allow hanging side comment to join current group, if any. This
2089             # will help keep side comments aligned, because otherwise we
2090             # will have to start a new group, making alignment less likely.
2091             # -------------------------------------------------------------
2092
2093             if ( $new_line->get_is_hanging_side_comment() ) {
2094                 join_hanging_comment( $new_line, $base_line );
2095             }
2096
2097             # If this line has no matching tokens, then flush out the lines
2098             # BEFORE this line unless both it and the previous line have side
2099             # comments.  This prevents this line from pushing side coments out
2100             # to the right.
2101             ##elsif ( $new_line->get_jmax() == 1 ) {
2102             elsif ( $new_line->get_jmax() == 1 && !$keep_group_intact ) {
2103
2104                 # There are no matching tokens, so now check side comments:
2105                 my $prev_comment = $group_lines[-1]->get_rfields()->[-1];
2106                 my $side_comment = $new_line->get_rfields()->[-1];
2107                 my_flush_code() unless ( $side_comment && $prev_comment );
2108
2109             }
2110
2111             # -------------------------------------------------------------
2112             # If there is just one previous line, and it has more fields
2113             # than the new line, try to join fields together to get a match
2114             # with the new line.  At the present time, only a single
2115             # leading '=' is allowed to be compressed out.  This is useful
2116             # in rare cases where a table is forced to use old breakpoints
2117             # because of side comments,
2118             # and the table starts out something like this:
2119             #   my %MonthChars = ('0', 'Jan',   # side comment
2120             #                     '1', 'Feb',
2121             #                     '2', 'Mar',
2122             # Eliminating the '=' field will allow the remaining fields to
2123             # line up.  This situation does not occur if there are no side
2124             # comments because scan_list would put a break after the
2125             # opening '('.
2126             # -------------------------------------------------------------
2127
2128             eliminate_old_fields( $new_line, $base_line );
2129
2130             # -------------------------------------------------------------
2131             # If the new line has more fields than the current group,
2132             # see if we can match the first fields and combine the remaining
2133             # fields of the new line.
2134             # -------------------------------------------------------------
2135
2136             eliminate_new_fields( $new_line, $base_line );
2137
2138             # -------------------------------------------------------------
2139             # Flush previous group unless all common tokens and patterns
2140             # match..
2141
2142             check_match( $new_line, $base_line );
2143
2144             # -------------------------------------------------------------
2145             # See if there is space for this line in the current group (if
2146             # any)
2147             # -------------------------------------------------------------
2148             if (@group_lines) {
2149                 check_fit( $new_line, $base_line );
2150             }
2151
2152             add_to_group($new_line);
2153
2154             if ( defined($j_terminal_match) ) {
2155
2156                 # if there is only one line in the group (maybe due to failure
2157                 # to match perfectly with previous lines), then align the ? or
2158                 # { of this terminal line with the previous one unless that
2159                 # would make the line too long
2160                 if ( @group_lines == 1 ) {
2161                     $base_line = $group_lines[0];
2162                     my $col_now = $base_line->get_column($j_terminal_match);
2163                     my $pad     = $col_matching_terminal - $col_now;
2164                     my $padding_available =
2165                       $base_line->get_available_space_on_right();
2166                     if ( $pad > 0 && $pad <= $padding_available ) {
2167                         $base_line->increase_field_width( $j_terminal_match,
2168                             $pad );
2169                     }
2170                 }
2171                 my_flush_code();
2172                 $is_matching_terminal_line = 0;
2173             }
2174
2175             # Optional optimization; end the group if we know we cannot match
2176             # next line.
2177             elsif ( $new_line->{_end_group} ) {
2178                 my_flush_code();
2179             }
2180         }
2181
2182         # LP FIX PART 2
2183         # if we managed to keep the group intact for -lp formatting,
2184         # restore the flag which allows extra indentation
2185         if ( $keep_group_intact && @group_lines == @new_lines ) {
2186             $extra_indent_ok = $extra_indent_ok_save;
2187         }
2188         my_flush_code();
2189     }
2190     return;
2191 }
2192
2193 sub delete_selected_tokens {
2194
2195     my ( $line_obj, $ridel ) = @_;
2196
2197     # remove an unused alignment token(s) to improve alignment chances
2198     return unless ( defined($line_obj) && defined($ridel) && @{$ridel} );
2199
2200     my $jmax_old      = $line_obj->get_jmax();
2201     my $rfields_old   = $line_obj->get_rfields();
2202     my $rpatterns_old = $line_obj->get_rpatterns();
2203     my $rtokens_old   = $line_obj->get_rtokens();
2204
2205     local $" = '> <';
2206     0 && print <<EOM;
2207 delete indexes: <@{$ridel}>
2208 old jmax: $jmax_old
2209 old tokens: <@{$rtokens_old}>
2210 old patterns: <@{$rpatterns_old}>
2211 old fields: <@{$rfields_old}>
2212 EOM
2213
2214     my $rfields_new   = [];
2215     my $rpatterns_new = [];
2216     my $rtokens_new   = [];
2217
2218     my $kmax      = @{$ridel} - 1;
2219     my $k         = 0;
2220     my $jdel_next = $ridel->[$k];
2221
2222     # FIXME:
2223     if ( $jdel_next < 0 ) { print STDERR "bad jdel_next=$jdel_next\n"; return }
2224     my $pattern = $rpatterns_old->[0];
2225     my $field   = $rfields_old->[0];
2226     push @{$rfields_new},   $field;
2227     push @{$rpatterns_new}, $pattern;
2228     for ( my $j = 0 ; $j < $jmax_old ; $j++ ) {
2229         my $token   = $rtokens_old->[$j];
2230         my $field   = $rfields_old->[ $j + 1 ];
2231         my $pattern = $rpatterns_old->[ $j + 1 ];
2232         if ( $k > $kmax || $j < $jdel_next ) {
2233             push @{$rtokens_new},   $token;
2234             push @{$rfields_new},   $field;
2235             push @{$rpatterns_new}, $pattern;
2236         }
2237         elsif ( $j == $jdel_next ) {
2238             $rfields_new->[-1]   .= $field;
2239             $rpatterns_new->[-1] .= $pattern;
2240             if ( ++$k <= $kmax ) {
2241                 my $jdel_last = $jdel_next;
2242                 $jdel_next = $ridel->[$k];
2243                 if ( $jdel_next < $jdel_last ) {
2244
2245                     # FIXME:
2246                     print STDERR "bad jdel_next=$jdel_next\n";
2247                     return;
2248                 }
2249             }
2250         }
2251     }
2252
2253     # ----- x ------ x ------ x ------
2254     #t      0        1        2        <- token indexing
2255     #f   0      1        2        3    <- field and pattern
2256
2257     my $jmax_new = @{$rfields_new} - 1;
2258     $line_obj->set_rtokens($rtokens_new);
2259     $line_obj->set_rpatterns($rpatterns_new);
2260     $line_obj->set_rfields($rfields_new);
2261     $line_obj->set_jmax($jmax_new);
2262
2263     0 && print <<EOM;
2264
2265 new jmax: $jmax_new
2266 new tokens: <@{$rtokens_new}>
2267 new patterns: <@{$rpatterns_new}>
2268 new fields: <@{$rfields_new}>
2269 EOM
2270     return;
2271 }
2272
2273 {    # sub is_deletable_token
2274
2275     my %is_deletable_equals;
2276
2277     BEGIN {
2278         my @q;
2279
2280         # These tokens with = may be deleted for vertical aligmnemt
2281         @q = qw(
2282           <= >= == =~ != <=>
2283         );
2284         @is_deletable_equals{@q} = (1) x scalar(@q);
2285
2286     }
2287
2288     sub is_deletable_token {
2289
2290         # Determine if an token with no match possibility can be removed to
2291         # improve chances of making an alignment.
2292         my ( $token, $i, $imax, $jline, $i_eq ) = @_;
2293
2294         # Strip off the level and other stuff appended to the token.
2295         # Tokens have a trailing decimal level and optional tag (for commas):
2296         # For example, the first comma in the following line
2297         #     sub banner  { crlf; report( shift, '/', shift ); crlf }
2298         # is decorated as follows:
2299         #    ,2+report-6  => (tok,lev,tag) =qw( ,   2   +report-6)
2300         my ( $tok, $lev, $tag ) = ( $token, 0, "" );
2301         if ( $tok =~ /^(\D+)(\d+)(.*)$/ ) { $tok = $1; $lev = $2; $tag = $3 }
2302         ##print "$token >> $tok   $lev   $tag\n";
2303
2304         # only remove lower level commas
2305         ##if ( $tok eq ',' ) { return unless $lev > $group_level; }
2306         if ( $tok eq ',' ) {
2307
2308             #print "tok=$tok, lev=$lev, gl=$group_level, i=$i, ieq=$i_eq\n";
2309             return if ( defined($i_eq) && $i < $i_eq );
2310             return if ( $lev >= $group_level );
2311         }
2312
2313         # most operators with an equals sign should be retained if at
2314         # same level as this statement
2315         elsif ( $tok =~ /=/ ) {
2316             return unless ( $lev > $group_level || $is_deletable_equals{$tok} );
2317         }
2318
2319         # otherwise, ok to delete the token
2320         return 1;
2321     }
2322 }
2323
2324 sub delete_unmatched_tokens {
2325     my ($rlines) = @_;
2326
2327     # We will look at each line of a collection and compare its alignment
2328     # tokens with its neighbors.  If it has alignment tokens which do not match
2329     # either neighbor, then we will usually remove them.  This will
2330     # simplify later work and improve chances of aligning.
2331
2332     return unless @{$rlines};
2333     my $has_terminal_match = $rlines->[-1]->get_j_terminal_match();
2334
2335     # ignore hanging side comments
2336     my @filtered   = grep { !$_->{_is_hanging_side_comment} } @{$rlines};
2337     my $rnew_lines = \@filtered;
2338     my @i_equals;
2339
2340     # Step 1: create a hash of tokens for each line
2341     my $rline_hashes = [];
2342     foreach my $line ( @{$rnew_lines} ) {
2343         my $rhash   = {};
2344         my $rtokens = $line->get_rtokens();
2345         my $i       = 0;
2346         my $i_eq;
2347         foreach my $tok ( @{$rtokens} ) {
2348             $rhash->{$tok} = [ $i, undef, undef ];
2349
2350             # remember the first equals at line level
2351             if ( !defined($i_eq) && $tok =~ /^=(\d+)/ ) {
2352                 my $lev = $1;
2353                 if ( $lev eq $group_level ) { $i_eq = $i }
2354             }
2355             $i++;
2356         }
2357         push @{$rline_hashes}, $rhash;
2358         push @i_equals, $i_eq;
2359     }
2360
2361     # Step 2: compare each line pair and record matches
2362     for ( my $jl = 0 ; $jl < @{$rline_hashes} - 1 ; $jl++ ) {
2363         my $jr      = $jl + 1;
2364         my $rhash_l = $rline_hashes->[$jl];
2365         my $rhash_r = $rline_hashes->[$jr];
2366         my $count   = 0;
2367         my $ntoks   = 0;
2368         foreach my $tok ( keys %{$rhash_l} ) {
2369             $ntoks++;
2370             if ( defined( $rhash_r->{$tok} ) ) {
2371                 if ( $tok ne '#' ) { $count++; }
2372                 my $il = $rhash_l->{$tok}->[0];
2373                 my $ir = $rhash_r->{$tok}->[0];
2374                 $rhash_l->{$tok}->[2] = $ir;
2375                 $rhash_r->{$tok}->[1] = $il;
2376             }
2377         }
2378     }
2379
2380     # Step 3: remove unmatched tokens
2381     my $jj   = 0;
2382     my $jmax = @{$rnew_lines} - 1;
2383     foreach my $line ( @{$rnew_lines} ) {
2384         my $rtokens = $line->get_rtokens();
2385         my $rhash   = $rline_hashes->[$jj];
2386         my $i       = 0;
2387         my $nl      = 0;
2388         my $nr      = 0;
2389         my $i_eq    = $i_equals[$jj];
2390         my @idel;
2391         my $imax = @{$rtokens} - 2;
2392
2393         for ( my $i = 0 ; $i <= $imax ; $i++ ) {
2394             my $tok = $rtokens->[$i];
2395             next if ( $tok eq '#' );    # shouldn't happen
2396             my ( $il, $ir ) = @{ $rhash->{$tok} }[ 1, 2 ];
2397             $nl++ if defined($il);
2398             $nr++ if defined($ir);
2399             if (
2400                    !defined($il)
2401                 && !defined($ir)
2402                 && is_deletable_token( $tok, $i, $imax, $jj, $i_eq )
2403
2404                 # Patch: do not touch the first line of a terminal match,
2405                 # such as below, because j_terminal has already been set.
2406                 #    if ($tag) { $tago = "<$tag>"; $tagc = "</$tag>"; }
2407                 #    else      { $tago = $tagc = ''; }
2408                 # But see snippets 'else1.t' and 'else2.t'
2409                 && !( $jj == 0 && $has_terminal_match && $jmax == 1 )
2410
2411               )
2412             {
2413                 push @idel, $i;
2414             }
2415         }
2416
2417         if (@idel) { delete_selected_tokens( $line, \@idel ) }
2418
2419         # set a break if this is an interior line with possible left matches
2420         # but no matches to the right.  We do not do this for the last line
2421         # because it could be followed by hanging side comments filtered out
2422         # above.
2423         if ( $nr == 0 && $nl > 0 && $jj < @{$rnew_lines} - 1 ) {
2424             $rnew_lines->[$jj]->{_end_group} = 1;
2425         }
2426         $jj++;
2427     }
2428
2429     #use Data::Dumper;
2430     #print Data::Dumper->Dump( [$rline_hashes] );
2431     return;
2432 }
2433
2434 sub decide_if_aligned_pair {
2435
2436     # Do not try to align two lines which are not really similar
2437     return unless ( @group_lines == 2 );
2438     return if ($is_matching_terminal_line);
2439
2440     my $group_list_type = $group_lines[0]->get_list_type();
2441
2442     my $rtokens        = $group_lines[0]->get_rtokens();
2443     my $leading_equals = ( $rtokens->[0] =~ /=/ );
2444
2445    # A marginal match is a match which has different patterns. Normally, we
2446    # should not allow exactly two lines to match if marginal. But we will modify
2447    # this rule for two lines with a leading equals-like operator such that we
2448    # match if the patterns to the left of the equals are the same. So for
2449    # example the following two lines are a marginal match but have the same
2450    # left side patterns, so we will align the equals.
2451    #     my $orig = my $format = "^<<<<< ~~\n";
2452    #     my $abc  = "abc";
2453    # But these have a different left pattern so they will not be aligned
2454    #     $xmldoc .= $`;
2455    #     $self->{'leftovers'} .= "<bx-seq:seq" . $';
2456     my $is_marginal = $marginal_match;
2457     if ( $leading_equals && $is_marginal ) {
2458         my $rpatterns0 = $group_lines[0]->get_rpatterns();
2459         my $rpatterns1 = $group_lines[1]->get_rpatterns();
2460         my $pat0       = $rpatterns0->[0];
2461         my $pat1       = $rpatterns1->[0];
2462         $is_marginal = $pat0 ne $pat1;
2463     }
2464
2465     my $do_not_align = (
2466
2467         # always align lists
2468         !$group_list_type
2469
2470           && (
2471
2472             # don't align if it was just a marginal match
2473             $is_marginal    ##$marginal_match
2474
2475             # don't align two lines with big gap
2476             # NOTE: I am not sure if this test is actually functional any longer
2477             || $group_maximum_gap > 12
2478
2479             # or lines with differing number of alignment tokens
2480             || ( $previous_maximum_jmax_seen != $previous_minimum_jmax_seen
2481                 && !$leading_equals )
2482           )
2483     );
2484
2485     # But try to convert them into a simple comment group if the first line
2486     # a has side comment
2487     my $rfields             = $group_lines[0]->get_rfields();
2488     my $maximum_field_index = $group_lines[0]->get_jmax();
2489     if ( $do_not_align
2490         && ( length( $rfields->[$maximum_field_index] ) > 0 ) )
2491     {
2492         combine_fields();
2493         $do_not_align = 0;
2494     }
2495     return $do_not_align;
2496 }
2497
2498 sub adjust_side_comment {
2499
2500     my $do_not_align = shift;
2501
2502     # let's see if we can move the side comment field out a little
2503     # to improve readability (the last field is always a side comment field)
2504     my $have_side_comment       = 0;
2505     my $first_side_comment_line = -1;
2506     my $maximum_field_index     = $group_lines[0]->get_jmax();
2507     my $i                       = 0;
2508     foreach my $line (@group_lines) {
2509         if ( length( $line->get_rfields()->[$maximum_field_index] ) ) {
2510             $have_side_comment       = 1;
2511             $first_side_comment_line = $i;
2512             last;
2513         }
2514         $i++;
2515     }
2516
2517     my $kmax = $maximum_field_index + 1;
2518
2519     if ($have_side_comment) {
2520
2521         my $line = $group_lines[0];
2522
2523         # the maximum space without exceeding the line length:
2524         my $avail = $line->get_available_space_on_right();
2525
2526         # try to use the previous comment column
2527         my $side_comment_column = $line->get_column( $kmax - 2 );
2528         my $move                = $last_comment_column - $side_comment_column;
2529
2530 ##        my $sc_line0 = $side_comment_history[0]->[0];
2531 ##        my $sc_col0  = $side_comment_history[0]->[1];
2532 ##        my $sc_line1 = $side_comment_history[1]->[0];
2533 ##        my $sc_col1  = $side_comment_history[1]->[1];
2534 ##        my $sc_line2 = $side_comment_history[2]->[0];
2535 ##        my $sc_col2  = $side_comment_history[2]->[1];
2536 ##
2537 ##        # FUTURE UPDATES:
2538 ##        # Be sure to ignore 'do not align' and  '} # end comments'
2539 ##        # Find first $move > 0 and $move <= $avail as follows:
2540 ##        # 1. try sc_col1 if sc_col1 == sc_col0 && (line-sc_line0) < 12
2541 ##        # 2. try sc_col2 if (line-sc_line2) < 12
2542 ##        # 3. try min possible space, plus up to 8,
2543 ##        # 4. try min possible space
2544
2545         if ( $kmax > 0 && !$do_not_align ) {
2546
2547             # but if this doesn't work, give up and use the minimum space
2548             if ( $move > $avail ) {
2549                 $move = $rOpts_minimum_space_to_comment - 1;
2550             }
2551
2552             # but we want some minimum space to the comment
2553             my $min_move = $rOpts_minimum_space_to_comment - 1;
2554             if (   $move >= 0
2555                 && $last_side_comment_length > 0
2556                 && ( $first_side_comment_line == 0 )
2557                 && $group_level == $last_level_written )
2558             {
2559                 $min_move = 0;
2560             }
2561
2562             if ( $move < $min_move ) {
2563                 $move = $min_move;
2564             }
2565
2566             # previously, an upper bound was placed on $move here,
2567             # (maximum_space_to_comment), but it was not helpful
2568
2569             # don't exceed the available space
2570             if ( $move > $avail ) { $move = $avail }
2571
2572             # we can only increase space, never decrease
2573             if ( $move > 0 ) {
2574                 $line->increase_field_width( $maximum_field_index - 1, $move );
2575             }
2576
2577             # remember this column for the next group
2578             $last_comment_column = $line->get_column( $kmax - 2 );
2579         }
2580         else {
2581
2582             # try to at least line up the existing side comment location
2583             if ( $kmax > 0 && $move > 0 && $move < $avail ) {
2584                 $line->increase_field_width( $maximum_field_index - 1, $move );
2585                 $do_not_align = 0;
2586             }
2587
2588             # reset side comment column if we can't align
2589             else {
2590                 forget_side_comment();
2591             }
2592         }
2593     }
2594     return $do_not_align;
2595 }
2596
2597 sub valign_output_step_A {
2598
2599     ###############################################################
2600     # This is Step A in writing vertically aligned lines.
2601     # The line is prepared according to the alignments which have
2602     # been found. Then it is shipped to the next step.
2603     ###############################################################
2604
2605     my ( $line, $min_ci_gap, $do_not_align, $group_leader_length,
2606         $extra_leading_spaces )
2607       = @_;
2608     my $rfields                   = $line->get_rfields();
2609     my $leading_space_count       = $line->get_leading_space_count();
2610     my $outdent_long_lines        = $line->get_outdent_long_lines();
2611     my $maximum_field_index       = $line->get_jmax();
2612     my $rvertical_tightness_flags = $line->get_rvertical_tightness_flags();
2613
2614     # add any extra spaces
2615     if ( $leading_space_count > $group_leader_length ) {
2616         $leading_space_count += $min_ci_gap;
2617     }
2618
2619     my $str = $rfields->[0];
2620
2621     # loop to concatenate all fields of this line and needed padding
2622     my $total_pad_count = 0;
2623     for my $j ( 1 .. $maximum_field_index ) {
2624
2625         # skip zero-length side comments
2626         last
2627           if (
2628             ( $j == $maximum_field_index )
2629             && ( !defined( $rfields->[$j] )
2630                 || ( length( $rfields->[$j] ) == 0 ) )
2631           );
2632
2633         # compute spaces of padding before this field
2634         my $col = $line->get_column( $j - 1 );
2635         my $pad = $col - ( length($str) + $leading_space_count );
2636
2637         if ($do_not_align) {
2638             $pad =
2639               ( $j < $maximum_field_index )
2640               ? 0
2641               : $rOpts_minimum_space_to_comment - 1;
2642         }
2643
2644         # if the -fpsc flag is set, move the side comment to the selected
2645         # column if and only if it is possible, ignoring constraints on
2646         # line length and minimum space to comment
2647         if ( $rOpts_fixed_position_side_comment && $j == $maximum_field_index )
2648         {
2649             my $newpad = $pad + $rOpts_fixed_position_side_comment - $col - 1;
2650             if ( $newpad >= 0 ) { $pad = $newpad; }
2651         }
2652
2653         # accumulate the padding
2654         if ( $pad > 0 ) { $total_pad_count += $pad; }
2655
2656         # add this field
2657         if ( !defined $rfields->[$j] ) {
2658             write_diagnostics("UNDEFined field at j=$j\n");
2659         }
2660
2661         # only add padding when we have a finite field;
2662         # this avoids extra terminal spaces if we have empty fields
2663         if ( length( $rfields->[$j] ) > 0 ) {
2664             $str .= ' ' x $total_pad_count;
2665             $total_pad_count = 0;
2666             $str .= $rfields->[$j];
2667         }
2668         else {
2669             $total_pad_count = 0;
2670         }
2671
2672         # update side comment history buffer
2673         if ( $j == $maximum_field_index ) {
2674             my $lineno = $file_writer_object->get_output_line_number();
2675             shift @side_comment_history;
2676             push @side_comment_history, [ $lineno, $col ];
2677         }
2678     }
2679
2680     my $side_comment_length = ( length( $rfields->[$maximum_field_index] ) );
2681
2682     # ship this line off
2683     valign_output_step_B( $leading_space_count + $extra_leading_spaces,
2684         $str, $side_comment_length, $outdent_long_lines,
2685         $rvertical_tightness_flags, $group_level );
2686     return;
2687 }
2688
2689 sub get_extra_leading_spaces {
2690
2691     #----------------------------------------------------------
2692     # Define any extra indentation space (for the -lp option).
2693     # Here is why:
2694     # If a list has side comments, sub scan_list must dump the
2695     # list before it sees everything.  When this happens, it sets
2696     # the indentation to the standard scheme, but notes how
2697     # many spaces it would have liked to use.  We may be able
2698     # to recover that space here in the event that all of the
2699     # lines of a list are back together again.
2700     #----------------------------------------------------------
2701
2702     my $extra_leading_spaces = 0;
2703     if ($extra_indent_ok) {
2704         my $object = $group_lines[0]->get_indentation();
2705         if ( ref($object) ) {
2706             my $extra_indentation_spaces_wanted =
2707               get_recoverable_spaces($object);
2708
2709             # all indentation objects must be the same
2710             for my $i ( 1 .. @group_lines - 1 ) {
2711                 if ( $object != $group_lines[$i]->get_indentation() ) {
2712                     $extra_indentation_spaces_wanted = 0;
2713                     last;
2714                 }
2715             }
2716
2717             if ($extra_indentation_spaces_wanted) {
2718
2719                 # the maximum space without exceeding the line length:
2720                 my $avail = $group_lines[0]->get_available_space_on_right();
2721                 $extra_leading_spaces =
2722                   ( $avail > $extra_indentation_spaces_wanted )
2723                   ? $extra_indentation_spaces_wanted
2724                   : $avail;
2725
2726                 # update the indentation object because with -icp the terminal
2727                 # ');' will use the same adjustment.
2728                 $object->permanently_decrease_available_spaces(
2729                     -$extra_leading_spaces );
2730             }
2731         }
2732     }
2733     return $extra_leading_spaces;
2734 }
2735
2736 sub combine_fields {
2737
2738     # combine all fields except for the comment field  ( sidecmt.t )
2739     # Uses global variables:
2740     #  @group_lines
2741     my $maximum_field_index = $group_lines[0]->get_jmax();
2742     foreach my $line (@group_lines) {
2743         my $rfields = $line->get_rfields();
2744         foreach ( 1 .. $maximum_field_index - 1 ) {
2745             $rfields->[0] .= $rfields->[$_];
2746         }
2747         $rfields->[1] = $rfields->[$maximum_field_index];
2748
2749         $line->set_jmax(1);
2750         $line->set_column( 0, 0 );
2751         $line->set_column( 1, 0 );
2752
2753     }
2754     $maximum_field_index = 1;
2755
2756     foreach my $line (@group_lines) {
2757         my $rfields = $line->get_rfields();
2758         for my $k ( 0 .. $maximum_field_index ) {
2759             my $pad = length( $rfields->[$k] ) - $line->current_field_width($k);
2760             if ( $k == 0 ) {
2761                 $pad += $line->get_leading_space_count();
2762             }
2763
2764             if ( $pad > 0 ) { $line->increase_field_width( $k, $pad ) }
2765
2766         }
2767     }
2768     return;
2769 }
2770
2771 sub get_output_line_number {
2772
2773     # the output line number reported to a caller is the number of items
2774     # written plus the number of items in the buffer
2775     my $self   = shift;
2776     my $nlines = @group_lines;
2777     return $nlines + $file_writer_object->get_output_line_number();
2778 }
2779
2780 sub valign_output_step_B {
2781
2782     ###############################################################
2783     # This is Step B in writing vertically aligned lines.
2784     # Vertical tightness is applied according to preset flags.
2785     # In particular this routine handles stacking of opening
2786     # and closing tokens.
2787     ###############################################################
2788
2789     my ( $leading_space_count, $str, $side_comment_length, $outdent_long_lines,
2790         $rvertical_tightness_flags, $level )
2791       = @_;
2792
2793     # handle outdenting of long lines:
2794     if ($outdent_long_lines) {
2795         my $excess =
2796           length($str) -
2797           $side_comment_length +
2798           $leading_space_count -
2799           maximum_line_length_for_level($level);
2800         if ( $excess > 0 ) {
2801             $leading_space_count = 0;
2802             $last_outdented_line_at =
2803               $file_writer_object->get_output_line_number();
2804
2805             unless ($outdented_line_count) {
2806                 $first_outdented_line_at = $last_outdented_line_at;
2807             }
2808             $outdented_line_count++;
2809         }
2810     }
2811
2812     # Make preliminary leading whitespace.  It could get changed
2813     # later by entabbing, so we have to keep track of any changes
2814     # to the leading_space_count from here on.
2815     my $leading_string =
2816       $leading_space_count > 0 ? ( ' ' x $leading_space_count ) : "";
2817
2818     # Unpack any recombination data; it was packed by
2819     # sub send_lines_to_vertical_aligner. Contents:
2820     #
2821     #   [0] type: 1=opening non-block    2=closing non-block
2822     #             3=opening block brace  4=closing block brace
2823     #   [1] flag: if opening: 1=no multiple steps, 2=multiple steps ok
2824     #             if closing: spaces of padding to use
2825     #   [2] sequence number of container
2826     #   [3] valid flag: do not append if this flag is false
2827     #
2828     my ( $open_or_close, $tightness_flag, $seqno, $valid, $seqno_beg,
2829         $seqno_end );
2830     if ($rvertical_tightness_flags) {
2831         (
2832             $open_or_close, $tightness_flag, $seqno, $valid, $seqno_beg,
2833             $seqno_end
2834         ) = @{$rvertical_tightness_flags};
2835     }
2836
2837     $seqno_string = $seqno_end;
2838
2839     # handle any cached line ..
2840     # either append this line to it or write it out
2841     if ( length($cached_line_text) ) {
2842
2843         # Dump an invalid cached line
2844         if ( !$cached_line_valid ) {
2845             valign_output_step_C( $cached_line_text,
2846                 $cached_line_leading_space_count,
2847                 $last_level_written );
2848         }
2849
2850         # Handle cached line ending in OPENING tokens
2851         elsif ( $cached_line_type == 1 || $cached_line_type == 3 ) {
2852
2853             my $gap = $leading_space_count - length($cached_line_text);
2854
2855             # handle option of just one tight opening per line:
2856             if ( $cached_line_flag == 1 ) {
2857                 if ( defined($open_or_close) && $open_or_close == 1 ) {
2858                     $gap = -1;
2859                 }
2860             }
2861
2862             if ( $gap >= 0 && defined($seqno_beg) ) {
2863                 $leading_string      = $cached_line_text . ' ' x $gap;
2864                 $leading_space_count = $cached_line_leading_space_count;
2865                 $seqno_string        = $cached_seqno_string . ':' . $seqno_beg;
2866                 $level               = $last_level_written;
2867             }
2868             else {
2869                 valign_output_step_C( $cached_line_text,
2870                     $cached_line_leading_space_count,
2871                     $last_level_written );
2872             }
2873         }
2874
2875         # Handle cached line ending in CLOSING tokens
2876         else {
2877             my $test_line = $cached_line_text . ' ' x $cached_line_flag . $str;
2878             if (
2879
2880                 # The new line must start with container
2881                 $seqno_beg
2882
2883                 # The container combination must be okay..
2884                 && (
2885
2886                     # okay to combine like types
2887                     ( $open_or_close == $cached_line_type )
2888
2889                     # closing block brace may append to non-block
2890                     || ( $cached_line_type == 2 && $open_or_close == 4 )
2891
2892                     # something like ');'
2893                     || ( !$open_or_close && $cached_line_type == 2 )
2894
2895                 )
2896
2897                 # The combined line must fit
2898                 && (
2899                     length($test_line) <=
2900                     maximum_line_length_for_level($last_level_written) )
2901               )
2902             {
2903
2904                 $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
2905
2906                 # Patch to outdent closing tokens ending # in ');'
2907                 # If we are joining a line like ');' to a previous stacked
2908                 # set of closing tokens, then decide if we may outdent the
2909                 # combined stack to the indentation of the ');'.  Since we
2910                 # should not normally outdent any of the other tokens more than
2911                 # the indentation of the lines that contained them, we will
2912                 # only do this if all of the corresponding opening
2913                 # tokens were on the same line.  This can happen with
2914                 # -sot and -sct.  For example, it is ok here:
2915                 #   __PACKAGE__->load_components( qw(
2916                 #         PK::Auto
2917                 #         Core
2918                 #   ));
2919                 #
2920                 #   But, for example, we do not outdent in this example because
2921                 #   that would put the closing sub brace out farther than the
2922                 #   opening sub brace:
2923                 #
2924                 #   perltidy -sot -sct
2925                 #   $c->Tk::bind(
2926                 #       '<Control-f>' => sub {
2927                 #           my ($c) = @_;
2928                 #           my $e = $c->XEvent;
2929                 #           itemsUnderArea $c;
2930                 #       } );
2931                 #
2932                 if ( $str =~ /^\);/ && $cached_line_text =~ /^[\)\}\]\s]*$/ ) {
2933
2934                     # The way to tell this is if the stacked sequence numbers
2935                     # of this output line are the reverse of the stacked
2936                     # sequence numbers of the previous non-blank line of
2937                     # sequence numbers.  So we can join if the previous
2938                     # nonblank string of tokens is the mirror image.  For
2939                     # example if stack )}] is 13:8:6 then we are looking for a
2940                     # leading stack like [{( which is 6:8:13 We only need to
2941                     # check the two ends, because the intermediate tokens must
2942                     # fall in order.  Note on speed: having to split on colons
2943                     # and eliminate multiple colons might appear to be slow,
2944                     # but it's not an issue because we almost never come
2945                     # through here.  In a typical file we don't.
2946                     $seqno_string               =~ s/^:+//;
2947                     $last_nonblank_seqno_string =~ s/^:+//;
2948                     $seqno_string               =~ s/:+/:/g;
2949                     $last_nonblank_seqno_string =~ s/:+/:/g;
2950
2951                     # how many spaces can we outdent?
2952                     my $diff =
2953                       $cached_line_leading_space_count - $leading_space_count;
2954                     if (   $diff > 0
2955                         && length($seqno_string)
2956                         && length($last_nonblank_seqno_string) ==
2957                         length($seqno_string) )
2958                     {
2959                         my @seqno_last =
2960                           ( split /:/, $last_nonblank_seqno_string );
2961                         my @seqno_now = ( split /:/, $seqno_string );
2962                         if (   $seqno_now[-1] == $seqno_last[0]
2963                             && $seqno_now[0] == $seqno_last[-1] )
2964                         {
2965
2966                             # OK to outdent ..
2967                             # for absolute safety, be sure we only remove
2968                             # whitespace
2969                             my $ws = substr( $test_line, 0, $diff );
2970                             if ( ( length($ws) == $diff ) && $ws =~ /^\s+$/ ) {
2971
2972                                 $test_line = substr( $test_line, $diff );
2973                                 $cached_line_leading_space_count -= $diff;
2974                                 $last_level_written =
2975                                   level_change(
2976                                     $cached_line_leading_space_count,
2977                                     $diff, $last_level_written );
2978                                 reduce_valign_buffer_indentation($diff);
2979                             }
2980
2981                             # shouldn't happen, but not critical:
2982                             ##else {
2983                             ## ERROR transferring indentation here
2984                             ##}
2985                         }
2986                     }
2987                 }
2988
2989                 $str                 = $test_line;
2990                 $leading_string      = "";
2991                 $leading_space_count = $cached_line_leading_space_count;
2992                 $level               = $last_level_written;
2993             }
2994             else {
2995                 valign_output_step_C( $cached_line_text,
2996                     $cached_line_leading_space_count,
2997                     $last_level_written );
2998             }
2999         }
3000     }
3001     $cached_line_type = 0;
3002     $cached_line_text = "";
3003
3004     # make the line to be written
3005     my $line = $leading_string . $str;
3006
3007     # write or cache this line
3008     if ( !$open_or_close || $side_comment_length > 0 ) {
3009         valign_output_step_C( $line, $leading_space_count, $level );
3010     }
3011     else {
3012         $cached_line_text                = $line;
3013         $cached_line_type                = $open_or_close;
3014         $cached_line_flag                = $tightness_flag;
3015         $cached_seqno                    = $seqno;
3016         $cached_line_valid               = $valid;
3017         $cached_line_leading_space_count = $leading_space_count;
3018         $cached_seqno_string             = $seqno_string;
3019     }
3020
3021     $last_level_written       = $level;
3022     $last_side_comment_length = $side_comment_length;
3023     $extra_indent_ok          = 0;
3024     return;
3025 }
3026
3027 sub valign_output_step_C {
3028
3029     ###############################################################
3030     # This is Step C in writing vertically aligned lines.
3031     # Lines are either stored in a buffer or passed along to the next step.
3032     # The reason for storing lines is that we may later want to reduce their
3033     # indentation when -sot and -sct are both used.
3034     ###############################################################
3035     my @args = @_;
3036
3037     # Dump any saved lines if we see a line with an unbalanced opening or
3038     # closing token.
3039     dump_valign_buffer() if ( $seqno_string && $valign_buffer_filling );
3040
3041     # Either store or write this line
3042     if ($valign_buffer_filling) {
3043         push @valign_buffer, [@args];
3044     }
3045     else {
3046         valign_output_step_D(@args);
3047     }
3048
3049     # For lines starting or ending with opening or closing tokens..
3050     if ($seqno_string) {
3051         $last_nonblank_seqno_string = $seqno_string;
3052
3053         # Start storing lines when we see a line with multiple stacked opening
3054         # tokens.
3055         # patch for RT #94354, requested by Colin Williams
3056         if ( $seqno_string =~ /^\d+(\:+\d+)+$/ && $args[0] !~ /^[\}\)\]\:\?]/ )
3057         {
3058
3059             # This test is efficient but a little subtle: The first test says
3060             # that we have multiple sequence numbers and hence multiple opening
3061             # or closing tokens in this line.  The second part of the test
3062             # rejects stacked closing and ternary tokens.  So if we get here
3063             # then we should have stacked unbalanced opening tokens.
3064
3065             # Here is a complex example:
3066
3067             # Foo($Bar[0], {  # (side comment)
3068             #   baz => 1,
3069             # });
3070
3071             # The first line has sequence 6::4.  It does not begin with
3072             # a closing token or ternary, so it passes the test and must be
3073             # stacked opening tokens.
3074
3075             # The last line has sequence 4:6 but is a stack of closing tokens,
3076             # so it gets rejected.
3077
3078             # Note that the sequence number of an opening token for a qw quote
3079             # is a negative number and will be rejected.
3080             # For example, for the following line:
3081             #    skip_symbols([qw(
3082             # $seqno_string='10:5:-1'.  It would be okay to accept it but
3083             # I decided not to do this after testing.
3084
3085             $valign_buffer_filling = $seqno_string;
3086
3087         }
3088     }
3089     return;
3090 }
3091
3092 sub valign_output_step_D {
3093
3094     ###############################################################
3095     # This is Step D in writing vertically aligned lines.
3096     # Write one vertically aligned line of code to the output object.
3097     ###############################################################
3098
3099     my ( $line, $leading_space_count, $level ) = @_;
3100
3101     # The line is currently correct if there is no tabbing (recommended!)
3102     # We may have to lop off some leading spaces and replace with tabs.
3103     if ( $leading_space_count > 0 ) {
3104
3105         # Nothing to do if no tabs
3106         if ( !( $rOpts_tabs || $rOpts_entab_leading_whitespace )
3107             || $rOpts_indent_columns <= 0 )
3108         {
3109
3110             # nothing to do
3111         }
3112
3113         # Handle entab option
3114         elsif ($rOpts_entab_leading_whitespace) {
3115
3116          # Patch 12-nov-2018 based on report from Glenn. Extra padding was
3117          # not correctly entabbed, nor were side comments:
3118          # Increase leading space count for a padded line to get correct tabbing
3119             if ( $line =~ /^(\s+)(.*)$/ ) {
3120                 my $spaces = length($1);
3121                 if ( $spaces > $leading_space_count ) {
3122                     $leading_space_count = $spaces;
3123                 }
3124             }
3125
3126             my $space_count =
3127               $leading_space_count % $rOpts_entab_leading_whitespace;
3128             my $tab_count =
3129               int( $leading_space_count / $rOpts_entab_leading_whitespace );
3130             my $leading_string = "\t" x $tab_count . ' ' x $space_count;
3131             if ( $line =~ /^\s{$leading_space_count,$leading_space_count}/ ) {
3132                 substr( $line, 0, $leading_space_count ) = $leading_string;
3133             }
3134             else {
3135
3136                 # shouldn't happen - program error counting whitespace
3137                 # - skip entabbing
3138                 VALIGN_DEBUG_FLAG_TABS
3139                   && warning(
3140 "Error entabbing in valign_output_step_D: expected count=$leading_space_count\n"
3141                   );
3142             }
3143         }
3144
3145         # Handle option of one tab per level
3146         else {
3147             my $leading_string = ( "\t" x $level );
3148             my $space_count =
3149               $leading_space_count - $level * $rOpts_indent_columns;
3150
3151             # shouldn't happen:
3152             if ( $space_count < 0 ) {
3153
3154                 # But it could be an outdented comment
3155                 if ( $line !~ /^\s*#/ ) {
3156                     VALIGN_DEBUG_FLAG_TABS
3157                       && warning(
3158 "Error entabbing in valign_output_step_D: for level=$group_level count=$leading_space_count\n"
3159                       );
3160                 }
3161                 $leading_string = ( ' ' x $leading_space_count );
3162             }
3163             else {
3164                 $leading_string .= ( ' ' x $space_count );
3165             }
3166             if ( $line =~ /^\s{$leading_space_count,$leading_space_count}/ ) {
3167                 substr( $line, 0, $leading_space_count ) = $leading_string;
3168             }
3169             else {
3170
3171                 # shouldn't happen - program error counting whitespace
3172                 # we'll skip entabbing
3173                 VALIGN_DEBUG_FLAG_TABS
3174                   && warning(
3175 "Error entabbing in valign_output_step_D: expected count=$leading_space_count\n"
3176                   );
3177             }
3178         }
3179     }
3180     $file_writer_object->write_code_line( $line . "\n" );
3181     return;
3182 }
3183
3184 {    # begin get_leading_string
3185
3186     my @leading_string_cache;
3187
3188     sub get_leading_string {
3189
3190         # define the leading whitespace string for this line..
3191         my $leading_whitespace_count = shift;
3192
3193         # Handle case of zero whitespace, which includes multi-line quotes
3194         # (which may have a finite level; this prevents tab problems)
3195         if ( $leading_whitespace_count <= 0 ) {
3196             return "";
3197         }
3198
3199         # look for previous result
3200         elsif ( $leading_string_cache[$leading_whitespace_count] ) {
3201             return $leading_string_cache[$leading_whitespace_count];
3202         }
3203
3204         # must compute a string for this number of spaces
3205         my $leading_string;
3206
3207         # Handle simple case of no tabs
3208         if ( !( $rOpts_tabs || $rOpts_entab_leading_whitespace )
3209             || $rOpts_indent_columns <= 0 )
3210         {
3211             $leading_string = ( ' ' x $leading_whitespace_count );
3212         }
3213
3214         # Handle entab option
3215         elsif ($rOpts_entab_leading_whitespace) {
3216             my $space_count =
3217               $leading_whitespace_count % $rOpts_entab_leading_whitespace;
3218             my $tab_count = int(
3219                 $leading_whitespace_count / $rOpts_entab_leading_whitespace );
3220             $leading_string = "\t" x $tab_count . ' ' x $space_count;
3221         }
3222
3223         # Handle option of one tab per level
3224         else {
3225             $leading_string = ( "\t" x $group_level );
3226             my $space_count =
3227               $leading_whitespace_count - $group_level * $rOpts_indent_columns;
3228
3229             # shouldn't happen:
3230             if ( $space_count < 0 ) {
3231                 VALIGN_DEBUG_FLAG_TABS
3232                   && warning(
3233 "Error in get_leading_string: for level=$group_level count=$leading_whitespace_count\n"
3234                   );
3235
3236                 # -- skip entabbing
3237                 $leading_string = ( ' ' x $leading_whitespace_count );
3238             }
3239             else {
3240                 $leading_string .= ( ' ' x $space_count );
3241             }
3242         }
3243         $leading_string_cache[$leading_whitespace_count] = $leading_string;
3244         return $leading_string;
3245     }
3246 }    # end get_leading_string
3247
3248 sub report_anything_unusual {
3249     my $self = shift;
3250     if ( $outdented_line_count > 0 ) {
3251         write_logfile_entry(
3252             "$outdented_line_count long lines were outdented:\n");
3253         write_logfile_entry(
3254             "  First at output line $first_outdented_line_at\n");
3255
3256         if ( $outdented_line_count > 1 ) {
3257             write_logfile_entry(
3258                 "   Last at output line $last_outdented_line_at\n");
3259         }
3260         write_logfile_entry(
3261             "  use -noll to prevent outdenting, -l=n to increase line length\n"
3262         );
3263         write_logfile_entry("\n");
3264     }
3265     return;
3266 }
3267 1;