1 package Perl::Tidy::VerticalAligner;
4 our $VERSION = '20181120';
6 use Perl::Tidy::VerticalAligner::Alignment;
7 use Perl::Tidy::VerticalAligner::Line;
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.
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
18 # valign_input -----> flush
26 # Caution: these debug flags produce a lot of output
27 # They should all be 0 except when debugging small scripts
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;
34 my $debug_warning = sub {
35 print STDOUT "VALIGN_DEBUGGING with key $_[0]\n";
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');
47 $vertical_aligner_self
49 $maximum_alignment_index
53 $previous_minimum_jmax_seen
54 $previous_maximum_jmax_seen
61 $last_leading_space_count
66 $last_side_comment_line_number
67 $last_side_comment_length
68 $last_side_comment_level
70 $first_outdented_line_at
71 $last_outdented_line_at
76 $comment_leading_space_count
77 $is_matching_terminal_line
78 $consecutive_block_comments
85 $cached_line_leading_space_count
88 $valign_buffer_filling
92 $last_nonblank_seqno_string
96 $rOpts_maximum_line_length
97 $rOpts_variable_maximum_line_length
98 $rOpts_continuation_indentation
101 $rOpts_entab_leading_whitespace
104 $rOpts_fixed_position_side_comment
105 $rOpts_minimum_space_to_comment
112 my $class, $rOpts, $file_writer_object, $logger_object,
116 # variables describing the entire space group:
117 $ralignment_list = [];
119 $last_level_written = -1;
120 $extra_indent_ok = 0; # can we move all lines to the right?
121 $last_side_comment_length = 0;
122 $maximum_jmax_seen = 0;
123 $minimum_jmax_seen = 0;
124 $previous_minimum_jmax_seen = 0;
125 $previous_maximum_jmax_seen = 0;
127 # variables describing each line of the group
128 @group_lines = (); # list of all lines in group
130 $outdented_line_count = 0;
131 $first_outdented_line_at = 0;
132 $last_outdented_line_at = 0;
133 $last_side_comment_line_number = 0;
134 $last_side_comment_level = -1;
135 $is_matching_terminal_line = 0;
137 # most recent 3 side comments; [ line number, column ]
138 $side_comment_history[0] = [ -300, 0 ];
139 $side_comment_history[1] = [ -200, 0 ];
140 $side_comment_history[2] = [ -100, 0 ];
142 # valign_output_step_B cache:
143 $cached_line_text = "";
144 $cached_line_type = 0;
145 $cached_line_flag = 0;
147 $cached_line_valid = 0;
148 $cached_line_leading_space_count = 0;
149 $cached_seqno_string = "";
151 # string of sequence numbers joined together
153 $last_nonblank_seqno_string = "";
155 # frequently used parameters
156 $rOpts_indent_columns = $rOpts->{'indent-columns'};
157 $rOpts_tabs = $rOpts->{'tabs'};
158 $rOpts_entab_leading_whitespace = $rOpts->{'entab-leading-whitespace'};
159 $rOpts_fixed_position_side_comment =
160 $rOpts->{'fixed-position-side-comment'};
161 $rOpts_minimum_space_to_comment = $rOpts->{'minimum-space-to-comment'};
162 $rOpts_maximum_line_length = $rOpts->{'maximum-line-length'};
163 $rOpts_variable_maximum_line_length =
164 $rOpts->{'variable-maximum-line-length'};
165 $rOpts_valign = $rOpts->{'valign'};
167 $consecutive_block_comments = 0;
168 forget_side_comment();
170 initialize_for_new_group();
172 $vertical_aligner_self = {};
173 bless $vertical_aligner_self, $class;
174 return $vertical_aligner_self;
177 sub initialize_for_new_group {
178 $maximum_line_index = -1; # lines in the current group
179 $maximum_alignment_index = -1; # alignments in current group
180 $zero_count = 0; # count consecutive lines without tokens
181 $current_line = undef; # line being matched for alignment
182 $group_maximum_gap = 0; # largest gap introduced
185 $comment_leading_space_count = 0;
186 $last_leading_space_count = 0;
190 # interface to Perl::Tidy::Diagnostics routines
191 sub write_diagnostics {
193 if ($diagnostics_object) {
194 $diagnostics_object->write_diagnostics($msg);
199 # interface to Perl::Tidy::Logger routines
202 if ($logger_object) {
203 $logger_object->warning($msg);
208 sub write_logfile_entry {
210 if ($logger_object) {
211 $logger_object->write_logfile_entry($msg);
216 sub report_definite_bug {
217 if ($logger_object) {
218 $logger_object->report_definite_bug();
225 # return the number of leading spaces associated with an indentation
226 # variable $indentation is either a constant number of spaces or an
227 # object with a get_spaces method.
228 my $indentation = shift;
229 return ref($indentation) ? $indentation->get_spaces() : $indentation;
232 sub get_recoverable_spaces {
234 # return the number of spaces (+ means shift right, - means shift left)
235 # that we would like to shift a group of lines with the same indentation
236 # to get them to line up with their opening parens
237 my $indentation = shift;
238 return ref($indentation) ? $indentation->get_recoverable_spaces() : 0;
241 sub get_stack_depth {
243 my $indentation = shift;
244 return ref($indentation) ? $indentation->get_stack_depth() : 0;
248 my ( $col, $token ) = @_;
250 # make one new alignment at column $col which aligns token $token
251 ++$maximum_alignment_index;
253 #my $alignment = new Perl::Tidy::VerticalAligner::Alignment(
254 my $alignment = Perl::Tidy::VerticalAligner::Alignment->new(
256 starting_column => $col,
257 matching_token => $token,
258 starting_line => $maximum_line_index,
259 ending_line => $maximum_line_index,
260 serial_number => $maximum_alignment_index,
262 $ralignment_list->[$maximum_alignment_index] = $alignment;
266 sub dump_alignments {
268 "Current Alignments:\ni\ttoken\tstarting_column\tcolumn\tstarting_line\tending_line\n";
269 for my $i ( 0 .. $maximum_alignment_index ) {
270 my $column = $ralignment_list->[$i]->get_column();
271 my $starting_column = $ralignment_list->[$i]->get_starting_column();
272 my $matching_token = $ralignment_list->[$i]->get_matching_token();
273 my $starting_line = $ralignment_list->[$i]->get_starting_line();
274 my $ending_line = $ralignment_list->[$i]->get_ending_line();
276 "$i\t$matching_token\t$starting_column\t$column\t$starting_line\t$ending_line\n";
281 sub save_alignment_columns {
282 for my $i ( 0 .. $maximum_alignment_index ) {
283 $ralignment_list->[$i]->save_column();
288 sub restore_alignment_columns {
289 for my $i ( 0 .. $maximum_alignment_index ) {
290 $ralignment_list->[$i]->restore_column();
295 sub forget_side_comment {
296 $last_comment_column = 0;
300 sub maximum_line_length_for_level {
302 # return maximum line length for line starting with a given level
303 my $maximum_line_length = $rOpts_maximum_line_length;
304 if ($rOpts_variable_maximum_line_length) {
306 if ( $level < 0 ) { $level = 0 }
307 $maximum_line_length += $level * $rOpts_indent_columns;
309 return $maximum_line_length;
314 # Place one line in the current vertical group.
316 # The input parameters are:
317 # $level = indentation level of this line
318 # $rfields = reference to array of fields
319 # $rpatterns = reference to array of patterns, one per field
320 # $rtokens = reference to array of tokens starting fields 1,2,..
322 # Here is an example of what this package does. In this example,
323 # we are trying to line up both the '=>' and the '#'.
325 # '18' => 'grave', # \`
326 # '19' => 'acute', # `'
327 # '20' => 'caron', # \v
328 # <-tabs-><f1-><--field 2 ---><-f3->
331 # col1 col2 col3 col4
333 # The calling routine has already broken the entire line into 3 fields as
334 # indicated. (So the work of identifying promising common tokens has
335 # already been done).
337 # In this example, there will be 2 tokens being matched: '=>' and '#'.
338 # They are the leading parts of fields 2 and 3, but we do need to know
339 # what they are so that we can dump a group of lines when these tokens
342 # The fields contain the actual characters of each field. The patterns
343 # are like the fields, but they contain mainly token types instead
344 # of tokens, so they have fewer characters. They are used to be
345 # sure we are matching fields of similar type.
347 # In this example, there will be 4 column indexes being adjusted. The
348 # first one is always at zero. The interior columns are at the start of
349 # the matching tokens, and the last one tracks the maximum line length.
351 # Each time a new line comes in, it joins the current vertical
352 # group if possible. Otherwise it causes the current group to be dumped
353 # and a new group is started.
355 # For each new group member, the column locations are increased, as
356 # necessary, to make room for the new fields. When the group is finally
357 # output, these column numbers are used to compute the amount of spaces of
358 # padding needed for each field.
360 # Programming note: the fields are assumed not to have any tab characters.
361 # Tabs have been previously removed except for tabs in quoted strings and
362 # side comments. Tabs in these fields can mess up the column counting.
363 # The log file warns the user if there are any such tabs.
365 my ( $rline_hash, $rfields, $rtokens, $rpatterns ) = @_;
366 my $level = $rline_hash->{level};
367 my $level_end = $rline_hash->{level_end};
368 my $indentation = $rline_hash->{indentation};
369 my $is_forced_break = $rline_hash->{is_forced_break};
370 my $outdent_long_lines = $rline_hash->{outdent_long_lines};
371 my $is_terminal_ternary = $rline_hash->{is_terminal_ternary};
372 my $is_terminal_statement = $rline_hash->{is_terminal_statement};
373 my $do_not_pad = $rline_hash->{do_not_pad};
374 my $rvertical_tightness_flags = $rline_hash->{rvertical_tightness_flags};
375 my $level_jump = $rline_hash->{level_jump};
377 # number of fields is $jmax
378 # number of tokens between fields is $jmax-1
379 my $jmax = $#{$rfields};
381 my $leading_space_count = get_spaces($indentation);
383 # set outdented flag to be sure we either align within statements or
384 # across statement boundaries, but not both.
385 my $is_outdented = $last_leading_space_count > $leading_space_count;
386 $last_leading_space_count = $leading_space_count;
388 # Patch: undo for hanging side comment
389 my $is_hanging_side_comment =
390 ( $jmax == 1 && $rtokens->[0] eq '#' && $rfields->[0] =~ /^\s*$/ );
391 $is_outdented = 0 if $is_hanging_side_comment;
393 # Forget side comment alignment after seeing 2 or more block comments
394 my $is_block_comment = ( $jmax == 0 && $rfields->[0] =~ /^#/ );
395 if ($is_block_comment) {
396 $consecutive_block_comments++;
399 if ( $consecutive_block_comments > 1 ) { forget_side_comment() }
400 $consecutive_block_comments = 0;
403 VALIGN_DEBUG_FLAG_APPEND0 && do {
405 "APPEND0: entering lines=$maximum_line_index new #fields= $jmax, leading_count=$leading_space_count last_cmt=$last_comment_column force=$is_forced_break\n";
408 # Validate cached line if necessary: If we can produce a container
409 # with just 2 lines total by combining an existing cached opening
410 # token with the closing token to follow, then we will mark both
411 # cached flags as valid.
412 if ($rvertical_tightness_flags) {
413 if ( $maximum_line_index <= 0
416 && $rvertical_tightness_flags->[2]
417 && $rvertical_tightness_flags->[2] == $cached_seqno )
419 $rvertical_tightness_flags->[3] ||= 1;
420 $cached_line_valid ||= 1;
424 # do not join an opening block brace with an unbalanced line
425 # unless requested with a flag value of 2
426 if ( $cached_line_type == 3
427 && $maximum_line_index < 0
428 && $cached_line_flag < 2
429 && $level_jump != 0 )
431 $cached_line_valid = 0;
434 # patch until new aligner is finished
435 if ($do_not_pad) { my_flush() }
438 if ( $level < 0 ) { $level = 0 }
440 # do not align code across indentation level changes
441 # or if vertical alignment is turned off for debugging
442 if ( $level != $group_level || $is_outdented || !$rOpts_valign ) {
444 # we are allowed to shift a group of lines to the right if its
445 # level is greater than the previous and next group
447 ( $level < $group_level && $last_level_written < $group_level );
451 # If we know that this line will get flushed out by itself because
452 # of level changes, we can leave the extra_indent_ok flag set.
453 # That way, if we get an external flush call, we will still be
454 # able to do some -lp alignment if necessary.
455 $extra_indent_ok = ( $is_terminal_statement && $level > $group_level );
457 $group_level = $level;
459 # wait until after the above flush to get the leading space
460 # count because it may have been changed if the -icp flag is in
462 $leading_space_count = get_spaces($indentation);
466 # --------------------------------------------------------------------
467 # Patch to collect outdentable block COMMENTS
468 # --------------------------------------------------------------------
469 my $is_blank_line = "";
470 if ( $group_type eq 'COMMENT' ) {
474 && $outdent_long_lines
475 && $leading_space_count == $comment_leading_space_count
480 $group_lines[ ++$maximum_line_index ] = $rfields->[0];
488 # --------------------------------------------------------------------
489 # add dummy fields for terminal ternary
490 # --------------------------------------------------------------------
491 my $j_terminal_match;
492 if ( $is_terminal_ternary && $current_line ) {
494 fix_terminal_ternary( $rfields, $rtokens, $rpatterns );
495 $jmax = @{$rfields} - 1;
498 # --------------------------------------------------------------------
499 # add dummy fields for else statement
500 # --------------------------------------------------------------------
501 if ( $rfields->[0] =~ /^else\s*$/
503 && $level_jump == 0 )
505 $j_terminal_match = fix_terminal_else( $rfields, $rtokens, $rpatterns );
506 $jmax = @{$rfields} - 1;
509 # --------------------------------------------------------------------
510 # Step 1. Handle simple line of code with no fields to match.
511 # --------------------------------------------------------------------
515 if ( $maximum_line_index >= 0
516 && !get_recoverable_spaces( $group_lines[0]->get_indentation() ) )
519 # flush the current group if it has some aligned columns..
520 if ( $group_lines[0]->get_jmax() > 1 ) { my_flush() }
522 # flush current group if we are just collecting side comments..
525 # ...and we haven't seen a comment lately
528 # ..or if this new line doesn't fit to the left of the comments
529 || ( ( $leading_space_count + length( $rfields->[0] ) ) >
530 $group_lines[0]->get_column(0) )
537 # patch to start new COMMENT group if this comment may be outdented
538 if ( $is_block_comment
539 && $outdent_long_lines
540 && $maximum_line_index < 0 )
542 $group_type = 'COMMENT';
543 $comment_leading_space_count = $leading_space_count;
544 $group_lines[ ++$maximum_line_index ] = $rfields->[0];
548 # just write this line directly if no current group, no side comment,
549 # and no space recovery is needed.
550 if ( $maximum_line_index < 0 && !get_recoverable_spaces($indentation) )
552 valign_output_step_B( $leading_space_count, $rfields->[0], 0,
553 $outdent_long_lines, $rvertical_tightness_flags, $level );
561 # programming check: (shouldn't happen)
562 # an error here implies an incorrect call was made
563 if ( $jmax > 0 && ( $#{$rtokens} != ( $jmax - 1 ) ) ) {
565 "Program bug in Perl::Tidy::VerticalAligner - number of tokens = $#{$rtokens} should be one less than number of fields: $#{$rfields})\n"
567 report_definite_bug();
570 # --------------------------------------------------------------------
571 # create an object to hold this line
572 # --------------------------------------------------------------------
573 ##my $new_line = new Perl::Tidy::VerticalAligner::Line(
574 my $new_line = Perl::Tidy::VerticalAligner::Line->new(
576 jmax_original_line => $jmax,
579 rpatterns => $rpatterns,
580 indentation => $indentation,
581 leading_space_count => $leading_space_count,
582 outdent_long_lines => $outdent_long_lines,
584 is_hanging_side_comment => $is_hanging_side_comment,
585 maximum_line_length => maximum_line_length_for_level($level),
586 rvertical_tightness_flags => $rvertical_tightness_flags,
589 # Initialize a global flag saying if the last line of the group should
590 # match end of group and also terminate the group. There should be no
591 # returns between here and where the flag is handled at the bottom.
592 my $col_matching_terminal = 0;
593 if ( defined($j_terminal_match) ) {
595 # remember the column of the terminal ? or { to match with
596 $col_matching_terminal = $current_line->get_column($j_terminal_match);
598 # set global flag for sub decide_if_aligned
599 $is_matching_terminal_line = 1;
602 # --------------------------------------------------------------------
603 # It simplifies things to create a zero length side comment
605 # --------------------------------------------------------------------
606 make_side_comment( $new_line, $level_end );
608 # --------------------------------------------------------------------
609 # Decide if this is a simple list of items.
610 # There are 3 list types: none, comma, comma-arrow.
611 # We use this below to be less restrictive in deciding what to align.
612 # --------------------------------------------------------------------
613 if ($is_forced_break) {
614 decide_if_list($new_line);
619 # --------------------------------------------------------------------
620 # Allow hanging side comment to join current group, if any
621 # This will help keep side comments aligned, because otherwise we
622 # will have to start a new group, making alignment less likely.
623 # --------------------------------------------------------------------
624 join_hanging_comment( $new_line, $current_line )
625 if $is_hanging_side_comment;
627 # --------------------------------------------------------------------
628 # If there is just one previous line, and it has more fields
629 # than the new line, try to join fields together to get a match with
630 # the new line. At the present time, only a single leading '=' is
631 # allowed to be compressed out. This is useful in rare cases where
632 # a table is forced to use old breakpoints because of side comments,
633 # and the table starts out something like this:
634 # my %MonthChars = ('0', 'Jan', # side comment
637 # Eliminating the '=' field will allow the remaining fields to line up.
638 # This situation does not occur if there are no side comments
639 # because scan_list would put a break after the opening '('.
640 # --------------------------------------------------------------------
641 eliminate_old_fields( $new_line, $current_line );
643 # --------------------------------------------------------------------
644 # If the new line has more fields than the current group,
645 # see if we can match the first fields and combine the remaining
646 # fields of the new line.
647 # --------------------------------------------------------------------
648 eliminate_new_fields( $new_line, $current_line );
650 # --------------------------------------------------------------------
651 # Flush previous group unless all common tokens and patterns match..
652 # --------------------------------------------------------------------
653 check_match( $new_line, $current_line );
655 # --------------------------------------------------------------------
656 # See if there is space for this line in the current group (if any)
657 # --------------------------------------------------------------------
659 check_fit( $new_line, $current_line );
663 # --------------------------------------------------------------------
664 # Append this line to the current group (or start new group)
665 # --------------------------------------------------------------------
666 add_to_group($new_line);
668 # Future update to allow this to vary:
669 $current_line = $new_line if ( $maximum_line_index == 0 );
671 # output this group if it ends in a terminal else or ternary line
672 if ( defined($j_terminal_match) ) {
674 # if there is only one line in the group (maybe due to failure to match
675 # perfectly with previous lines), then align the ? or { of this
676 # terminal line with the previous one unless that would make the line
678 if ( $maximum_line_index == 0 ) {
679 my $col_now = $current_line->get_column($j_terminal_match);
680 my $pad = $col_matching_terminal - $col_now;
681 my $padding_available =
682 $current_line->get_available_space_on_right();
683 if ( $pad > 0 && $pad <= $padding_available ) {
684 $current_line->increase_field_width( $j_terminal_match, $pad );
688 $is_matching_terminal_line = 0;
691 # --------------------------------------------------------------------
692 # Step 8. Some old debugging stuff
693 # --------------------------------------------------------------------
694 VALIGN_DEBUG_FLAG_APPEND && do {
695 print STDOUT "APPEND fields:";
696 dump_array( @{$rfields} );
697 print STDOUT "APPEND tokens:";
698 dump_array( @{$rtokens} );
699 print STDOUT "APPEND patterns:";
700 dump_array( @{$rpatterns} );
707 sub join_hanging_comment {
710 my $jmax = $line->get_jmax();
711 return 0 unless $jmax == 1; # must be 2 fields
712 my $rtokens = $line->get_rtokens();
713 return 0 unless $rtokens->[0] eq '#'; # the second field is a comment..
714 my $rfields = $line->get_rfields();
715 return 0 unless $rfields->[0] =~ /^\s*$/; # the first field is empty...
716 my $old_line = shift;
717 my $maximum_field_index = $old_line->get_jmax();
719 unless $maximum_field_index > $jmax; # the current line has more fields
720 my $rpatterns = $line->get_rpatterns();
722 $line->set_is_hanging_side_comment(1);
723 $jmax = $maximum_field_index;
724 $line->set_jmax($jmax);
725 $rfields->[$jmax] = $rfields->[1];
726 $rtokens->[ $jmax - 1 ] = $rtokens->[0];
727 $rpatterns->[ $jmax - 1 ] = $rpatterns->[0];
728 foreach my $j ( 1 .. $jmax - 1 ) {
729 $rfields->[$j] = " "; # NOTE: caused glitch unless 1 blank, why?
730 $rtokens->[ $j - 1 ] = "";
731 $rpatterns->[ $j - 1 ] = "";
736 sub eliminate_old_fields {
738 my $new_line = shift;
739 my $jmax = $new_line->get_jmax();
740 if ( $jmax > $maximum_jmax_seen ) { $maximum_jmax_seen = $jmax }
741 if ( $jmax < $minimum_jmax_seen ) { $minimum_jmax_seen = $jmax }
743 # there must be one previous line
744 return unless ( $maximum_line_index == 0 );
746 my $old_line = shift;
747 my $maximum_field_index = $old_line->get_jmax();
749 ###############################################
750 # Moved below to allow new coding for => matches
751 # return unless $maximum_field_index > $jmax;
752 ###############################################
754 # Identify specific cases where field elimination is allowed:
755 # case=1: both lines have comma-separated lists, and the first
757 # case=2: both lines have leading equals
759 # case 1 is the default
762 # See if case 2: both lines have leading '='
763 # We'll require similar leading patterns in this case
764 my $old_rtokens = $old_line->get_rtokens();
765 my $rtokens = $new_line->get_rtokens();
766 my $rpatterns = $new_line->get_rpatterns();
767 my $old_rpatterns = $old_line->get_rpatterns();
768 if ( $rtokens->[0] =~ /^=>?\d*$/
769 && $old_rtokens->[0] eq $rtokens->[0]
770 && $old_rpatterns->[0] eq $rpatterns->[0] )
775 # not too many fewer fields in new line for case 1
776 return unless ( $case != 1 || $maximum_field_index - 2 <= $jmax );
778 # case 1 must have side comment
779 my $old_rfields = $old_line->get_rfields();
782 && length( $old_rfields->[$maximum_field_index] ) == 0 );
784 my $rfields = $new_line->get_rfields();
788 my @new_alignments = ();
790 my @new_matching_patterns = ();
791 my @new_matching_tokens = ();
794 my $current_field = '';
795 my $current_pattern = '';
797 # loop over all old tokens
799 foreach my $k ( 0 .. $maximum_field_index - 1 ) {
800 $current_field .= $old_rfields->[$k];
801 $current_pattern .= $old_rpatterns->[$k];
802 last if ( $j > $jmax - 1 );
804 if ( $old_rtokens->[$k] eq $rtokens->[$j] ) {
806 $new_fields[$j] = $current_field;
807 $new_matching_patterns[$j] = $current_pattern;
809 $current_pattern = '';
810 $new_matching_tokens[$j] = $old_rtokens->[$k];
811 $new_alignments[$j] = $old_line->get_alignment($k);
816 if ( $old_rtokens->[$k] =~ /^\=\d*$/ ) {
817 last if ( $case == 2 ); # avoid problems with stuff
822 if ( $in_match && $case == 1 )
823 ; # disallow gaps in matching field types in case 1
827 # Modify the current state if we are successful.
828 # We must exactly reach the ends of the new list for success, and the old
829 # pattern must have more fields. Here is an example where the first and
830 # second lines have the same number, and we should not align:
831 # my @a = map chr, 0 .. 255;
832 # my @b = grep /\W/, @a;
833 # my @c = grep /[^\w]/, @a;
835 # Otherwise, we would get all of the commas aligned, which doesn't work as
837 # my @a = map chr, 0 .. 255;
838 # my @b = grep /\W/, @a;
839 # my @c = grep /[^\w]/, @a;
842 && ( $current_field eq '' )
843 && ( $case != 1 || $hid_equals )
844 && ( $maximum_field_index > $jmax ) )
846 my $k = $maximum_field_index;
847 $current_field .= $old_rfields->[$k];
848 $current_pattern .= $old_rpatterns->[$k];
849 $new_fields[$j] = $current_field;
850 $new_matching_patterns[$j] = $current_pattern;
852 $new_alignments[$j] = $old_line->get_alignment($k);
853 $maximum_field_index = $j;
855 $old_line->set_alignments(@new_alignments);
856 $old_line->set_jmax($jmax);
857 $old_line->set_rtokens( \@new_matching_tokens );
858 $old_line->set_rfields( \@new_fields );
859 $old_line->set_rpatterns( \@{$rpatterns} );
862 # Dumb Down starting match if necessary:
864 # Consider the following two lines:
867 # $a => 20 > 3 ? 1 : 0,
871 # We would like to get alignment regardless of the order of the two lines.
872 # If the lines come in in this order, then we will simplify the patterns of the first line
873 # in sub eliminate_new_fields.
874 # If the lines come in reverse order, then we achieve this with eliminate_new_fields.
876 # This update is currently restricted to leading '=>' matches. Although we
877 # could do this for both '=' and '=>', overall the results for '=' come out
878 # better without this step because this step can eliminate some other good
879 # matches. For example, with the '=' we get:
881 # my @disilva = ( "di Silva", "diSilva", "di Si\x{301}lva", "diSi\x{301}lva" );
882 # my @dsf = map "$_\x{FFFE}Fred", @disilva;
883 # my @dsj = map "$_\x{FFFE}John", @disilva;
884 # my @dsJ = map "$_ John", @disilva;
886 # without including '=' we get:
888 # my @disilva = ( "di Silva", "diSilva", "di Si\x{301}lva", "diSi\x{301}lva" );
889 # my @dsf = map "$_\x{FFFE}Fred", @disilva;
890 # my @dsj = map "$_\x{FFFE}John", @disilva;
891 # my @dsJ = map "$_ John", @disilva;
895 && @new_matching_tokens == 1
896 ##&& $new_matching_tokens[0] =~ /^=/ # see note above
897 && $new_matching_tokens[0] =~ /^=>/
898 && $maximum_field_index > 2
901 my $jmaxm = $jmax - 1;
902 my $kmaxm = $maximum_field_index - 1;
903 my $have_side_comment = $old_rtokens->[$kmaxm] eq '#';
905 # We need to reduce the group pattern to be just two tokens,
906 # the leading equality or => and the final side comment
908 my $mid_field = join "",
909 @{$old_rfields}[ 1 .. $maximum_field_index - 1 ];
910 my $mid_patterns = join "",
911 @{$old_rpatterns}[ 1 .. $maximum_field_index - 1 ];
912 my @new_alignments = (
913 $old_line->get_alignment(0),
914 $old_line->get_alignment( $maximum_field_index - 1 )
917 ( $old_rtokens->[0], $old_rtokens->[ $maximum_field_index - 1 ] );
919 $old_rfields->[0], $mid_field, $old_rfields->[$maximum_field_index]
922 $old_rpatterns->[0], $mid_patterns,
923 $old_rpatterns->[$maximum_field_index]
926 $maximum_field_index = 2;
927 $old_line->set_jmax($maximum_field_index);
928 $old_line->set_rtokens( \@new_tokens );
929 $old_line->set_rfields( \@new_fields );
930 $old_line->set_rpatterns( \@new_patterns );
932 initialize_for_new_group();
933 add_to_group($old_line);
934 $current_line = $old_line;
939 # create an empty side comment if none exists
940 sub make_side_comment {
941 my ( $new_line, $level_end ) = @_;
942 my $jmax = $new_line->get_jmax();
943 my $rtokens = $new_line->get_rtokens();
945 # if line does not have a side comment...
946 if ( ( $jmax == 0 ) || ( $rtokens->[ $jmax - 1 ] ne '#' ) ) {
947 my $rfields = $new_line->get_rfields();
948 my $rpatterns = $new_line->get_rpatterns();
949 $rtokens->[$jmax] = '#';
950 $rfields->[ ++$jmax ] = '';
951 $rpatterns->[$jmax] = '#';
952 $new_line->set_jmax($jmax);
953 $new_line->set_jmax_original_line($jmax);
956 # line has a side comment..
959 # don't remember old side comment location for very long
960 my $line_number = $vertical_aligner_self->get_output_line_number();
961 my $rfields = $new_line->get_rfields();
963 $line_number - $last_side_comment_line_number > 12
965 # and don't remember comment location across block level changes
966 || ( $level_end < $last_side_comment_level
967 && $rfields->[0] =~ /^}/ )
970 forget_side_comment();
972 $last_side_comment_line_number = $line_number;
973 $last_side_comment_level = $level_end;
982 # A list will be taken to be a line with a forced break in which all
983 # of the field separators are commas or comma-arrows (except for the
986 # List separator tokens are things like ',3' or '=>2',
987 # where the trailing digit is the nesting depth. Allow braces
988 # to allow nested list items.
989 my $rtokens = $line->get_rtokens();
990 my $test_token = $rtokens->[0];
991 if ( $test_token =~ /^(\,|=>)/ ) {
992 my $list_type = $test_token;
993 my $jmax = $line->get_jmax();
995 foreach ( 1 .. $jmax - 2 ) {
996 if ( $rtokens->[$_] !~ /^(\,|=>|\{)/ ) {
1001 $line->set_list_type($list_type);
1006 sub eliminate_new_fields {
1008 my ( $new_line, $old_line ) = @_;
1009 return unless ( $maximum_line_index >= 0 );
1010 my $jmax = $new_line->get_jmax();
1012 my $old_rtokens = $old_line->get_rtokens();
1013 my $rtokens = $new_line->get_rtokens();
1015 ( $rtokens->[0] =~ /^=>?\d*$/ && ( $old_rtokens->[0] eq $rtokens->[0] ) );
1017 # must be monotonic variation
1018 return unless ( $is_assignment || $previous_maximum_jmax_seen <= $jmax );
1020 # must be more fields in the new line
1021 my $maximum_field_index = $old_line->get_jmax();
1022 return unless ( $maximum_field_index < $jmax );
1024 unless ($is_assignment) {
1026 unless ( $old_line->get_jmax_original_line() == $minimum_jmax_seen )
1027 ; # only if monotonic
1029 # never combine fields of a comma list
1031 unless ( $maximum_field_index > 1 )
1032 && ( $new_line->get_list_type() !~ /^,/ );
1035 my $rfields = $new_line->get_rfields();
1036 my $rpatterns = $new_line->get_rpatterns();
1037 my $old_rpatterns = $old_line->get_rpatterns();
1039 # loop over all OLD tokens except comment and check match
1041 foreach my $k ( 0 .. $maximum_field_index - 2 ) {
1042 if ( ( $old_rtokens->[$k] ne $rtokens->[$k] )
1043 || ( $old_rpatterns->[$k] ne $rpatterns->[$k] ) )
1050 # first tokens agree, so combine extra new tokens
1052 ##for my $k ( $maximum_field_index .. $jmax - 1 ) {
1053 foreach my $k ( $maximum_field_index .. $jmax - 1 ) {
1055 $rfields->[ $maximum_field_index - 1 ] .= $rfields->[$k];
1056 $rfields->[$k] = "";
1057 $rpatterns->[ $maximum_field_index - 1 ] .= $rpatterns->[$k];
1058 $rpatterns->[$k] = "";
1061 $rtokens->[ $maximum_field_index - 1 ] = '#';
1062 $rfields->[$maximum_field_index] = $rfields->[$jmax];
1063 $rpatterns->[$maximum_field_index] = $rpatterns->[$jmax];
1064 $jmax = $maximum_field_index;
1066 $new_line->set_jmax($jmax);
1070 sub fix_terminal_ternary {
1072 # Add empty fields as necessary to align a ternary term
1081 # returns 1 if the terminal item should be indented
1083 my ( $rfields, $rtokens, $rpatterns ) = @_;
1085 my $jmax = @{$rfields} - 1;
1086 my $old_line = $group_lines[$maximum_line_index];
1087 my $rfields_old = $old_line->get_rfields();
1089 my $rpatterns_old = $old_line->get_rpatterns();
1090 my $rtokens_old = $old_line->get_rtokens();
1091 my $maximum_field_index = $old_line->get_jmax();
1093 # look for the question mark after the :
1097 foreach my $j ( 0 .. $maximum_field_index - 1 ) {
1098 my $tok = $rtokens_old->[$j];
1099 if ( $tok =~ /^\?(\d+)$/ ) {
1100 $depth_question = $1;
1102 # depth must be correct
1103 next unless ( $depth_question eq $group_level );
1106 if ( $rfields_old->[ $j + 1 ] =~ /^(\?\s*)/ ) {
1107 $pad = " " x length($1);
1110 return; # shouldn't happen
1115 return unless ( defined($jquestion) ); # shouldn't happen
1117 # Now splice the tokens and patterns of the previous line
1118 # into the else line to insure a match. Add empty fields
1120 my $jadd = $jquestion;
1122 # Work on copies of the actual arrays in case we have
1123 # to return due to an error
1124 my @fields = @{$rfields};
1125 my @patterns = @{$rpatterns};
1126 my @tokens = @{$rtokens};
1128 VALIGN_DEBUG_FLAG_TERNARY && do {
1130 print STDOUT "CURRENT FIELDS=<@{$rfields_old}>\n";
1131 print STDOUT "CURRENT TOKENS=<@{$rtokens_old}>\n";
1132 print STDOUT "CURRENT PATTERNS=<@{$rpatterns_old}>\n";
1133 print STDOUT "UNMODIFIED FIELDS=<@{$rfields}>\n";
1134 print STDOUT "UNMODIFIED TOKENS=<@{$rtokens}>\n";
1135 print STDOUT "UNMODIFIED PATTERNS=<@{$rpatterns}>\n";
1138 # handle cases of leading colon on this line
1139 if ( $fields[0] =~ /^(:\s*)(.*)$/ ) {
1141 my ( $colon, $therest ) = ( $1, $2 );
1143 # Handle sub-case of first field with leading colon plus additional code
1144 # This is the usual situation as at the '1' below:
1150 # Split the first field after the leading colon and insert padding.
1151 # Note that this padding will remain even if the terminal value goes
1152 # out on a separate line. This does not seem to look to bad, so no
1153 # mechanism has been included to undo it.
1154 my $field1 = shift @fields;
1155 unshift @fields, ( $colon, $pad . $therest );
1157 # change the leading pattern from : to ?
1158 return unless ( $patterns[0] =~ s/^\:/?/ );
1160 # install leading tokens and patterns of existing line
1161 unshift( @tokens, @{$rtokens_old}[ 0 .. $jquestion ] );
1162 unshift( @patterns, @{$rpatterns_old}[ 0 .. $jquestion ] );
1164 # insert appropriate number of empty fields
1165 splice( @fields, 1, 0, ('') x $jadd ) if $jadd;
1168 # handle sub-case of first field just equal to leading colon.
1169 # This can happen for example in the example below where
1170 # the leading '(' would create a new alignment token
1171 # : ( $name =~ /[]}]$/ ) ? ( $mname = $name )
1172 # : ( $mname = $name . '->' );
1175 return unless ( $jmax > 0 && $tokens[0] ne '#' ); # shouldn't happen
1177 # prepend a leading ? onto the second pattern
1178 $patterns[1] = "?b" . $patterns[1];
1180 # pad the second field
1181 $fields[1] = $pad . $fields[1];
1183 # install leading tokens and patterns of existing line, replacing
1184 # leading token and inserting appropriate number of empty fields
1185 splice( @tokens, 0, 1, @{$rtokens_old}[ 0 .. $jquestion ] );
1186 splice( @patterns, 1, 0, @{$rpatterns_old}[ 1 .. $jquestion ] );
1187 splice( @fields, 1, 0, ('') x $jadd ) if $jadd;
1191 # Handle case of no leading colon on this line. This will
1192 # be the case when -wba=':' is used. For example,
1197 # install leading tokens and patterns of existing line
1198 $patterns[0] = '?' . 'b' . $patterns[0];
1199 unshift( @tokens, @{$rtokens_old}[ 0 .. $jquestion ] );
1200 unshift( @patterns, @{$rpatterns_old}[ 0 .. $jquestion ] );
1202 # insert appropriate number of empty fields
1203 $jadd = $jquestion + 1;
1204 $fields[0] = $pad . $fields[0];
1205 splice( @fields, 0, 0, ('') x $jadd ) if $jadd;
1208 VALIGN_DEBUG_FLAG_TERNARY && do {
1210 print STDOUT "MODIFIED TOKENS=<@tokens>\n";
1211 print STDOUT "MODIFIED PATTERNS=<@patterns>\n";
1212 print STDOUT "MODIFIED FIELDS=<@fields>\n";
1215 # all ok .. update the arrays
1216 @{$rfields} = @fields;
1217 @{$rtokens} = @tokens;
1218 @{$rpatterns} = @patterns;
1220 # force a flush after this line
1224 sub fix_terminal_else {
1226 # Add empty fields as necessary to align a balanced terminal
1227 # else block to a previous if/elsif/unless block,
1230 # if ( 1 || $x ) { print "ok 13\n"; }
1231 # else { print "not ok 13\n"; }
1233 # returns 1 if the else block should be indented
1235 my ( $rfields, $rtokens, $rpatterns ) = @_;
1236 my $jmax = @{$rfields} - 1;
1237 return unless ( $jmax > 0 );
1239 # check for balanced else block following if/elsif/unless
1240 my $rfields_old = $current_line->get_rfields();
1242 # TBD: add handling for 'case'
1243 return unless ( $rfields_old->[0] =~ /^(if|elsif|unless)\s*$/ );
1245 # look for the opening brace after the else, and extract the depth
1246 my $tok_brace = $rtokens->[0];
1248 if ( $tok_brace =~ /^\{(\d+)/ ) { $depth_brace = $1; }
1250 # probably: "else # side_comment"
1253 my $rpatterns_old = $current_line->get_rpatterns();
1254 my $rtokens_old = $current_line->get_rtokens();
1255 my $maximum_field_index = $current_line->get_jmax();
1257 # be sure the previous if/elsif is followed by an opening paren
1259 my $tok_paren = '(' . $depth_brace;
1260 my $tok_test = $rtokens_old->[$jparen];
1261 return unless ( $tok_test eq $tok_paren ); # shouldn't happen
1263 # Now find the opening block brace
1265 foreach my $j ( 1 .. $maximum_field_index - 1 ) {
1266 my $tok = $rtokens_old->[$j];
1267 if ( $tok eq $tok_brace ) {
1272 return unless ( defined($jbrace) ); # shouldn't happen
1274 # Now splice the tokens and patterns of the previous line
1275 # into the else line to insure a match. Add empty fields
1277 my $jadd = $jbrace - $jparen;
1278 splice( @{$rtokens}, 0, 0, @{$rtokens_old}[ $jparen .. $jbrace - 1 ] );
1279 splice( @{$rpatterns}, 1, 0, @{$rpatterns_old}[ $jparen + 1 .. $jbrace ] );
1280 splice( @{$rfields}, 1, 0, ('') x $jadd );
1282 # force a flush after this line if it does not follow a case
1283 if ( $rfields_old->[0] =~ /^case\s*$/ ) { return }
1284 else { return $jbrace }
1288 my %is_good_alignment;
1292 # Vertically aligning on certain "good" tokens is usually okay
1293 # so we can be less restrictive in marginal cases.
1294 my @q = qw( { ? => = );
1296 @is_good_alignment{@q} = (1) x scalar(@q);
1301 # See if the current line matches the current vertical alignment group.
1302 # If not, flush the current group.
1303 my ( $new_line, $old_line ) = @_;
1305 # uses global variables:
1306 # $previous_minimum_jmax_seen
1307 # $maximum_jmax_seen
1308 # $maximum_line_index
1310 my $jmax = $new_line->get_jmax();
1311 my $maximum_field_index = $old_line->get_jmax();
1313 # flush if this line has too many fields
1314 # variable $GoToLoc indicates goto branch point, for debugging
1316 if ( $jmax > $maximum_field_index ) { goto NO_MATCH }
1318 # flush if adding this line would make a non-monotonic field count
1320 ( $maximum_field_index > $jmax ) # this has too few fields
1322 ( $previous_minimum_jmax_seen <
1323 $jmax ) # and wouldn't be monotonic
1324 || ( $old_line->get_jmax_original_line() != $maximum_jmax_seen )
1332 # otherwise see if this line matches the current group
1333 my $jmax_original_line = $new_line->get_jmax_original_line();
1334 my $is_hanging_side_comment = $new_line->get_is_hanging_side_comment();
1335 my $rtokens = $new_line->get_rtokens();
1336 my $rfields = $new_line->get_rfields();
1337 my $rpatterns = $new_line->get_rpatterns();
1338 my $list_type = $new_line->get_list_type();
1340 my $group_list_type = $old_line->get_list_type();
1341 my $old_rpatterns = $old_line->get_rpatterns();
1342 my $old_rtokens = $old_line->get_rtokens();
1344 my $jlimit = $jmax - 1;
1345 if ( $maximum_field_index > $jmax ) {
1346 $jlimit = $jmax_original_line;
1347 --$jlimit unless ( length( $new_line->get_rfields()->[$jmax] ) );
1350 # handle comma-separated lists ..
1351 if ( $group_list_type && ( $list_type eq $group_list_type ) ) {
1352 for my $j ( 0 .. $jlimit ) {
1353 my $old_tok = $old_rtokens->[$j];
1354 next unless $old_tok;
1355 my $new_tok = $rtokens->[$j];
1356 next unless $new_tok;
1358 # lists always match ...
1359 # unless they would align any '=>'s with ','s
1362 if ( $old_tok =~ /^=>/ && $new_tok =~ /^,/
1363 || $new_tok =~ /^=>/ && $old_tok =~ /^,/ );
1367 # do detailed check for everything else except hanging side comments
1368 elsif ( !$is_hanging_side_comment ) {
1370 my $leading_space_count = $new_line->get_leading_space_count();
1374 my $saw_good_alignment;
1376 for my $j ( 0 .. $jlimit ) {
1378 my $old_tok = $old_rtokens->[$j];
1379 my $new_tok = $rtokens->[$j];
1381 # Note on encoding used for alignment tokens:
1382 # -------------------------------------------
1383 # Tokens are "decorated" with information which can help
1384 # prevent unwanted alignments. Consider for example the
1385 # following two lines:
1386 # local ( $xn, $xd ) = split( '/', &'rnorm(@_) );
1387 # local ( $i, $f ) = &'bdiv( $xn, $xd );
1388 # There are three alignment tokens in each line, a comma,
1389 # an =, and a comma. In the first line these three tokens
1391 # ,4+local-18 =3 ,4+split-7
1392 # and in the second line they are encoded as
1393 # ,4+local-18 =3 ,4+&'bdiv-8
1394 # Tokens always at least have token name and nesting
1395 # depth. So in this example the ='s are at depth 3 and
1396 # the ,'s are at depth 4. This prevents aligning tokens
1397 # of different depths. Commas contain additional
1398 # information, as follows:
1399 # , {depth} + {container name} - {spaces to opening paren}
1400 # This allows us to reject matching the rightmost commas
1401 # in the above two lines, since they are for different
1402 # function calls. This encoding is done in
1403 # 'sub send_lines_to_vertical_aligner'.
1405 # Pick off actual token.
1406 # Everything up to the first digit is the actual token.
1407 my $alignment_token = $new_tok;
1408 if ( $alignment_token =~ /^([^\d]+)/ ) { $alignment_token = $1 }
1410 # see if the decorated tokens match
1411 my $tokens_match = $new_tok eq $old_tok
1413 # Exception for matching terminal : of ternary statement..
1414 # consider containers prefixed by ? and : a match
1415 || ( $new_tok =~ /^,\d*\+\:/ && $old_tok =~ /^,\d*\+\?/ );
1417 # No match if the alignment tokens differ...
1418 if ( !$tokens_match ) {
1420 # ...Unless this is a side comment
1424 # and there is either at least one alignment token
1425 # or this is a single item following a list. This
1426 # latter rule is required for 'December' to join
1427 # the following list:
1429 # '', 'January', 'February', 'March',
1430 # 'April', 'May', 'June', 'July',
1431 # 'August', 'September', 'October', 'November',
1434 # If it doesn't then the -lp formatting will fail.
1435 && ( $j > 0 || $old_tok =~ /^,/ )
1439 if ( $marginal_match == 0
1440 && $maximum_line_index == 0 );
1448 # Calculate amount of padding required to fit this in.
1449 # $pad is the number of spaces by which we must increase
1450 # the current field to squeeze in this field.
1452 length( $rfields->[$j] ) - $old_line->current_field_width($j);
1453 if ( $j == 0 ) { $pad += $leading_space_count; }
1455 # remember max pads to limit marginal cases
1456 if ( $alignment_token ne '#' ) {
1457 if ( $pad > $max_pad ) { $max_pad = $pad }
1458 if ( $pad < $min_pad ) { $min_pad = $pad }
1460 if ( $is_good_alignment{$alignment_token} ) {
1461 $saw_good_alignment = 1;
1464 # If patterns don't match, we have to be careful...
1465 if ( $old_rpatterns->[$j] ne $rpatterns->[$j] ) {
1467 # flag this as a marginal match since patterns differ
1469 if ( $marginal_match == 0 && $maximum_line_index == 0 );
1471 # We have to be very careful about aligning commas
1472 # when the pattern's don't match, because it can be
1473 # worse to create an alignment where none is needed
1474 # than to omit one. Here's an example where the ','s
1475 # are not in named containers. The first line below
1476 # should not match the next two:
1477 # ( $a, $b ) = ( $b, $r );
1478 # ( $x1, $x2 ) = ( $x2 - $q * $x1, $x1 );
1479 # ( $y1, $y2 ) = ( $y2 - $q * $y1, $y1 );
1480 if ( $alignment_token eq ',' ) {
1482 # do not align commas unless they are in named containers
1484 goto NO_MATCH unless ( $new_tok =~ /[A-Za-z]/ );
1487 # do not align parens unless patterns match;
1488 # large ugly spaces can occur in math expressions.
1489 elsif ( $alignment_token eq '(' ) {
1491 # But we can allow a match if the parens don't
1492 # require any padding.
1494 if ( $pad != 0 ) { goto NO_MATCH }
1497 # Handle an '=' alignment with different patterns to
1499 elsif ( $alignment_token eq '=' ) {
1501 # It is best to be a little restrictive when
1502 # aligning '=' tokens. Here is an example of
1503 # two lines that we will not align:
1506 # The problem is that one is a 'my' declaration,
1507 # and the other isn't, so they're not very similar.
1508 # We will filter these out by comparing the first
1509 # letter of the pattern. This is crude, but works
1512 substr( $old_rpatterns->[$j], 0, 1 ) ne
1513 substr( $rpatterns->[$j], 0, 1 ) )
1519 # If we pass that test, we'll call it a marginal match.
1520 # Here is an example of a marginal match:
1522 # $op = compile_bblock($op);
1523 # The left tokens are both identifiers, but
1524 # one accesses a hash and the other doesn't.
1525 # We'll let this be a tentative match and undo
1526 # it later if we don't find more than 2 lines
1528 elsif ( $maximum_line_index == 0 ) {
1530 2; # =2 prevents being undone below
1535 # Don't let line with fewer fields increase column widths
1537 if ( $maximum_field_index > $jmax ) {
1539 # Exception: suspend this rule to allow last lines to join
1541 if ( $pad > 0 ) { goto NO_MATCH; }
1543 } ## end for my $j ( 0 .. $jlimit)
1545 # Turn off the "marginal match" flag in some cases...
1546 # A "marginal match" occurs when the alignment tokens agree
1547 # but there are differences in the other tokens (patterns).
1548 # If we leave the marginal match flag set, then the rule is that we
1549 # will align only if there are more than two lines in the group.
1550 # We will turn of the flag if we almost have a match
1551 # and either we have seen a good alignment token or we
1552 # just need a small pad (2 spaces) to fit. These rules are
1553 # the result of experimentation. Tokens which misaligned by just
1554 # one or two characters are annoying. On the other hand,
1555 # large gaps to less important alignment tokens are also annoying.
1556 if ( $marginal_match == 1
1557 && $jmax == $maximum_field_index
1558 && ( $saw_good_alignment || ( $max_pad < 3 && $min_pad > -3 ) )
1561 $marginal_match = 0;
1563 ##print "marginal=$marginal_match saw=$saw_good_alignment jmax=$jmax max=$maximum_field_index maxpad=$max_pad minpad=$min_pad\n";
1566 # We have a match (even if marginal).
1567 # If the current line has fewer fields than the current group
1568 # but otherwise matches, copy the remaining group fields to
1569 # make it a perfect match.
1570 if ( $maximum_field_index > $jmax ) {
1572 ##########################################################
1573 # FIXME: The previous version had a bug which made side comments
1574 # become regular fields, so for now the program does not allow a
1575 # line with side comment to match. This should eventually be done.
1576 # The best test file for experimenting is 'lista.t'
1577 ##########################################################
1579 my $comment = $rfields->[$jmax];
1581 goto NO_MATCH if ($comment);
1584 for my $jj ( $jlimit .. $maximum_field_index ) {
1585 $rtokens->[$jj] = $old_rtokens->[$jj];
1586 $rfields->[ $jj + 1 ] = '';
1587 $rpatterns->[ $jj + 1 ] = $old_rpatterns->[ $jj + 1 ];
1590 ## THESE DO NOT GIVE CORRECT RESULTS
1591 ## $rfields->[$jmax] = $comment;
1592 ## $new_line->set_jmax($jmax);
1599 # variable $GoToLoc is for debugging
1600 #print "no match from $GoToLoc\n";
1601 ##print "no match jmax=$jmax max=$maximum_field_index $group_list_type lines=$maximum_line_index token=$old_rtokens->[0]\n";
1603 # Make one last effort to retain a match of certain statements
1604 my $match = salvage_equality_matches( $new_line, $old_line );
1605 my_flush() unless ($match);
1610 sub salvage_equality_matches {
1611 my ( $new_line, $old_line ) = @_;
1613 # Reduce the complexity of the two lines if it will allow us to retain
1614 # alignment of some common alignments, including '=' and '=>'. We will
1615 # convert both lines to have just two matching tokens, the equality and the
1618 # return 0 or undef if unsuccessful
1619 # return 1 if successful
1621 # Here is a very simple example of two lines where we could at least
1623 # $x = $class->_sub( $x, $delta );
1624 # $xpownm1 = $class->_pow( $class->_copy($x), $nm1 ); # x(i)^(n-1)
1626 # We will only do this if there is one old line (and one new line)
1627 return unless ( $maximum_line_index == 0 );
1628 return if ($is_matching_terminal_line);
1630 # We are only looking for equality type statements
1631 my $old_rtokens = $old_line->get_rtokens();
1632 my $rtokens = $new_line->get_rtokens();
1634 ( $rtokens->[0] =~ /=/ && ( $old_rtokens->[0] eq $rtokens->[0] ) );
1635 return unless ($is_equals);
1637 # The leading patterns must match
1638 my $old_rpatterns = $old_line->get_rpatterns();
1639 my $rpatterns = $new_line->get_rpatterns();
1640 return if ( $old_rpatterns->[0] ne $rpatterns->[0] );
1642 # Both should have side comment fields (should always be true)
1643 my $jmax_old = $old_line->get_jmax();
1644 my $jmax_new = $new_line->get_jmax();
1645 my $end_tok_old = $old_rtokens->[ $jmax_old - 1 ];
1646 my $end_tok_new = $rtokens->[ $jmax_new - 1 ];
1647 my $have_side_comments =
1648 defined($end_tok_old)
1649 && $end_tok_old eq '#'
1650 && defined($end_tok_new)
1651 && $end_tok_new eq '#';
1652 if ( !$have_side_comments ) { return; }
1654 # Do not match if any remaining tokens in new line include '?', 'if',
1655 # 'unless','||', '&&'. The reason is that (1) this isn't a great match, and
1656 # (2) we will prevent possibly better matchs to follow. Here is an
1657 # example. The match of the first two lines is rejected, and this allows
1658 # the second and third lines to match.
1659 # my $type = shift || "o";
1660 # my $fname = ( $type eq 'oo' ? 'orte_city' : 'orte' );
1661 # my $suffix = ( $coord_system eq 'standard' ? '' : '-orig' );
1662 # This logic can cause some unwanted losses of alignments, but it can retain
1663 # long runs of multiple-token alignments, so overall it is worthwhile.
1664 # If we had a peek at the subsequent line we could make a much better
1665 # decision here, but for now this is not available.
1666 for ( my $j = 1 ; $j < $jmax_new - 1 ; $j++ ) {
1667 my $new_tok = $rtokens->[$j];
1668 my $is_good_alignment = ( $new_tok =~ /^(=|\?|if|unless|\|\||\&\&)/ );
1669 return if ($is_good_alignment);
1672 my $squeeze_line = sub {
1673 my ($line_obj) = @_;
1675 # reduce a line down to the three fields surrounding
1676 # the two tokens, an '=' of some sort and a '#' at the end
1678 my $jmax = $line_obj->get_jmax();
1680 return unless $jmax > $jmax_new;
1681 my $rfields = $line_obj->get_rfields();
1682 my $rpatterns = $line_obj->get_rpatterns();
1683 my $rtokens = $line_obj->get_rtokens();
1685 $rfields->[0], join( '', @{$rfields}[ 1 .. $jmax - 1 ] ),
1688 my $rpatterns_new = [
1689 $rpatterns->[0], join( '', @{$rpatterns}[ 1 .. $jmax - 1 ] ),
1692 my $rtokens_new = [ $rtokens->[0], $rtokens->[ $jmax - 1 ] ];
1693 $line_obj->{_rfields} = $rfields_new;
1694 $line_obj->{_rpatterns} = $rpatterns_new;
1695 $line_obj->{_rtokens} = $rtokens_new;
1696 $line_obj->set_jmax($jmax_new);
1699 # Okay, we will force a match at the equals-like token. We will fix both
1700 # lines to have just 2 tokens and 3 fields:
1701 $squeeze_line->($new_line);
1702 $squeeze_line->($old_line);
1704 # start over with a new group
1705 initialize_for_new_group();
1706 add_to_group($old_line);
1707 $current_line = $old_line;
1713 my ( $new_line, $old_line ) = @_;
1714 return unless ( $maximum_line_index >= 0 );
1716 my $jmax = $new_line->get_jmax();
1717 my $leading_space_count = $new_line->get_leading_space_count();
1718 my $is_hanging_side_comment = $new_line->get_is_hanging_side_comment();
1719 my $rtokens = $new_line->get_rtokens();
1720 my $rfields = $new_line->get_rfields();
1721 my $rpatterns = $new_line->get_rpatterns();
1723 my $group_list_type = $group_lines[0]->get_list_type();
1725 my $padding_so_far = 0;
1726 my $padding_available = $old_line->get_available_space_on_right();
1728 # save current columns in case this doesn't work
1729 save_alignment_columns();
1731 my $maximum_field_index = $old_line->get_jmax();
1732 for my $j ( 0 .. $jmax ) {
1734 my $pad = length( $rfields->[$j] ) - $old_line->current_field_width($j);
1737 $pad += $leading_space_count;
1740 # remember largest gap of the group, excluding gap to side comment
1742 && $group_maximum_gap < -$pad
1746 $group_maximum_gap = -$pad;
1752 ## This patch helps sometimes, but it doesn't check to see if
1753 ## the line is too long even without the side comment. It needs
1755 ##don't let a long token with no trailing side comment push
1756 ##side comments out, or end a group. (sidecmt1.t)
1757 ##next if ($j==$jmax-1 && length($rfields->[$jmax])==0);
1759 # BEGIN PATCH for keith1.txt.
1760 # If the group began matching multiple tokens but later this got
1761 # reduced to a fewer number of matching tokens, then the fields
1762 # of the later lines will still have to fit into their corresponding
1763 # fields. So a large later field will "push" the other fields to
1764 # the right, including previous side comments, and if there is no room
1765 # then there is no match.
1766 # For example, look at the last line in the following snippet:
1768 # my $b_prod_db = ( $ENV{ORACLE_SID} =~ m/p$/ && !$testing ) ? true : false;
1769 # my $env = ($b_prod_db) ? "prd" : "val";
1770 # my $plant = ( $OPT{p} ) ? $OPT{p} : "STL";
1771 # my $task = $OPT{t};
1772 # my $fnam = "longggggggggggggggg.$record_created.$env.$plant.idash";
1774 # The long term will push the '?' to the right to fit in, and in this
1775 # case there is not enough room so it will not match the equals unless
1776 # we do something special.
1778 # Usually it looks good to keep an initial alignment of '=' going, and
1779 # we can do this if the long term can fit in the space taken up by the
1780 # remaining fields (the ? : fields here).
1782 # Allowing any matching token for now, but it could be restricted
1783 # to an '='-like token if necessary.
1786 $pad > $padding_available
1787 && $jmax == 2 # matching one thing (plus #)
1788 && $j == $jmax - 1 # at last field
1789 && $maximum_line_index > 0 # more than 1 line in group now
1790 && $jmax < $maximum_field_index # other lines have more fields
1791 && length( $rfields->[$jmax] ) == 0 # no side comment
1793 # Uncomment to match only equals (but this does not seem necessary)
1794 # && $rtokens->[0] =~ /^=\d/ # matching an equals
1797 my $extra_padding = 0;
1798 foreach my $jj ( $j + 1 .. $maximum_field_index - 1 ) {
1799 $extra_padding += $old_line->current_field_width($jj);
1802 next if ( $pad <= $padding_available + $extra_padding );
1805 # END PATCH for keith1.pl
1807 # This line will need space; lets see if we want to accept it..
1810 # not if this won't fit
1811 ( $pad > $padding_available )
1813 # previously, there were upper bounds placed on padding here
1814 # (maximum_whitespace_columns), but they were not really helpful
1819 # revert to starting state then flush; things didn't work out
1820 restore_alignment_columns();
1825 # patch to avoid excessive gaps in previous lines,
1826 # due to a line of fewer fields.
1828 # $self->{"dfi"}, $self->{"aa"}, $self->rsvd, $self->{"rd"},
1829 # $self->{"area"}, $self->{"id"}, $self->{"sel"} );
1830 next if ( $jmax < $maximum_field_index && $j == $jmax - 1 );
1832 # looks ok, squeeze this field in
1833 $old_line->increase_field_width( $j, $pad );
1834 $padding_available -= $pad;
1836 # remember largest gap of the group, excluding gap to side comment
1837 if ( $pad > $group_maximum_gap && $j > 0 && $j < $jmax - 1 ) {
1838 $group_maximum_gap = $pad;
1846 # The current line either starts a new alignment group or is
1847 # accepted into the current alignment group.
1848 my $new_line = shift;
1849 $group_lines[ ++$maximum_line_index ] = $new_line;
1851 # initialize field lengths if starting new group
1852 if ( $maximum_line_index == 0 ) {
1854 my $jmax = $new_line->get_jmax();
1855 my $rfields = $new_line->get_rfields();
1856 my $rtokens = $new_line->get_rtokens();
1857 my $col = $new_line->get_leading_space_count();
1859 for my $j ( 0 .. $jmax ) {
1860 $col += length( $rfields->[$j] );
1862 # create initial alignments for the new group
1864 if ( $j < $jmax ) { $token = $rtokens->[$j] }
1865 my $alignment = make_alignment( $col, $token );
1866 $new_line->set_alignment( $j, $alignment );
1869 $maximum_jmax_seen = $jmax;
1870 $minimum_jmax_seen = $jmax;
1873 # use previous alignments otherwise
1875 my @new_alignments =
1876 $group_lines[ $maximum_line_index - 1 ]->get_alignments();
1877 $new_line->set_alignments(@new_alignments);
1880 # remember group jmax extremes for next call to valign_input
1881 $previous_minimum_jmax_seen = $minimum_jmax_seen;
1882 $previous_maximum_jmax_seen = $maximum_jmax_seen;
1888 # debug routine to dump array contents
1890 print STDOUT "(@_)\n";
1894 # flush() sends the current Perl::Tidy::VerticalAligner group down the
1895 # pipeline to Perl::Tidy::FileWriter.
1897 # This is the external flush, which also empties the buffer and cache
1900 # the buffer must be emptied first, then any cached text
1901 dump_valign_buffer();
1903 if ( $maximum_line_index < 0 ) {
1904 if ($cached_line_type) {
1905 $seqno_string = $cached_seqno_string;
1906 valign_output_step_C( $cached_line_text,
1907 $cached_line_leading_space_count,
1908 $last_level_written );
1909 $cached_line_type = 0;
1910 $cached_line_text = "";
1911 $cached_seqno_string = "";
1920 sub reduce_valign_buffer_indentation {
1923 if ( $valign_buffer_filling && $diff ) {
1924 my $max_valign_buffer = @valign_buffer;
1925 foreach my $i ( 0 .. $max_valign_buffer - 1 ) {
1926 my ( $line, $leading_space_count, $level ) =
1927 @{ $valign_buffer[$i] };
1928 my $ws = substr( $line, 0, $diff );
1929 if ( ( length($ws) == $diff ) && $ws =~ /^\s+$/ ) {
1930 $line = substr( $line, $diff );
1932 if ( $leading_space_count >= $diff ) {
1933 $leading_space_count -= $diff;
1934 $level = level_change( $leading_space_count, $diff, $level );
1936 $valign_buffer[$i] = [ $line, $leading_space_count, $level ];
1944 # compute decrease in level when we remove $diff spaces from the
1946 my ( $leading_space_count, $diff, $level ) = @_;
1947 if ($rOpts_indent_columns) {
1949 int( ( $leading_space_count + $diff ) / $rOpts_indent_columns );
1950 my $nlev = int( $leading_space_count / $rOpts_indent_columns );
1951 $level -= ( $olev - $nlev );
1952 if ( $level < 0 ) { $level = 0 }
1957 sub dump_valign_buffer {
1958 if (@valign_buffer) {
1959 foreach (@valign_buffer) {
1960 valign_output_step_D( @{$_} );
1962 @valign_buffer = ();
1964 $valign_buffer_filling = "";
1968 # This is the internal flush, which leaves the cache intact
1971 return if ( $maximum_line_index < 0 );
1973 # handle a group of comment lines
1974 if ( $group_type eq 'COMMENT' ) {
1976 VALIGN_DEBUG_FLAG_APPEND0 && do {
1977 my ( $a, $b, $c ) = caller();
1979 "APPEND0: Flush called from $a $b $c for COMMENT group: lines=$maximum_line_index \n";
1982 my $leading_space_count = $comment_leading_space_count;
1983 my $leading_string = get_leading_string($leading_space_count);
1985 # zero leading space count if any lines are too long
1987 for my $i ( 0 .. $maximum_line_index ) {
1988 my $str = $group_lines[$i];
1991 $leading_space_count -
1992 maximum_line_length_for_level($group_level);
1993 if ( $excess > $max_excess ) {
1994 $max_excess = $excess;
1998 if ( $max_excess > 0 ) {
1999 $leading_space_count -= $max_excess;
2000 if ( $leading_space_count < 0 ) { $leading_space_count = 0 }
2001 $last_outdented_line_at =
2002 $file_writer_object->get_output_line_number();
2003 unless ($outdented_line_count) {
2004 $first_outdented_line_at = $last_outdented_line_at;
2006 $outdented_line_count += ( $maximum_line_index + 1 );
2009 # write the group of lines
2010 my $outdent_long_lines = 0;
2011 for my $i ( 0 .. $maximum_line_index ) {
2012 valign_output_step_B( $leading_space_count, $group_lines[$i], 0,
2013 $outdent_long_lines, "", $group_level );
2017 # handle a group of code lines
2020 VALIGN_DEBUG_FLAG_APPEND0 && do {
2021 my $group_list_type = $group_lines[0]->get_list_type();
2022 my ( $a, $b, $c ) = caller();
2023 my $maximum_field_index = $group_lines[0]->get_jmax();
2025 "APPEND0: Flush called from $a $b $c fields=$maximum_field_index list=$group_list_type lines=$maximum_line_index extra=$extra_indent_ok\n";
2029 # some small groups are best left unaligned
2030 my $do_not_align = decide_if_aligned();
2032 # optimize side comment location
2033 $do_not_align = adjust_side_comment($do_not_align);
2035 # recover spaces for -lp option if possible
2036 my $extra_leading_spaces = get_extra_leading_spaces();
2038 # all lines of this group have the same basic leading spacing
2039 my $group_leader_length = $group_lines[0]->get_leading_space_count();
2041 # add extra leading spaces if helpful
2042 # NOTE: Use zero; this did not work well
2045 # loop to output all lines
2046 for my $i ( 0 .. $maximum_line_index ) {
2047 my $line = $group_lines[$i];
2048 valign_output_step_A( $line, $min_ci_gap, $do_not_align,
2049 $group_leader_length, $extra_leading_spaces );
2052 initialize_for_new_group();
2056 sub decide_if_aligned {
2058 # Do not try to align two lines which are not really similar
2059 return unless $maximum_line_index == 1;
2060 return if ($is_matching_terminal_line);
2062 my $group_list_type = $group_lines[0]->get_list_type();
2064 my $rtokens = $group_lines[0]->get_rtokens();
2065 my $leading_equals = ( $rtokens->[0] =~ /=/ );
2067 # A marginal match is a match which has different patterns. Normally, we
2068 # should not allow exactly two lines to match if marginal. But we will modify
2069 # this rule for two lines with a leading equals-like operator such that we
2070 # match if the patterns to the left of the equals are the same. So for
2071 # example the following two lines are a marginal match but have the same
2072 # left side patterns, so we will align the equals.
2073 # my $orig = my $format = "^<<<<< ~~\n";
2075 # But these have a different left pattern so they will not be aligned
2077 # $self->{'leftovers'} .= "<bx-seq:seq" . $';
2078 my $is_marginal = $marginal_match;
2079 if ( $leading_equals && $is_marginal ) {
2080 my $rpatterns0 = $group_lines[0]->get_rpatterns();
2081 my $rpatterns1 = $group_lines[1]->get_rpatterns();
2082 my $pat0 = $rpatterns0->[0];
2083 my $pat1 = $rpatterns1->[0];
2084 $is_marginal = $pat0 ne $pat1;
2087 my $do_not_align = (
2089 # always align lists
2094 # don't align if it was just a marginal match
2095 $is_marginal ##$marginal_match
2097 # don't align two lines with big gap
2098 # NOTE: I am not sure if this test is actually functional any longer
2099 || $group_maximum_gap > 12
2101 # or lines with differing number of alignment tokens
2102 || ( $previous_maximum_jmax_seen != $previous_minimum_jmax_seen
2103 && !$leading_equals )
2107 # But try to convert them into a simple comment group if the first line
2108 # a has side comment
2109 my $rfields = $group_lines[0]->get_rfields();
2110 my $maximum_field_index = $group_lines[0]->get_jmax();
2112 && ( $maximum_line_index > 0 )
2113 && ( length( $rfields->[$maximum_field_index] ) > 0 ) )
2118 return $do_not_align;
2121 sub adjust_side_comment {
2123 my $do_not_align = shift;
2125 # let's see if we can move the side comment field out a little
2126 # to improve readability (the last field is always a side comment field)
2127 my $have_side_comment = 0;
2128 my $first_side_comment_line = -1;
2129 my $maximum_field_index = $group_lines[0]->get_jmax();
2130 for my $i ( 0 .. $maximum_line_index ) {
2131 my $line = $group_lines[$i];
2133 if ( length( $line->get_rfields()->[$maximum_field_index] ) ) {
2134 $have_side_comment = 1;
2135 $first_side_comment_line = $i;
2140 my $kmax = $maximum_field_index + 1;
2142 if ($have_side_comment) {
2144 my $line = $group_lines[0];
2146 # the maximum space without exceeding the line length:
2147 my $avail = $line->get_available_space_on_right();
2149 # try to use the previous comment column
2150 my $side_comment_column = $line->get_column( $kmax - 2 );
2151 my $move = $last_comment_column - $side_comment_column;
2153 ## my $sc_line0 = $side_comment_history[0]->[0];
2154 ## my $sc_col0 = $side_comment_history[0]->[1];
2155 ## my $sc_line1 = $side_comment_history[1]->[0];
2156 ## my $sc_col1 = $side_comment_history[1]->[1];
2157 ## my $sc_line2 = $side_comment_history[2]->[0];
2158 ## my $sc_col2 = $side_comment_history[2]->[1];
2160 ## # FUTURE UPDATES:
2161 ## # Be sure to ignore 'do not align' and '} # end comments'
2162 ## # Find first $move > 0 and $move <= $avail as follows:
2163 ## # 1. try sc_col1 if sc_col1 == sc_col0 && (line-sc_line0) < 12
2164 ## # 2. try sc_col2 if (line-sc_line2) < 12
2165 ## # 3. try min possible space, plus up to 8,
2166 ## # 4. try min possible space
2168 if ( $kmax > 0 && !$do_not_align ) {
2170 # but if this doesn't work, give up and use the minimum space
2171 if ( $move > $avail ) {
2172 $move = $rOpts_minimum_space_to_comment - 1;
2175 # but we want some minimum space to the comment
2176 my $min_move = $rOpts_minimum_space_to_comment - 1;
2178 && $last_side_comment_length > 0
2179 && ( $first_side_comment_line == 0 )
2180 && $group_level == $last_level_written )
2185 if ( $move < $min_move ) {
2189 # previously, an upper bound was placed on $move here,
2190 # (maximum_space_to_comment), but it was not helpful
2192 # don't exceed the available space
2193 if ( $move > $avail ) { $move = $avail }
2195 # we can only increase space, never decrease
2197 $line->increase_field_width( $maximum_field_index - 1, $move );
2200 # remember this column for the next group
2201 $last_comment_column = $line->get_column( $kmax - 2 );
2205 # try to at least line up the existing side comment location
2206 if ( $kmax > 0 && $move > 0 && $move < $avail ) {
2207 $line->increase_field_width( $maximum_field_index - 1, $move );
2211 # reset side comment column if we can't align
2213 forget_side_comment();
2217 return $do_not_align;
2220 sub valign_output_step_A {
2222 ###############################################################
2223 # This is Step A in writing vertically aligned lines.
2224 # The line is prepared according to the alignments which have
2225 # been found and shipped to the next step.
2226 ###############################################################
2228 my ( $line, $min_ci_gap, $do_not_align, $group_leader_length,
2229 $extra_leading_spaces )
2231 my $rfields = $line->get_rfields();
2232 my $leading_space_count = $line->get_leading_space_count();
2233 my $outdent_long_lines = $line->get_outdent_long_lines();
2234 my $maximum_field_index = $line->get_jmax();
2235 my $rvertical_tightness_flags = $line->get_rvertical_tightness_flags();
2237 # add any extra spaces
2238 if ( $leading_space_count > $group_leader_length ) {
2239 $leading_space_count += $min_ci_gap;
2242 my $str = $rfields->[0];
2244 # loop to concatenate all fields of this line and needed padding
2245 my $total_pad_count = 0;
2246 for my $j ( 1 .. $maximum_field_index ) {
2248 # skip zero-length side comments
2251 ( $j == $maximum_field_index )
2252 && ( !defined( $rfields->[$j] )
2253 || ( length( $rfields->[$j] ) == 0 ) )
2256 # compute spaces of padding before this field
2257 my $col = $line->get_column( $j - 1 );
2258 my $pad = $col - ( length($str) + $leading_space_count );
2260 if ($do_not_align) {
2262 ( $j < $maximum_field_index )
2264 : $rOpts_minimum_space_to_comment - 1;
2267 # if the -fpsc flag is set, move the side comment to the selected
2268 # column if and only if it is possible, ignoring constraints on
2269 # line length and minimum space to comment
2270 if ( $rOpts_fixed_position_side_comment && $j == $maximum_field_index )
2272 my $newpad = $pad + $rOpts_fixed_position_side_comment - $col - 1;
2273 if ( $newpad >= 0 ) { $pad = $newpad; }
2276 # accumulate the padding
2277 if ( $pad > 0 ) { $total_pad_count += $pad; }
2280 if ( !defined $rfields->[$j] ) {
2281 write_diagnostics("UNDEFined field at j=$j\n");
2284 # only add padding when we have a finite field;
2285 # this avoids extra terminal spaces if we have empty fields
2286 if ( length( $rfields->[$j] ) > 0 ) {
2287 $str .= ' ' x $total_pad_count;
2288 $total_pad_count = 0;
2289 $str .= $rfields->[$j];
2292 $total_pad_count = 0;
2295 # update side comment history buffer
2296 if ( $j == $maximum_field_index ) {
2297 my $lineno = $file_writer_object->get_output_line_number();
2298 shift @side_comment_history;
2299 push @side_comment_history, [ $lineno, $col ];
2303 my $side_comment_length = ( length( $rfields->[$maximum_field_index] ) );
2305 # ship this line off
2306 valign_output_step_B( $leading_space_count + $extra_leading_spaces,
2307 $str, $side_comment_length, $outdent_long_lines,
2308 $rvertical_tightness_flags, $group_level );
2312 sub get_extra_leading_spaces {
2314 #----------------------------------------------------------
2315 # Define any extra indentation space (for the -lp option).
2317 # If a list has side comments, sub scan_list must dump the
2318 # list before it sees everything. When this happens, it sets
2319 # the indentation to the standard scheme, but notes how
2320 # many spaces it would have liked to use. We may be able
2321 # to recover that space here in the event that all of the
2322 # lines of a list are back together again.
2323 #----------------------------------------------------------
2325 my $extra_leading_spaces = 0;
2326 if ($extra_indent_ok) {
2327 my $object = $group_lines[0]->get_indentation();
2328 if ( ref($object) ) {
2329 my $extra_indentation_spaces_wanted =
2330 get_recoverable_spaces($object);
2332 # all indentation objects must be the same
2333 for my $i ( 1 .. $maximum_line_index ) {
2334 if ( $object != $group_lines[$i]->get_indentation() ) {
2335 $extra_indentation_spaces_wanted = 0;
2340 if ($extra_indentation_spaces_wanted) {
2342 # the maximum space without exceeding the line length:
2343 my $avail = $group_lines[0]->get_available_space_on_right();
2344 $extra_leading_spaces =
2345 ( $avail > $extra_indentation_spaces_wanted )
2346 ? $extra_indentation_spaces_wanted
2349 # update the indentation object because with -icp the terminal
2350 # ');' will use the same adjustment.
2351 $object->permanently_decrease_available_spaces(
2352 -$extra_leading_spaces );
2356 return $extra_leading_spaces;
2359 sub combine_fields {
2361 # combine all fields except for the comment field ( sidecmt.t )
2362 # Uses global variables:
2364 # $maximum_line_index
2365 my $maximum_field_index = $group_lines[0]->get_jmax();
2366 foreach my $j ( 0 .. $maximum_line_index ) {
2367 my $line = $group_lines[$j];
2368 my $rfields = $line->get_rfields();
2369 foreach ( 1 .. $maximum_field_index - 1 ) {
2370 $rfields->[0] .= $rfields->[$_];
2372 $rfields->[1] = $rfields->[$maximum_field_index];
2375 $line->set_column( 0, 0 );
2376 $line->set_column( 1, 0 );
2379 $maximum_field_index = 1;
2381 for my $j ( 0 .. $maximum_line_index ) {
2382 my $line = $group_lines[$j];
2383 my $rfields = $line->get_rfields();
2384 for my $k ( 0 .. $maximum_field_index ) {
2385 my $pad = length( $rfields->[$k] ) - $line->current_field_width($k);
2387 $pad += $group_lines[$j]->get_leading_space_count();
2390 if ( $pad > 0 ) { $line->increase_field_width( $k, $pad ) }
2397 sub get_output_line_number {
2399 # the output line number reported to a caller is the number of items
2400 # written plus the number of items in the buffer
2402 return 1 + $maximum_line_index +
2403 $file_writer_object->get_output_line_number();
2406 sub valign_output_step_B {
2408 ###############################################################
2409 # This is Step B in writing vertically aligned lines.
2410 # Vertical tightness is applied according to preset flags.
2411 # In particular this routine handles stacking of opening
2412 # and closing tokens.
2413 ###############################################################
2415 my ( $leading_space_count, $str, $side_comment_length, $outdent_long_lines,
2416 $rvertical_tightness_flags, $level )
2419 # handle outdenting of long lines:
2420 if ($outdent_long_lines) {
2423 $side_comment_length +
2424 $leading_space_count -
2425 maximum_line_length_for_level($level);
2426 if ( $excess > 0 ) {
2427 $leading_space_count = 0;
2428 $last_outdented_line_at =
2429 $file_writer_object->get_output_line_number();
2431 unless ($outdented_line_count) {
2432 $first_outdented_line_at = $last_outdented_line_at;
2434 $outdented_line_count++;
2438 # Make preliminary leading whitespace. It could get changed
2439 # later by entabbing, so we have to keep track of any changes
2440 # to the leading_space_count from here on.
2441 my $leading_string =
2442 $leading_space_count > 0 ? ( ' ' x $leading_space_count ) : "";
2444 # Unpack any recombination data; it was packed by
2445 # sub send_lines_to_vertical_aligner. Contents:
2447 # [0] type: 1=opening non-block 2=closing non-block
2448 # 3=opening block brace 4=closing block brace
2449 # [1] flag: if opening: 1=no multiple steps, 2=multiple steps ok
2450 # if closing: spaces of padding to use
2451 # [2] sequence number of container
2452 # [3] valid flag: do not append if this flag is false
2454 my ( $open_or_close, $tightness_flag, $seqno, $valid, $seqno_beg,
2456 if ($rvertical_tightness_flags) {
2458 $open_or_close, $tightness_flag, $seqno, $valid, $seqno_beg,
2460 ) = @{$rvertical_tightness_flags};
2463 $seqno_string = $seqno_end;
2465 # handle any cached line ..
2466 # either append this line to it or write it out
2467 if ( length($cached_line_text) ) {
2469 # Dump an invalid cached line
2470 if ( !$cached_line_valid ) {
2471 valign_output_step_C( $cached_line_text,
2472 $cached_line_leading_space_count,
2473 $last_level_written );
2476 # Handle cached line ending in OPENING tokens
2477 elsif ( $cached_line_type == 1 || $cached_line_type == 3 ) {
2479 my $gap = $leading_space_count - length($cached_line_text);
2481 # handle option of just one tight opening per line:
2482 if ( $cached_line_flag == 1 ) {
2483 if ( defined($open_or_close) && $open_or_close == 1 ) {
2488 if ( $gap >= 0 && defined($seqno_beg) ) {
2489 $leading_string = $cached_line_text . ' ' x $gap;
2490 $leading_space_count = $cached_line_leading_space_count;
2491 $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
2492 $level = $last_level_written;
2495 valign_output_step_C( $cached_line_text,
2496 $cached_line_leading_space_count,
2497 $last_level_written );
2501 # Handle cached line ending in CLOSING tokens
2503 my $test_line = $cached_line_text . ' ' x $cached_line_flag . $str;
2506 # The new line must start with container
2509 # The container combination must be okay..
2512 # okay to combine like types
2513 ( $open_or_close == $cached_line_type )
2515 # closing block brace may append to non-block
2516 || ( $cached_line_type == 2 && $open_or_close == 4 )
2518 # something like ');'
2519 || ( !$open_or_close && $cached_line_type == 2 )
2523 # The combined line must fit
2525 length($test_line) <=
2526 maximum_line_length_for_level($last_level_written) )
2530 $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
2532 # Patch to outdent closing tokens ending # in ');'
2533 # If we are joining a line like ');' to a previous stacked
2534 # set of closing tokens, then decide if we may outdent the
2535 # combined stack to the indentation of the ');'. Since we
2536 # should not normally outdent any of the other tokens more than
2537 # the indentation of the lines that contained them, we will
2538 # only do this if all of the corresponding opening
2539 # tokens were on the same line. This can happen with
2540 # -sot and -sct. For example, it is ok here:
2541 # __PACKAGE__->load_components( qw(
2546 # But, for example, we do not outdent in this example because
2547 # that would put the closing sub brace out farther than the
2548 # opening sub brace:
2550 # perltidy -sot -sct
2552 # '<Control-f>' => sub {
2554 # my $e = $c->XEvent;
2555 # itemsUnderArea $c;
2558 if ( $str =~ /^\);/ && $cached_line_text =~ /^[\)\}\]\s]*$/ ) {
2560 # The way to tell this is if the stacked sequence numbers
2561 # of this output line are the reverse of the stacked
2562 # sequence numbers of the previous non-blank line of
2563 # sequence numbers. So we can join if the previous
2564 # nonblank string of tokens is the mirror image. For
2565 # example if stack )}] is 13:8:6 then we are looking for a
2566 # leading stack like [{( which is 6:8:13 We only need to
2567 # check the two ends, because the intermediate tokens must
2568 # fall in order. Note on speed: having to split on colons
2569 # and eliminate multiple colons might appear to be slow,
2570 # but it's not an issue because we almost never come
2571 # through here. In a typical file we don't.
2572 $seqno_string =~ s/^:+//;
2573 $last_nonblank_seqno_string =~ s/^:+//;
2574 $seqno_string =~ s/:+/:/g;
2575 $last_nonblank_seqno_string =~ s/:+/:/g;
2577 # how many spaces can we outdent?
2579 $cached_line_leading_space_count - $leading_space_count;
2581 && length($seqno_string)
2582 && length($last_nonblank_seqno_string) ==
2583 length($seqno_string) )
2586 ( split /:/, $last_nonblank_seqno_string );
2587 my @seqno_now = ( split /:/, $seqno_string );
2588 if ( $seqno_now[-1] == $seqno_last[0]
2589 && $seqno_now[0] == $seqno_last[-1] )
2593 # for absolute safety, be sure we only remove
2595 my $ws = substr( $test_line, 0, $diff );
2596 if ( ( length($ws) == $diff ) && $ws =~ /^\s+$/ ) {
2598 $test_line = substr( $test_line, $diff );
2599 $cached_line_leading_space_count -= $diff;
2600 $last_level_written =
2602 $cached_line_leading_space_count,
2603 $diff, $last_level_written );
2604 reduce_valign_buffer_indentation($diff);
2607 # shouldn't happen, but not critical:
2609 ## ERROR transferring indentation here
2616 $leading_string = "";
2617 $leading_space_count = $cached_line_leading_space_count;
2618 $level = $last_level_written;
2621 valign_output_step_C( $cached_line_text,
2622 $cached_line_leading_space_count,
2623 $last_level_written );
2627 $cached_line_type = 0;
2628 $cached_line_text = "";
2630 # make the line to be written
2631 my $line = $leading_string . $str;
2633 # write or cache this line
2634 if ( !$open_or_close || $side_comment_length > 0 ) {
2635 valign_output_step_C( $line, $leading_space_count, $level );
2638 $cached_line_text = $line;
2639 $cached_line_type = $open_or_close;
2640 $cached_line_flag = $tightness_flag;
2641 $cached_seqno = $seqno;
2642 $cached_line_valid = $valid;
2643 $cached_line_leading_space_count = $leading_space_count;
2644 $cached_seqno_string = $seqno_string;
2647 $last_level_written = $level;
2648 $last_side_comment_length = $side_comment_length;
2649 $extra_indent_ok = 0;
2653 sub valign_output_step_C {
2655 ###############################################################
2656 # This is Step C in writing vertically aligned lines.
2657 # Lines are either stored in a buffer or passed along to the next step.
2658 # The reason for storing lines is that we may later want to reduce their
2659 # indentation when -sot and -sct are both used.
2660 ###############################################################
2663 # Dump any saved lines if we see a line with an unbalanced opening or
2665 dump_valign_buffer() if ( $seqno_string && $valign_buffer_filling );
2667 # Either store or write this line
2668 if ($valign_buffer_filling) {
2669 push @valign_buffer, [@args];
2672 valign_output_step_D(@args);
2675 # For lines starting or ending with opening or closing tokens..
2676 if ($seqno_string) {
2677 $last_nonblank_seqno_string = $seqno_string;
2679 # Start storing lines when we see a line with multiple stacked opening
2681 # patch for RT #94354, requested by Colin Williams
2682 if ( $seqno_string =~ /^\d+(\:+\d+)+$/ && $args[0] !~ /^[\}\)\]\:\?]/ )
2685 # This test is efficient but a little subtle: The first test says
2686 # that we have multiple sequence numbers and hence multiple opening
2687 # or closing tokens in this line. The second part of the test
2688 # rejects stacked closing and ternary tokens. So if we get here
2689 # then we should have stacked unbalanced opening tokens.
2691 # Here is a complex example:
2693 # Foo($Bar[0], { # (side comment)
2697 # The first line has sequence 6::4. It does not begin with
2698 # a closing token or ternary, so it passes the test and must be
2699 # stacked opening tokens.
2701 # The last line has sequence 4:6 but is a stack of closing tokens,
2702 # so it gets rejected.
2704 # Note that the sequence number of an opening token for a qw quote
2705 # is a negative number and will be rejected.
2706 # For example, for the following line:
2708 # $seqno_string='10:5:-1'. It would be okay to accept it but
2709 # I decided not to do this after testing.
2711 $valign_buffer_filling = $seqno_string;
2718 sub valign_output_step_D {
2720 ###############################################################
2721 # This is Step D in writing vertically aligned lines.
2722 # Write one vertically aligned line of code to the output object.
2723 ###############################################################
2725 my ( $line, $leading_space_count, $level ) = @_;
2727 # The line is currently correct if there is no tabbing (recommended!)
2728 # We may have to lop off some leading spaces and replace with tabs.
2729 if ( $leading_space_count > 0 ) {
2731 # Nothing to do if no tabs
2732 if ( !( $rOpts_tabs || $rOpts_entab_leading_whitespace )
2733 || $rOpts_indent_columns <= 0 )
2739 # Handle entab option
2740 elsif ($rOpts_entab_leading_whitespace) {
2742 # Patch 12-nov-2018 based on report from Glenn. Extra padding was
2743 # not correctly entabbed, nor were side comments:
2744 # Increase leading space count for a padded line to get correct tabbing
2745 if ( $line =~ /^(\s+)(.*)$/ ) {
2746 my $spaces = length($1);
2747 if ( $spaces > $leading_space_count ) {
2748 $leading_space_count = $spaces;
2753 $leading_space_count % $rOpts_entab_leading_whitespace;
2755 int( $leading_space_count / $rOpts_entab_leading_whitespace );
2756 my $leading_string = "\t" x $tab_count . ' ' x $space_count;
2757 if ( $line =~ /^\s{$leading_space_count,$leading_space_count}/ ) {
2758 substr( $line, 0, $leading_space_count ) = $leading_string;
2762 # shouldn't happen - program error counting whitespace
2764 VALIGN_DEBUG_FLAG_TABS
2766 "Error entabbing in valign_output_step_D: expected count=$leading_space_count\n"
2771 # Handle option of one tab per level
2773 my $leading_string = ( "\t" x $level );
2775 $leading_space_count - $level * $rOpts_indent_columns;
2778 if ( $space_count < 0 ) {
2780 # But it could be an outdented comment
2781 if ( $line !~ /^\s*#/ ) {
2782 VALIGN_DEBUG_FLAG_TABS
2784 "Error entabbing in valign_output_step_D: for level=$group_level count=$leading_space_count\n"
2787 $leading_string = ( ' ' x $leading_space_count );
2790 $leading_string .= ( ' ' x $space_count );
2792 if ( $line =~ /^\s{$leading_space_count,$leading_space_count}/ ) {
2793 substr( $line, 0, $leading_space_count ) = $leading_string;
2797 # shouldn't happen - program error counting whitespace
2798 # we'll skip entabbing
2799 VALIGN_DEBUG_FLAG_TABS
2801 "Error entabbing in valign_output_step_D: expected count=$leading_space_count\n"
2806 $file_writer_object->write_code_line( $line . "\n" );
2810 { # begin get_leading_string
2812 my @leading_string_cache;
2814 sub get_leading_string {
2816 # define the leading whitespace string for this line..
2817 my $leading_whitespace_count = shift;
2819 # Handle case of zero whitespace, which includes multi-line quotes
2820 # (which may have a finite level; this prevents tab problems)
2821 if ( $leading_whitespace_count <= 0 ) {
2825 # look for previous result
2826 elsif ( $leading_string_cache[$leading_whitespace_count] ) {
2827 return $leading_string_cache[$leading_whitespace_count];
2830 # must compute a string for this number of spaces
2833 # Handle simple case of no tabs
2834 if ( !( $rOpts_tabs || $rOpts_entab_leading_whitespace )
2835 || $rOpts_indent_columns <= 0 )
2837 $leading_string = ( ' ' x $leading_whitespace_count );
2840 # Handle entab option
2841 elsif ($rOpts_entab_leading_whitespace) {
2843 $leading_whitespace_count % $rOpts_entab_leading_whitespace;
2844 my $tab_count = int(
2845 $leading_whitespace_count / $rOpts_entab_leading_whitespace );
2846 $leading_string = "\t" x $tab_count . ' ' x $space_count;
2849 # Handle option of one tab per level
2851 $leading_string = ( "\t" x $group_level );
2853 $leading_whitespace_count - $group_level * $rOpts_indent_columns;
2856 if ( $space_count < 0 ) {
2857 VALIGN_DEBUG_FLAG_TABS
2859 "Error in get_leading_string: for level=$group_level count=$leading_whitespace_count\n"
2863 $leading_string = ( ' ' x $leading_whitespace_count );
2866 $leading_string .= ( ' ' x $space_count );
2869 $leading_string_cache[$leading_whitespace_count] = $leading_string;
2870 return $leading_string;
2872 } # end get_leading_string
2874 sub report_anything_unusual {
2876 if ( $outdented_line_count > 0 ) {
2877 write_logfile_entry(
2878 "$outdented_line_count long lines were outdented:\n");
2879 write_logfile_entry(
2880 " First at output line $first_outdented_line_at\n");
2882 if ( $outdented_line_count > 1 ) {
2883 write_logfile_entry(
2884 " Last at output line $last_outdented_line_at\n");
2886 write_logfile_entry(
2887 " use -noll to prevent outdenting, -l=n to increase line length\n"
2889 write_logfile_entry("\n");