]> git.donarmstrong.com Git - perltidy.git/commitdiff
New upstream version 20221112 upstream/20221112
authorPhilip Hands <phil@hands.com>
Tue, 24 Jan 2023 15:18:38 +0000 (16:18 +0100)
committerPhilip Hands <phil@hands.com>
Tue, 24 Jan 2023 15:18:38 +0000 (16:18 +0100)
36 files changed:
CHANGES.md
MANIFEST
META.json
META.yml
bin/perltidy
docs/ChangeLog.html
docs/Tidy.html
docs/eos_flag.md
docs/perltidy.html
lib/Perl/Tidy.pm
lib/Perl/Tidy.pod
lib/Perl/Tidy/Debugger.pm
lib/Perl/Tidy/DevNull.pm
lib/Perl/Tidy/Diagnostics.pm
lib/Perl/Tidy/FileWriter.pm
lib/Perl/Tidy/Formatter.pm
lib/Perl/Tidy/HtmlWriter.pm
lib/Perl/Tidy/IOScalar.pm
lib/Perl/Tidy/IOScalarArray.pm
lib/Perl/Tidy/IndentationItem.pm
lib/Perl/Tidy/LineBuffer.pm
lib/Perl/Tidy/LineSink.pm
lib/Perl/Tidy/LineSource.pm
lib/Perl/Tidy/Logger.pm
lib/Perl/Tidy/Tokenizer.pm
lib/Perl/Tidy/VerticalAligner.pm
lib/Perl/Tidy/VerticalAligner/Alignment.pm
lib/Perl/Tidy/VerticalAligner/Line.pm
pm2pl
t/snippets15.t
t/snippets16.t
t/snippets26.t
t/snippets27.t [new file with mode: 0644]
t/test_DEBUG.t [new file with mode: 0755]
t/testwide-passthrough.t
t/testwide-tidy.t

index 2a8a11ee06d6ade482b9b915f636e5a9a3a671b5..55cf8e3b3a2964cdccd3fb537f3a0851dff45b59 100644 (file)
@@ -1,5 +1,139 @@
 # Perltidy Change Log
 
+## 2022 11 12
+
+    - Fix rt #145095, undef warning in Perl before 5.12. Version 20221112 is
+      identical to 2022111 except for this fix for older versions of Perl.
+
+    - No significant bugs have been found since the last release to CPAN.
+      Several minor issues have been fixed, and some new parameters have been
+      added, as follows:
+
+    - Fixed rare problem with irregular indentation involving --cuddled-else,
+      usually also with the combination -xci and -lp.  Reported in rt #144979.
+
+    - Add option --weld-fat-comma (-wfc) for issue git #108. When -wfc
+      is set, along with -wn, perltidy is allowed to weld an opening paren
+      to an inner opening container when they are separated by a hash key
+      and fat comma (=>).  For example:
+
+        # perltidy -wn
+        elf->call_method(
+            method_name_foo => {
+                some_arg1       => $foo,
+                some_other_arg3 => $bar->{'baz'},
+            }
+        );
+
+        # perltidy -wn -wfc
+        elf->call_method( method_name_foo => {
+            some_arg1       => $foo,
+            some_other_arg3 => $bar->{'baz'},
+        } );
+
+      This flag is off by default.
+
+    - Fix issue git #106. This fixes some edge cases of formatting with the
+      combination -xlp -pt=2, mainly for two-line lists with short function
+      names. One indentation space is removed to improve alignment:
+
+        # OLD: perltidy -xlp -pt=2
+        is($module->VERSION, $expected,
+            "$main_module->VERSION matches $module->VERSION ($expected)");
+
+        # NEW: perltidy -xlp -pt=2
+        is($module->VERSION, $expected,
+           "$main_module->VERSION matches $module->VERSION ($expected)");
+
+    - Fix for issue git #105, incorrect formatting with 5.36 experimental
+      for_list feature.
+
+    - Fix for issue git #103. For parameter -b, or --backup-and-modify-in-place,
+      the default backup method has been changed to preserve the inode value
+      of the file being formatted.  If this causes a problem, the previous
+      method is available and can be used by setting -backup-mode='move', or
+      -bm='move'.  The new default corresponds to -bm='copy'.  The difference
+      between the two methods is as follows.  For the older method,
+      -bm='move', the input file was moved to the backup, and a new file was
+      created for the formatted output.  This caused the inode to change.  For
+      the new default method, -bm='copy', the input is copied to the backup
+      and then the input file is reopened and rewritten. This preserves the
+      file inode.  Tests have not produced any problems with this change, but
+      before using the --backup-and-modify-in-place parameter please verify
+      that it works correctly in your environment and operating system. The
+      initial update for this had an error which was caught and fixed
+      in git #109.
+
+    - Fix undefined value message when perltidy -D is used (git #104)
+
+    - Fixed an inconsistency in html colors near pointers when -html is used.
+      Previously, a '->' at the end of a line got the 'punctuation color', black
+      by default but a '->' before an identifier got the color of the following
+      identifier. Now all pointers get the same color, which is black by default.
+      Also, previously a word following a '->' was given the color of a bareword,
+      black by default, but now it is given the color of an identifier.
+
+    - Fixed incorrect indentation of any function named 'err'.  This was
+      due to some old code from when "use feature 'err'" was valid.
+
+            # OLD:
+            my ($curr) = current();
+              err (@_);
+
+            # NEW:
+            my ($curr) = current();
+            err(@_);
+
+    - Added parameter --delete-repeated-commas (-drc) to delete repeated
+      commas. This is off by default. For example, given:
+
+            ignoreSpec( $file, "file",, \%spec, \%Rspec );
+
+      # perltidy -drc:
+            ignoreSpec( $file, "file", \%spec, \%Rspec );
+
+    - Add continuation indentation to long C-style 'for' terms; i.e.
+
+            # OLD
+            for (
+                $j = $i - $shell ;
+                $j >= 0
+                && ++$ncomp
+                && $array->[$j] gt $array->[ $j + $shell ] ;
+                $j -= $shell
+              )
+
+            # NEW
+            for (
+                $j = $i - $shell ;
+                $j >= 0
+                  && ++$ncomp
+                  && $array->[$j] gt $array->[ $j + $shell ] ;
+                $j -= $shell
+              )
+
+      This will change some existing formatting with very long 'for' terms.
+
+    - The following new parameters are available for manipulating
+      trailing commas of lists. They are described in the manual.
+
+           --want-trailing-commas=s, -wtc=s
+           --add-trailing-commas,    -atc
+           --delete-trailing-commas, -dtc
+           --delete-weld-interfering-commas, -dwic
+
+    - Files with errors due to missing, extra or misplaced parens, braces,
+      or square brackets are now written back out verbatim, without any
+      attempt at formatting.
+
+    - This version runs 10 to 15 percent faster than the previous
+      release on large files due to optimizations made with the help of
+      Devel::NYTProf.
+
+    - This version was stress-tested for over 200 cpu hours with random
+      input parameters. No failures to converge, internal fault checks,
+      undefined variable references or other irregularities were seen.
+
 ## 2022 06 13
 
     - No significant bugs have been found since the last release but users
     - Added vertical alignment for qw quotes and empty parens in 'use'
       statements (see issue #git 93).  This new alignment is 'on' by default
       and will change formatting as shown below. If this is not wanted it can
-      be turned off with the parameter -vxl='q' (--valign-exclude-list='q').
+      be turned off with the parameter -vxl='q' (--valign-exclusion-list='q').
 
         # old default, or -vxl='q'
         use Getopt::Long qw(GetOptions);
index ef716dff08ee9e9290090063088c54c7d8100f58..412eb70ef2fb5886f87ca67f34e634c71fbd5bfe 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -78,6 +78,7 @@ t/snippets23.t
 t/snippets24.t
 t/snippets25.t
 t/snippets26.t
+t/snippets27.t
 t/snippets3.t
 t/snippets4.t
 t/snippets5.t
@@ -87,6 +88,7 @@ t/snippets8.t
 t/snippets9.t
 t/test-eol.t
 t/test.t
+t/test_DEBUG.t
 t/testsa.t
 t/testss.t
 t/testwide-passthrough.pl.src
index 5ba01b46d129af3e8110309872ae62b8e71e4d95..dc9c70b74377a85ba3d6f2dabb94874ed0611fb6 100644 (file)
--- a/META.json
+++ b/META.json
@@ -44,6 +44,6 @@
          "web" : "https://github.com/perltidy/perltidy"
       }
    },
-   "version" : "20220613",
+   "version" : "20221112",
    "x_serialization_backend" : "JSON::PP version 4.04"
 }
index b5d8015797d80e2325d86d049b30d06d8d3bd617..b792a0a240478b8d02b8896d865a16bdee9b5e25 100644 (file)
--- a/META.yml
+++ b/META.yml
@@ -21,5 +21,5 @@ requires:
   perl: '5.008'
 resources:
   repository: https://github.com/perltidy/perltidy.git
-version: '20220613'
+version: '20221112'
 x_serialization_backend: 'CPAN::Meta::YAML version 0.012'
index 4a342acccddda85a365eaacea8b6bda1f3048068..ec86b27b9b4228d1da93ca91de4ca3c697cacd36 100755 (executable)
@@ -169,7 +169,7 @@ Option names may be terminated early as long as they are uniquely identified.
 For example, instead of B<--dump-token-types>, it would be sufficient to enter
 B<--dump-tok>, or even B<--dump-t>, to uniquely identify this command.
 
-=head2 I/O control
+=head2 I/O Control
 
 The following parameters concern the files which are read and written.
 
@@ -243,6 +243,11 @@ extension F<.bak>.  Any existing F<.bak> file will be deleted.  See next
 item for changing the default backup extension, and for eliminating the
 backup file altogether.
 
+B<Please Note>: Writing back to the input file increases the risk of data loss
+or corruption in the event of a software or hardware malfunction. Before using
+the B<-b> parameter please be sure to have backups and verify that it works
+correctly in your environment and operating system.
+
 A B<-b> flag will be ignored if input is from standard input or goes to
 standard output, or if the B<-html> flag is set.
 
@@ -278,6 +283,19 @@ Here are some examples:
   <-bext='/backup'>   F<.backup>         Delete if no errors
   <-bext='original/'> F<.original>       Delete if no errors
 
+=item B<-bm=s>,  B<--backup-method=s>
+
+This parameter should not normally be used but is available in the event that
+problems arise as a transition is made from an older implementation of the
+backup logic to a newer implementation.  The newer implementation is the
+default and is specified with B<-bm='copy'>. The older implementation is
+specified with B<-bm='move'>.  The difference is that the older implementation
+made the backup by moving the input file to the backup file, and the newer
+implementation makes the backup by copying the input file.  The newer
+implementation preserves the file system B<inode> value. This may avoid
+problems with other software running simultaneously.  This change was made
+as part of issue B<git #103> at github.
+
 =item B<-w>,    B<--warning-output>
 
 Setting B<-w> causes any non-critical warning
@@ -1175,7 +1193,7 @@ has been set to), if possible.  This is the default.  For example:
             fixit($i);
         }
 
-Use B<-nola> to not outdent labels.  To control line breaks after labels see L<"bal=n, --break-after-labels=n">.
+Use B<-nola> to not outdent labels.  To control line breaks after labels see L<"-bal=n, --break-after-labels=n">.
 
 =item B<Outdenting Keywords>
 
@@ -2247,12 +2265,29 @@ The default is equivalent to -cse='#>>V'.
 
 =head2 Line Break Control
 
-The parameters in this section control breaks after
+The parameters in this and the next sections control breaks after
 non-blank lines of code.  Blank lines are controlled
 separately by parameters in the section L<"Blank Line Control">.
 
 =over 4
 
+=item B<-dnl>,  B<--delete-old-newlines>
+
+By default, perltidy first deletes all old line break locations, and then it
+looks for good break points to match the desired line length.  Use B<-ndnl>
+or  B<--nodelete-old-newlines> to force perltidy to retain all old line break
+points.
+
+=item B<-anl>,  B<--add-newlines>
+
+By default, perltidy will add line breaks when necessary to create
+continuations of long lines and to improve the script appearance.  Use
+B<-nanl> or B<--noadd-newlines> to prevent any new line breaks.
+
+This flag does not prevent perltidy from eliminating existing line
+breaks; see B<--freeze-newlines> to completely prevent changes to line
+break points.
+
 =item B<-fnl>,  B<--freeze-newlines>
 
 If you do not want any changes to the line breaks within
@@ -2267,6 +2302,12 @@ Note: If you also want to keep your blank lines exactly
 as they are, you can use the B<-fbl> flag which is described
 in the section L<"Blank Line Control">.
 
+=back
+
+=head2 Controlling Breaks at Braces, Parens, and Square Brackets
+
+=over 4
+
 =item B<-ce>,   B<--cuddled-else>
 
 Enable the "cuddled else" style, in which C<else> and C<elsif> are
@@ -2360,7 +2401,7 @@ B<-ce>.
 When cuddled else formatting is selected with B<-ce>, setting this flag causes
 perltidy to ignore its built-in defaults and rely exclusively on the block types
 specified on the B<-cbl> flag described in the previous section.  For example,
-to avoid using cuddled B<catch> and B<finally>, which among in the defaults, the
+to avoid using cuddled B<catch> and B<finally>, which are among the defaults, the
 following set of parameters could be used:
 
   perltidy -ce -cbl='else elsif continue' -cblx
@@ -2702,6 +2743,12 @@ which is placed on a new line by that parameter.  The indentation is as follows:
   -bbpi=1 outdent by one continuation level
   -bbpi=2 indent one full indentation level
 
+=back
+
+=head2 Welding
+
+=over 4
+
 =item B<-wn>,  B<--weld-nested-containers>
 
 The B<-wn> flag causes closely nested pairs of opening and closing container
@@ -2731,7 +2778,8 @@ must either (1) be adjacent as in the above example, or (2) have an anonymous
 sub declaration following an outer opening container symbol which is not a
 code block brace, or (3) have an outer opening paren separated from the inner
 opening symbol by any single non-container symbol or something that looks like
-a function evaluation, as illustrated in the next examples.
+a function evaluation, as illustrated in the next examples. An additonal
+option (4) which can be turned on with the flag B<--weld-fat-comma> is when the opening container symbols are separated by a hash key and fat comma (=>).
 
 Any container symbol may serve as both the inner container of one pair and as
 the outer container of an adjacent pair. Consequently, any number of adjacent
@@ -2810,6 +2858,19 @@ specially in perltidy.
 Finally, the stacking of containers defined by this flag have priority over
 any other container stacking flags.  This is because any welding is done first.
 
+=item B<-wfc>,  B<--weld-fat-comma >
+
+When the B<-wfc> flag is set, along with B<-wn>, perltidy is allowed to weld
+an opening paren to an inner opening container when they are separated by a hash key and fat comma (=>). for example
+
+    # perltidy -wn -wfc
+    elf->call_method( method_name_foo => {
+        some_arg1       => $foo,
+        some_other_arg3 => $bar->{'baz'},
+    } );
+
+This option is off by default.
+
 =item B<-wnxl=s>,  B<--weld-nested-exclusion-list>
 
 The B<-wnxl=s> flag provides some control over the types of containers which
@@ -2906,6 +2967,7 @@ Here are some additional example strings and their meanings:
     '[ {'  - exclude all brackets and braces
     '[ ( ^K{' - exclude everything except nested structures like do {{  ... }}
 
+
 =item B<Vertical tightness> of non-block curly braces, parentheses, and square brackets.
 
 These parameters control what shall be called vertical tightness.  Here are the
@@ -3217,24 +3279,9 @@ unnecessary indentation within welded containers.  It is able to do this
 because it works on formatting globally rather than locally, as the B<-sot> and
 B<-sct> flags do.
 
-=item B<-dnl>,  B<--delete-old-newlines>
-
-By default, perltidy first deletes all old line break locations, and then it
-looks for good break points to match the desired line length.  Use B<-ndnl>
-or  B<--nodelete-old-newlines> to force perltidy to retain all old line break
-points.
-
-=item B<-anl>,  B<--add-newlines>
-
-By default, perltidy will add line breaks when necessary to create
-continuations of long lines and to improve the script appearance.  Use
-B<-nanl> or B<--noadd-newlines> to prevent any new line breaks.
-
-This flag does not prevent perltidy from eliminating existing line
-breaks; see B<--freeze-newlines> to completely prevent changes to line
-break points.
+=back
 
-=item B<Controlling whether perltidy breaks before or after operators>
+=head2 Breaking Before or After Operators
 
 Four command line parameters provide some control over whether
 a line break should be before or after specific token types.
@@ -3296,7 +3343,9 @@ with the B<-wba> and B<-wbb> flags.  For example, to break before all operators
 except an B<=> one could use --bbao -wba='=' rather than listing every
 single perl operator except B<=> on a -wbb flag.
 
-=item B<bal=n, --break-after-labels=n>
+=over 4
+
+=item B<-bal=n, --break-after-labels=n>
 
 This flag controls whether or not a line break occurs after a label. There
 are three possible values for B<n>:
@@ -3455,6 +3504,216 @@ Here is an example.
 
 =back
 
+=head2 Adding and Deleting Commas
+
+=over 4
+
+=item B<-drc>,  B<--delete-repeated-commas>
+
+Repeated commas in a list are undesirable and can be removed with this flag.
+For example, given this list with a repeated comma
+
+      ignoreSpec( $file, "file",, \%spec, \%Rspec );
+
+we can remove it with -drc
+
+      # perltidy -drc:
+      ignoreSpec( $file, "file", \%spec, \%Rspec );
+
+Since the default is not to add or delete commas, this feature is off by default and must be requested.
+
+
+=item B<--want-trailing-commas=s> or B<-wtc=s>, B<--add-trailing-commas> or B<-atc>, and B<--delete-trailing-commas> or B<-dtc>
+
+A trailing comma is a comma following the last item of a list. Perl allows
+trailing commas but they are not required.  By default, perltidy does not add
+or delete trailing commas, but it is possible to manipulate them with the
+following set of three related parameters:
+
+  --want-trailing-commas=s, -wtc=s - defines where trailing commas are wanted
+  --add-trailing-commas,    -atc   - gives permission to add trailing commas to match the style wanted
+  --delete-trailing-commas, -dtc   - gives permission to delete trailing commas which do not match the style wanted
+
+The parameter B<--want-trailing-commas=s>, or B<-wtc=s>, defines a preferred style.  The string B<s> indicates which lists should get trailing commas, as follows:
+
+  s=0 : no list should have a trailing comma
+  s=1 or * : every list should have a trailing comma
+  s=m a multi-line list should have a trailing commas
+  s=b trailing commas should be 'bare' (comma followed by newline)
+  s=h lists of key=>value pairs, with about one one '=>' and one ',' per line,
+      with a bare trailing comma
+  s=i lists with about one comma per line, with a bare trailing comma
+  s=' ' or -wtc not defined : leave trailing commas unchanged [DEFAULT].
+
+This parameter by itself only indicates the where trailing commas are
+wanted.  Perltidy only adds these trailing commas if the flag B<--add-trailing-commas>, or B<-atc> is set.  And perltidy only removes unwanted trailing commas
+if the flag B<--delete-trailing-commas>, or B<-dtc> is set.
+
+Here are some example parameter combinations and their meanings
+
+  -wtc=0 -dtc   : delete all trailing commas
+  -wtc=1 -atc   : all lists get trailing commas
+  -wtc=m -atc   : all multi-line lists get trailing commas, but
+                  single line lists remain unchanged.
+  -wtc=m -dtc   : multi-line lists remain unchanged, but
+                  any trailing commas on single line lists are removed.
+  -wtc=m -atc -dtc  : all multi-line lists get trailing commas, and
+                      any trailing commas on single line lists are removed.
+
+For example, given the following input without a trailing comma
+
+    bless {
+        B    => $B,
+        Root => $Root
+    } => $package;
+
+we can add a trailing comma after the variable C<$Root> using
+
+    # perltidy -wtc=m -atc
+    bless {
+        B    => $B,
+        Root => $Root,
+    } => $package;
+
+This could also be achieved in this case with B<-wtc=b> instead of B<-wtc=m>
+because the trailing comma here is bare (separated from its closing brace by a
+newline).  And it could also be achieved with B<-wtc=h> because this particular
+list is a list of key=>value pairs.
+
+The above styles should cover the main of situations of interest, but it is
+possible to apply a different style to each type of container token by
+including an opening token ahead of the style character in the above table.
+For example
+
+    -wtc='(m [b'
+
+means that lists within parens should have multi-line trailing commas, and that
+lists within square brackets have bare trailing commas. Since there is no
+specification for curly braces in this example, their trailing commas would
+remain unchanged.
+
+For parentheses, an additional item of information which can be given is an
+alphanumeric letter which is used to limit the selection further depending on
+the type of token immediately before the opening paren.  The possible letters
+are currently 'k', 'K', 'f', 'F', 'w', and 'W', with these meanings for
+matching whatever precedes an opening paren:
+
+ 'k' matches if the previous nonblank token is a perl built-in keyword (such as 'if', 'while'),
+ 'K' matches if 'k' does not, meaning that the previous token is not a keyword.
+ 'f' matches if the previous token is a function other than a keyword.
+ 'F' matches if 'f' does not.
+ 'w' matches if either 'k' or 'f' match.
+ 'W' matches if 'w' does not.
+
+These are the same codes used for B<--line-up-parentheses-inclusion-list>.
+For example,
+
+  -wtc = 'w(m'
+
+means that trailing commas are wanted for multi-line parenthesized lists following a function call or keyword.
+
+Here are some points to note regarding adding and deleting trailing commas:
+
+=over 4
+
+=item *
+
+For the implementation of these parameters, a B<list> is basically taken to be
+a container of items (parens, square brackets, or braces), which is not a code
+block, with one or more commas.  These parameters only apply to something that
+fits this definition of a list.
+
+Note that a paren-less list of parameters is not a list by this definition, so
+these parameters have no effect on a peren-less list.
+
+Another consequence is that if the only comma in a list is deleted, then it
+cannot later be added back with these parameters because the container no
+longer fits this definition of a list.  For example, given
+
+    my ( $self, ) = @_;
+
+and if we remove the comma with
+
+    # perltidy -wtc=m -dtc
+    my ( $self ) = @_;
+
+then we cannot use these trailing comma controls to add this comma back.
+
+=item *
+
+By B<multiline> list is meant a list for which the first comma and trailing comma
+are on different lines.
+
+=item *
+
+A B<bare> trailing comma is a comma which is at the end of a line. That is,
+the closing container token follows on a different line.  So a list with a
+bare trailing comma is a special case of a multi-line list.
+
+=item *
+
+The decision regarding whether or not a list is multi-line or bare is
+made based on the B<input> stream.  In some cases it may take an iteration
+or two to reach a final state.
+
+=item *
+
+When using these parameters for the first time it is a good idea to practice
+on some test scripts and verify that the results are as expected.
+
+=item *
+
+Since the default behavior is not to add or delete commas, these parameters
+can be useful on a temporary basis for reformatting a script.
+
+=back
+
+=item B<-dwic>,  B<--delete-weld-interfering-commas>
+
+If the closing tokens of two nested containers are separated by a comma, then
+welding requested with B<--weld-nested-containers> cannot occur.  Any commas in
+this situation are optional trailing commas and can be removed with B<-dwic>.
+For example, a comma in this scipt prevents welding:
+
+    # perltidy -wn
+    skip_symbols(
+        [ qw(
+            Perl_dump_fds
+            Perl_ErrorNo
+            Perl_GetVars
+            PL_sys_intern
+        ) ],
+    );
+
+Using B<-dwic> removes the comma and allows welding:
+
+    # perltidy -wn -dwic
+    skip_symbols( [ qw(
+        Perl_dump_fds
+        Perl_ErrorNo
+        Perl_GetVars
+        PL_sys_intern
+    ) ] );
+
+Since the default is not to add or delete commas, this feature is off by default.
+Here are some points to note about the B<-dwic> parameter
+
+=over 4
+
+=item *
+
+This operation is not reversible, so please check results of using this parameter carefully.
+
+=item *
+
+Removing this type of isolated trailing comma is necessary for welding to be
+possible, but not sufficient.  So welding will not always occur where these
+commas are removed.
+
+=back
+
+=back
+
 =head2 Retaining or Ignoring Existing Line Breaks
 
 Several additional parameters are available for controlling the extent
@@ -3504,7 +3763,7 @@ For example, given this snippet:
 By default, a method call arrow C<-E<gt>> is considered a candidate for
 a breakpoint, but method chains will fill to the line width before a break is
 considered.  With B<-bom>, breaks before the arrow are preserved, so if you
-have preformatted a method chain:
+have pre-formatted a method chain:
 
   my $q = $rs
     ->related_resultset('CDs')
@@ -5051,23 +5310,24 @@ dot is added, and the backup file will be F<somefile.pl~>  .
 The following list shows all short parameter names which allow a prefix
 'n' to produce the negated form:
 
- D      anl    asbl   asc    ast    asu    atnl   aws    b      baa
- baao   bar    bbao   bbb    bbc    bbs    bl     bli    boa    boc
- bok    bol    bom    bos    bot    cblx   ce     conv   cs     csc
- cscb   cscw   dac    dbc    dcbl   dcsc   ddf    dln    dnl    dop
- dp     dpro   dsc    dsm    dsn    dtt    dwls   dwrs   dws    eos
- f      fll    fpva   frm    fs     fso    gcs    hbc    hbcm   hbco
- hbh    hbhh   hbi    hbj    hbk    hbm    hbn    hbp    hbpd   hbpu
- hbq    hbs    hbsc   hbv    hbw    hent   hic    hicm   hico   hih
- hihh   hii    hij    hik    him    hin    hip    hipd   hipu   hiq
- his    hisc   hiv    hiw    hsc    html   ibc    icb    icp    iob
- isbc   iscl   kgb    kgbd   kgbi   kis    lal    log    lop    lp
- lsl    mem    nib    ohbr   okw    ola    olc    oll    olq    opr
- opt    osbc   osbr   otr    ple    pod    pvl    q      sac    sbc
- sbl    scbb   schb   scp    scsb   sct    se     sfp    sfs    skp
- sob    sobb   sohb   sop    sosb   sot    ssc    st     sts    t
- tac    tbc    toc    tp     tqw    trp    ts     tsc    tso    vbc
- vc     vmll   vsc    w      wn     x      xci    xlp    xs
+ D      anl    asbl   asc    ast    asu    atc    atnl   aws    b
+ baa    baao   bar    bbao   bbb    bbc    bbs    bl     bli    boa
+ boc    bok    bol    bom    bos    bot    cblx   ce     conv   cs
+ csc    cscb   cscw   dac    dbc    dcbl   dcsc   ddf    dln    dnl
+ dop    dp     dpro   drc    dsc    dsm    dsn    dtc    dtt    dwic
+ dwls   dwrs   dws    eos    f      fll    fpva   frm    fs     fso
+ gcs    hbc    hbcm   hbco   hbh    hbhh   hbi    hbj    hbk    hbm
+ hbn    hbp    hbpd   hbpu   hbq    hbs    hbsc   hbv    hbw    hent
+ hic    hicm   hico   hih    hihh   hii    hij    hik    him    hin
+ hip    hipd   hipu   hiq    his    hisc   hiv    hiw    hsc    html
+ ibc    icb    icp    iob    isbc   iscl   kgb    kgbd   kgbi   kis
+ lal    log    lop    lp     lsl    mem    nib    ohbr   okw    ola
+ olc    oll    olq    opr    opt    osbc   osbr   otr    ple    pod
+ pvl    q      sac    sbc    sbl    scbb   schb   scp    scsb   sct
+ se     sfp    sfs    skp    sob    sobb   sohb   sop    sosb   sot
+ ssc    st     sts    t      tac    tbc    toc    tp     tqw    trp
+ ts     tsc    tso    vbc    vc     vmll   vsc    w      wfc    wn
+ x      xci    xlp    xs
 
 Equivalently, the prefix 'no' or 'no-' on the corresponding long names may be
 used.
@@ -5166,7 +5426,7 @@ The perltidy binary uses the Perl::Tidy module and is installed when that module
 
 =head1 VERSION
 
-This man page documents perltidy version 20220613
+This man page documents perltidy version 20221112
 
 =head1 BUG REPORTS
 
index 90acabb91760022156edbdd57c14fa8b6f44d017..f64cf1d947ae96f6298272da1cbc58bba50e459c 100644 (file)
@@ -1,5 +1,140 @@
 <h1>Perltidy Change Log</h1>
 
+<h2>2022 11 12</h2>
+
+<pre><code>- Fix rt #145095, undef warning in Perl before 5.12. Version 20221112 is
+  identical to 2022111 except for this fix for older versions of Perl.
+
+- No significant bugs have been found since the last release to CPAN.
+  Several minor issues have been fixed, and some new parameters have been
+  added, as follows:
+
+- Fixed rare problem with irregular indentation involving --cuddled-else,
+  usually also with the combination -xci and -lp.  Reported in rt #144979.
+
+- Add option --weld-fat-comma (-wfc) for issue git #108. When -wfc
+  is set, along with -wn, perltidy is allowed to weld an opening paren
+  to an inner opening container when they are separated by a hash key
+  and fat comma (=&gt;).  For example:
+
+    # perltidy -wn
+    elf-&gt;call_method(
+        method_name_foo =&gt; {
+            some_arg1       =&gt; $foo,
+            some_other_arg3 =&gt; $bar-&gt;{'baz'},
+        }
+    );
+
+    # perltidy -wn -wfc
+    elf-&gt;call_method( method_name_foo =&gt; {
+        some_arg1       =&gt; $foo,
+        some_other_arg3 =&gt; $bar-&gt;{'baz'},
+    } );
+
+  This flag is off by default.
+
+- Fix issue git #106. This fixes some edge cases of formatting with the
+  combination -xlp -pt=2, mainly for two-line lists with short function
+  names. One indentation space is removed to improve alignment:
+
+    # OLD: perltidy -xlp -pt=2
+    is($module-&gt;VERSION, $expected,
+        "$main_module-&gt;VERSION matches $module-&gt;VERSION ($expected)");
+
+    # NEW: perltidy -xlp -pt=2
+    is($module-&gt;VERSION, $expected,
+       "$main_module-&gt;VERSION matches $module-&gt;VERSION ($expected)");
+
+- Fix for issue git #105, incorrect formatting with 5.36 experimental
+  for_list feature.
+
+- Fix for issue git #103. For parameter -b, or --backup-and-modify-in-place,
+  the default backup method has been changed to preserve the inode value
+  of the file being formatted.  If this causes a problem, the previous
+  method is available and can be used by setting -backup-mode='move', or
+  -bm='move'.  The new default corresponds to -bm='copy'.  The difference
+  between the two methods is as follows.  For the older method,
+  -bm='move', the input file was moved to the backup, and a new file was
+  created for the formatted output.  This caused the inode to change.  For
+  the new default method, -bm='copy', the input is copied to the backup
+  and then the input file is reopened and rewritten. This preserves the
+  file inode.  Tests have not produced any problems with this change, but
+  before using the --backup-and-modify-in-place parameter please verify
+  that it works correctly in your environment and operating system. The
+  initial update for this had an error which was caught and fixed
+  in git #109.
+
+- Fix undefined value message when perltidy -D is used (git #104)
+
+- Fixed an inconsistency in html colors near pointers when -html is used.
+  Previously, a '-&gt;' at the end of a line got the 'punctuation color', black
+  by default but a '-&gt;' before an identifier got the color of the following
+  identifier. Now all pointers get the same color, which is black by default.
+  Also, previously a word following a '-&gt;' was given the color of a bareword,
+  black by default, but now it is given the color of an identifier.
+
+- Fixed incorrect indentation of any function named 'err'.  This was
+  due to some old code from when "use feature 'err'" was valid.
+
+        # OLD:
+        my ($curr) = current();
+          err (@_);
+
+        # NEW:
+        my ($curr) = current();
+        err(@_);
+
+- Added parameter --delete-repeated-commas (-drc) to delete repeated
+  commas. This is off by default. For example, given:
+
+        ignoreSpec( $file, "file",, \%spec, \%Rspec );
+
+  # perltidy -drc:
+        ignoreSpec( $file, "file", \%spec, \%Rspec );
+
+- Add continuation indentation to long C-style 'for' terms; i.e.
+
+        # OLD
+        for (
+            $j = $i - $shell ;
+            $j &gt;= 0
+            &amp;&amp; ++$ncomp
+            &amp;&amp; $array-&gt;[$j] gt $array-&gt;[ $j + $shell ] ;
+            $j -= $shell
+          )
+
+        # NEW
+        for (
+            $j = $i - $shell ;
+            $j &gt;= 0
+              &amp;&amp; ++$ncomp
+              &amp;&amp; $array-&gt;[$j] gt $array-&gt;[ $j + $shell ] ;
+            $j -= $shell
+          )
+
+  This will change some existing formatting with very long 'for' terms.
+
+- The following new parameters are available for manipulating
+  trailing commas of lists. They are described in the manual.
+
+       --want-trailing-commas=s, -wtc=s
+       --add-trailing-commas,    -atc
+       --delete-trailing-commas, -dtc
+       --delete-weld-interfering-commas, -dwic
+
+- Files with errors due to missing, extra or misplaced parens, braces,
+  or square brackets are now written back out verbatim, without any
+  attempt at formatting.
+
+- This version runs 10 to 15 percent faster than the previous
+  release on large files due to optimizations made with the help of
+  Devel::NYTProf.
+
+- This version was stress-tested for over 200 cpu hours with random
+  input parameters. No failures to converge, internal fault checks,
+  undefined variable references or other irregularities were seen.
+</code></pre>
+
 <h2>2022 06 13</h2>
 
 <pre><code>- No significant bugs have been found since the last release but users
 - Added vertical alignment for qw quotes and empty parens in 'use'
   statements (see issue #git 93).  This new alignment is 'on' by default
   and will change formatting as shown below. If this is not wanted it can
-  be turned off with the parameter -vxl='q' (--valign-exclude-list='q').
+  be turned off with the parameter -vxl='q' (--valign-exclusion-list='q').
 
     # old default, or -vxl='q'
     use Getopt::Long qw(GetOptions);
index ae33f1d7eb6c96a96db0aefbe109e5f89ea3a2d4..f81e27863ac50ae3265d03850610c24e0bf0bbc9 100644 (file)
 
 <h1 id="VERSION">VERSION</h1>
 
-<p>This man page documents Perl::Tidy version 20220613</p>
+<p>This man page documents Perl::Tidy version 20221112</p>
 
 <h1 id="LICENSE">LICENSE</h1>
 
index 11bda6d67df9fb1d7d16aa2007e9d767a022ed1b..1dca74e0bc869e7b8f5f4c5ee67ca9c60bae4188 100644 (file)
@@ -32,15 +32,15 @@ source and decoded it from a utf8 but did not re-encode it before storing it in
 the output string.  So the source string was in a different storage mode than
 the output string, and a direct comparison was not meaningful.
 
-This problem is an unintentional result of the historical evolution of perltidy and needs to be fixed.
+This problem is an unintentional result of the historical evolution of perltidy.
 
 The same problem occurs if the destination is an array rather than a string,
 so for simplicity we can limit this discussion to string destinations, which
 are more common.
 
-## How will the problem be fixed?
+## How has the problem been fixed?
 
-A fix is being phased in over a couple of steps. The first step was to
+A fix was phased in over a couple of steps. The first step was to
 introduce a new flag in in version 20220217.  The new flag is
 **--encode-output-strings**, or **-eos**.  When this is set, perltidy will fix
 the specific problem mentioned above by doing an encoding before returning.
@@ -60,9 +60,9 @@ To illustrate using this flag in the above example, we could write
     );
 ```
 
-With this modification we can make a meaningful direct comparison of `$source` and `$output`. The test on `$VERSION` allows this to work with older versions of perltidy (which would not recognize the flag -eos).  An update such as the above can be made right now to facilitate a smooth transition to the new default.
+With this modification we can make a meaningful direct comparison of `$source` and `$output`. The test on `$VERSION` allows this to work with older versions of perltidy (which would not recognize the flag -eos).
 
-In the second step, possibly later in 2022, the new **-eos** flag will become the default.
+In the second step, introduced in version 20220613, the new **-eos** flag became the default.
 
 ## What can go wrong?
 
@@ -115,17 +115,15 @@ A related problem is if an update of Perl::Tidy is made without also updating
 a corrected version of a module such as the above.  To help reduce the chance
 that this will occur the Change Log for perltidy will contain a warning to be
 alert for the double encoding problem, and how to reset the default if
-necessary.  This is also the reason for waiting some time before the second step is made.
+necessary.  This is also the reason for waiting some time before the second step was made.
 
-If double encoding does appear to be occuring after the default change for some program which calls Perl::Tidy, then a quick emergency fix can be made by the program user by setting **-neos** to revert to the old default.  A better fix can eventually be made by the program author by removing the second encoding using a technique such as illustrated above.
+If double encoding does appear to be occuring with the change in the default for some program which calls Perl::Tidy, then a quick emergency fix can be made by the program user by setting **-neos** to revert to the old default.  A better fix can eventually be made by the program author by removing the second encoding using a technique such as illustrated above.
 
 ## Summary
 
 A new flag, **-eos**, has been added to cause Perl::Tidy to behave better as a
-filter when called from other Perl scripts.  This flag will eventually become
-the default setting.  Programs which use Perl::Tidy as a
-filter can be tested right now with the new **-eos** flag to be sure that double
-encoding is not possible when the default is changed.
+filter when called from other Perl scripts.  This flag is the default setting
+in the current release.
 
 ## Reference
 
index 8087a23f82acdf0a49277f696bbc58116dc38ff8..70df09d25bb7cea474c819adb31898149b71199c 100644 (file)
@@ -18,7 +18,7 @@
   <li><a href="#EXAMPLES">EXAMPLES</a></li>
   <li><a href="#OPTIONS---OVERVIEW">OPTIONS - OVERVIEW</a>
     <ul>
-      <li><a href="#I-O-control">I/O control</a></li>
+      <li><a href="#I-O-Control">I/O Control</a></li>
     </ul>
   </li>
   <li><a href="#FORMATTING-OPTIONS">FORMATTING OPTIONS</a>
       <li><a href="#Comment-Controls">Comment Controls</a></li>
       <li><a href="#Skipping-Selected-Sections-of-Code">Skipping Selected Sections of Code</a></li>
       <li><a href="#Line-Break-Control">Line Break Control</a></li>
+      <li><a href="#Controlling-Breaks-at-Braces-Parens-and-Square-Brackets">Controlling Breaks at Braces, Parens, and Square Brackets</a></li>
+      <li><a href="#Welding">Welding</a></li>
+      <li><a href="#Breaking-Before-or-After-Operators">Breaking Before or After Operators</a></li>
       <li><a href="#Controlling-List-Formatting">Controlling List Formatting</a></li>
+      <li><a href="#Adding-and-Deleting-Commas">Adding and Deleting Commas</a></li>
       <li><a href="#Retaining-or-Ignoring-Existing-Line-Breaks">Retaining or Ignoring Existing Line Breaks</a></li>
       <li><a href="#Blank-Line-Control">Blank Line Control</a></li>
       <li><a href="#Styles">Styles</a></li>
 
 <p>Option names may be terminated early as long as they are uniquely identified. For example, instead of <b>--dump-token-types</b>, it would be sufficient to enter <b>--dump-tok</b>, or even <b>--dump-t</b>, to uniquely identify this command.</p>
 
-<h2 id="I-O-control">I/O control</h2>
+<h2 id="I-O-Control">I/O Control</h2>
 
 <p>The following parameters concern the files which are read and written.</p>
 
 
 <p>Modify the input file or files in-place and save the original with the extension <i>.bak</i>. Any existing <i>.bak</i> file will be deleted. See next item for changing the default backup extension, and for eliminating the backup file altogether.</p>
 
+<p><b>Please Note</b>: Writing back to the input file increases the risk of data loss or corruption in the event of a software or hardware malfunction. Before using the <b>-b</b> parameter please be sure to have backups and verify that it works correctly in your environment and operating system.</p>
+
 <p>A <b>-b</b> flag will be ignored if input is from standard input or goes to standard output, or if the <b>-html</b> flag is set.</p>
 
 <p>In particular, if you want to use both the <b>-b</b> flag and the <b>-pbp</b> (--perl-best-practices) flag, then you must put a <b>-nst</b> flag after the <b>-pbp</b> flag because it contains a <b>-st</b> flag as one of its components, which means that output will go to the standard output stream.</p>
   &lt;-bext=&#39;/backup&#39;&gt;   F&lt;.backup&gt;         Delete if no errors
   &lt;-bext=&#39;original/&#39;&gt; F&lt;.original&gt;       Delete if no errors</code></pre>
 
+</dd>
+<dt id="bm-s---backup-method-s"><b>-bm=s</b>, <b>--backup-method=s</b></dt>
+<dd>
+
+<p>This parameter should not normally be used but is available in the event that problems arise as a transition is made from an older implementation of the backup logic to a newer implementation. The newer implementation is the default and is specified with <b>-bm=&#39;copy&#39;</b>. The older implementation is specified with <b>-bm=&#39;move&#39;</b>. The difference is that the older implementation made the backup by moving the input file to the backup file, and the newer implementation makes the backup by copying the input file. The newer implementation preserves the file system <b>inode</b> value. This may avoid problems with other software running simultaneously. This change was made as part of issue <b>git #103</b> at github.</p>
+
 </dd>
 <dt id="w---warning-output"><b>-w</b>, <b>--warning-output</b></dt>
 <dd>
             fixit($i);
         }</code></pre>
 
-<p>Use <b>-nola</b> to not outdent labels. To control line breaks after labels see <a href="#bal-n---break-after-labels-n">&quot;bal=n, --break-after-labels=n&quot;</a>.</p>
+<p>Use <b>-nola</b> to not outdent labels. To control line breaks after labels see <a href="#bal-n---break-after-labels-n">&quot;-bal=n, --break-after-labels=n&quot;</a>.</p>
 
 </dd>
 <dt id="Outdenting-Keywords"><b>Outdenting Keywords</b></dt>
 
 <h2 id="Line-Break-Control">Line Break Control</h2>
 
-<p>The parameters in this section control breaks after non-blank lines of code. Blank lines are controlled separately by parameters in the section <a href="#Blank-Line-Control">&quot;Blank Line Control&quot;</a>.</p>
+<p>The parameters in this and the next sections control breaks after non-blank lines of code. Blank lines are controlled separately by parameters in the section <a href="#Blank-Line-Control">&quot;Blank Line Control&quot;</a>.</p>
 
 <dl>
 
+<dt id="dnl---delete-old-newlines"><b>-dnl</b>, <b>--delete-old-newlines</b></dt>
+<dd>
+
+<p>By default, perltidy first deletes all old line break locations, and then it looks for good break points to match the desired line length. Use <b>-ndnl</b> or <b>--nodelete-old-newlines</b> to force perltidy to retain all old line break points.</p>
+
+</dd>
+<dt id="anl---add-newlines"><b>-anl</b>, <b>--add-newlines</b></dt>
+<dd>
+
+<p>By default, perltidy will add line breaks when necessary to create continuations of long lines and to improve the script appearance. Use <b>-nanl</b> or <b>--noadd-newlines</b> to prevent any new line breaks.</p>
+
+<p>This flag does not prevent perltidy from eliminating existing line breaks; see <b>--freeze-newlines</b> to completely prevent changes to line break points.</p>
+
+</dd>
 <dt id="fnl---freeze-newlines"><b>-fnl</b>, <b>--freeze-newlines</b></dt>
 <dd>
 
 <p>Note: If you also want to keep your blank lines exactly as they are, you can use the <b>-fbl</b> flag which is described in the section <a href="#Blank-Line-Control">&quot;Blank Line Control&quot;</a>.</p>
 
 </dd>
+</dl>
+
+<h2 id="Controlling-Breaks-at-Braces-Parens-and-Square-Brackets">Controlling Breaks at Braces, Parens, and Square Brackets</h2>
+
+<dl>
+
 <dt id="ce---cuddled-else"><b>-ce</b>, <b>--cuddled-else</b></dt>
 <dd>
 
 <dt id="cblx---cuddled-block-list-exclusive"><b>-cblx</b>, <b>--cuddled-block-list-exclusive</b></dt>
 <dd>
 
-<p>When cuddled else formatting is selected with <b>-ce</b>, setting this flag causes perltidy to ignore its built-in defaults and rely exclusively on the block types specified on the <b>-cbl</b> flag described in the previous section. For example, to avoid using cuddled <b>catch</b> and <b>finally</b>, which among in the defaults, the following set of parameters could be used:</p>
+<p>When cuddled else formatting is selected with <b>-ce</b>, setting this flag causes perltidy to ignore its built-in defaults and rely exclusively on the block types specified on the <b>-cbl</b> flag described in the previous section. For example, to avoid using cuddled <b>catch</b> and <b>finally</b>, which are among the defaults, the following set of parameters could be used:</p>
 
 <pre><code>  perltidy -ce -cbl=&#39;else elsif continue&#39; -cblx</code></pre>
 
   -bbpi=2 indent one full indentation level</code></pre>
 
 </dd>
+</dl>
+
+<h2 id="Welding">Welding</h2>
+
+<dl>
+
 <dt id="wn---weld-nested-containers"><b>-wn</b>, <b>--weld-nested-containers</b></dt>
 <dd>
 
             next if $x == $y;
         } } until $x++ &gt; $z;</code></pre>
 
-<p>When this flag is set perltidy makes a preliminary pass through the file and identifies all nested pairs of containers. To qualify as a nested pair, the closing container symbols must be immediately adjacent and the opening symbols must either (1) be adjacent as in the above example, or (2) have an anonymous sub declaration following an outer opening container symbol which is not a code block brace, or (3) have an outer opening paren separated from the inner opening symbol by any single non-container symbol or something that looks like a function evaluation, as illustrated in the next examples.</p>
+<p>When this flag is set perltidy makes a preliminary pass through the file and identifies all nested pairs of containers. To qualify as a nested pair, the closing container symbols must be immediately adjacent and the opening symbols must either (1) be adjacent as in the above example, or (2) have an anonymous sub declaration following an outer opening container symbol which is not a code block brace, or (3) have an outer opening paren separated from the inner opening symbol by any single non-container symbol or something that looks like a function evaluation, as illustrated in the next examples. An additonal option (4) which can be turned on with the flag <b>--weld-fat-comma</b> is when the opening container symbols are separated by a hash key and fat comma (=&gt;).</p>
 
 <p>Any container symbol may serve as both the inner container of one pair and as the outer container of an adjacent pair. Consequently, any number of adjacent opening or closing symbols may join together in weld. For example, here are three levels of wrapped function calls:</p>
 
 
 <p>Finally, the stacking of containers defined by this flag have priority over any other container stacking flags. This is because any welding is done first.</p>
 
+</dd>
+<dt id="wfc---weld-fat-comma"><b>-wfc</b>, <b>--weld-fat-comma </b></dt>
+<dd>
+
+<p>When the <b>-wfc</b> flag is set, along with <b>-wn</b>, perltidy is allowed to weld an opening paren to an inner opening container when they are separated by a hash key and fat comma (=&gt;). for example</p>
+
+<pre><code>    # perltidy -wn -wfc
+    elf-&gt;call_method( method_name_foo =&gt; {
+        some_arg1       =&gt; $foo,
+        some_other_arg3 =&gt; $bar-&gt;{&#39;baz&#39;},
+    } );</code></pre>
+
+<p>This option is off by default.</p>
+
 </dd>
 <dt id="wnxl-s---weld-nested-exclusion-list"><b>-wnxl=s</b>, <b>--weld-nested-exclusion-list</b></dt>
 <dd>
 <p>Please note that if both opening and closing tokens are to be stacked, then the newer flag <b>-weld-nested-containers</b> may be preferable because it insures that stacking is always done symmetrically. It also removes an extra level of unnecessary indentation within welded containers. It is able to do this because it works on formatting globally rather than locally, as the <b>-sot</b> and <b>-sct</b> flags do.</p>
 
 </dd>
-<dt id="dnl---delete-old-newlines"><b>-dnl</b>, <b>--delete-old-newlines</b></dt>
-<dd>
-
-<p>By default, perltidy first deletes all old line break locations, and then it looks for good break points to match the desired line length. Use <b>-ndnl</b> or <b>--nodelete-old-newlines</b> to force perltidy to retain all old line break points.</p>
-
-</dd>
-<dt id="anl---add-newlines"><b>-anl</b>, <b>--add-newlines</b></dt>
-<dd>
-
-<p>By default, perltidy will add line breaks when necessary to create continuations of long lines and to improve the script appearance. Use <b>-nanl</b> or <b>--noadd-newlines</b> to prevent any new line breaks.</p>
-
-<p>This flag does not prevent perltidy from eliminating existing line breaks; see <b>--freeze-newlines</b> to completely prevent changes to line break points.</p>
+</dl>
 
-</dd>
-<dt id="Controlling-whether-perltidy-breaks-before-or-after-operators"><b>Controlling whether perltidy breaks before or after operators</b></dt>
-<dd>
+<h2 id="Breaking-Before-or-After-Operators">Breaking Before or After Operators</h2>
 
 <p>Four command line parameters provide some control over whether a line break should be before or after specific token types. Two parameters give detailed control:</p>
 
 
 <p>and the <b>-bbao</b> flag sets the default to break before all of these operators. These can be used to define an initial break preference which can be fine-tuned with the <b>-wba</b> and <b>-wbb</b> flags. For example, to break before all operators except an <b>=</b> one could use --bbao -wba=&#39;=&#39; rather than listing every single perl operator except <b>=</b> on a -wbb flag.</p>
 
-</dd>
-<dt id="bal-n---break-after-labels-n"><b>bal=n, --break-after-labels=n</b></dt>
+<dl>
+
+<dt id="bal-n---break-after-labels-n"><b>-bal=n, --break-after-labels=n</b></dt>
 <dd>
 
 <p>This flag controls whether or not a line break occurs after a label. There are three possible values for <b>n</b>:</p>
 </dd>
 </dl>
 
+<h2 id="Adding-and-Deleting-Commas">Adding and Deleting Commas</h2>
+
+<dl>
+
+<dt id="drc---delete-repeated-commas"><b>-drc</b>, <b>--delete-repeated-commas</b></dt>
+<dd>
+
+<p>Repeated commas in a list are undesirable and can be removed with this flag. For example, given this list with a repeated comma</p>
+
+<pre><code>      ignoreSpec( $file, &quot;file&quot;,, \%spec, \%Rspec );</code></pre>
+
+<p>we can remove it with -drc</p>
+
+<pre><code>      # perltidy -drc:
+      ignoreSpec( $file, &quot;file&quot;, \%spec, \%Rspec );</code></pre>
+
+<p>Since the default is not to add or delete commas, this feature is off by default and must be requested.</p>
+
+</dd>
+<dt id="want-trailing-commas-s-or--wtc-s---add-trailing-commas-or--atc-and---delete-trailing-commas-or--dtc"><b>--want-trailing-commas=s</b> or <b>-wtc=s</b>, <b>--add-trailing-commas</b> or <b>-atc</b>, and <b>--delete-trailing-commas</b> or <b>-dtc</b></dt>
+<dd>
+
+<p>A trailing comma is a comma following the last item of a list. Perl allows trailing commas but they are not required. By default, perltidy does not add or delete trailing commas, but it is possible to manipulate them with the following set of three related parameters:</p>
+
+<pre><code>  --want-trailing-commas=s, -wtc=s - defines where trailing commas are wanted
+  --add-trailing-commas,    -atc   - gives permission to add trailing commas to match the style wanted
+  --delete-trailing-commas, -dtc   - gives permission to delete trailing commas which do not match the style wanted</code></pre>
+
+<p>The parameter <b>--want-trailing-commas=s</b>, or <b>-wtc=s</b>, defines a preferred style. The string <b>s</b> indicates which lists should get trailing commas, as follows:</p>
+
+<pre><code>  s=0 : no list should have a trailing comma
+  s=1 or * : every list should have a trailing comma
+  s=m a multi-line list should have a trailing commas
+  s=b trailing commas should be &#39;bare&#39; (comma followed by newline)
+  s=h lists of key=&gt;value pairs, with about one one &#39;=&gt;&#39; and one &#39;,&#39; per line,
+      with a bare trailing comma
+  s=i lists with about one comma per line, with a bare trailing comma
+  s=&#39; &#39; or -wtc not defined : leave trailing commas unchanged [DEFAULT].</code></pre>
+
+<p>This parameter by itself only indicates the where trailing commas are wanted. Perltidy only adds these trailing commas if the flag <b>--add-trailing-commas</b>, or <b>-atc</b> is set. And perltidy only removes unwanted trailing commas if the flag <b>--delete-trailing-commas</b>, or <b>-dtc</b> is set.</p>
+
+<p>Here are some example parameter combinations and their meanings</p>
+
+<pre><code>  -wtc=0 -dtc   : delete all trailing commas
+  -wtc=1 -atc   : all lists get trailing commas
+  -wtc=m -atc   : all multi-line lists get trailing commas, but
+                  single line lists remain unchanged.
+  -wtc=m -dtc   : multi-line lists remain unchanged, but
+                  any trailing commas on single line lists are removed.
+  -wtc=m -atc -dtc  : all multi-line lists get trailing commas, and
+                      any trailing commas on single line lists are removed.</code></pre>
+
+<p>For example, given the following input without a trailing comma</p>
+
+<pre><code>    bless {
+        B    =&gt; $B,
+        Root =&gt; $Root
+    } =&gt; $package;</code></pre>
+
+<p>we can add a trailing comma after the variable <code>$Root</code> using</p>
+
+<pre><code>    # perltidy -wtc=m -atc
+    bless {
+        B    =&gt; $B,
+        Root =&gt; $Root,
+    } =&gt; $package;</code></pre>
+
+<p>This could also be achieved in this case with <b>-wtc=b</b> instead of <b>-wtc=m</b> because the trailing comma here is bare (separated from its closing brace by a newline). And it could also be achieved with <b>-wtc=h</b> because this particular list is a list of key=&gt;value pairs.</p>
+
+<p>The above styles should cover the main of situations of interest, but it is possible to apply a different style to each type of container token by including an opening token ahead of the style character in the above table. For example</p>
+
+<pre><code>    -wtc=&#39;(m [b&#39;</code></pre>
+
+<p>means that lists within parens should have multi-line trailing commas, and that lists within square brackets have bare trailing commas. Since there is no specification for curly braces in this example, their trailing commas would remain unchanged.</p>
+
+<p>For parentheses, an additional item of information which can be given is an alphanumeric letter which is used to limit the selection further depending on the type of token immediately before the opening paren. The possible letters are currently &#39;k&#39;, &#39;K&#39;, &#39;f&#39;, &#39;F&#39;, &#39;w&#39;, and &#39;W&#39;, with these meanings for matching whatever precedes an opening paren:</p>
+
+<pre><code> &#39;k&#39; matches if the previous nonblank token is a perl built-in keyword (such as &#39;if&#39;, &#39;while&#39;),
+ &#39;K&#39; matches if &#39;k&#39; does not, meaning that the previous token is not a keyword.
+ &#39;f&#39; matches if the previous token is a function other than a keyword.
+ &#39;F&#39; matches if &#39;f&#39; does not.
+ &#39;w&#39; matches if either &#39;k&#39; or &#39;f&#39; match.
+ &#39;W&#39; matches if &#39;w&#39; does not.</code></pre>
+
+<p>These are the same codes used for <b>--line-up-parentheses-inclusion-list</b>. For example,</p>
+
+<pre><code>  -wtc = &#39;w(m&#39;</code></pre>
+
+<p>means that trailing commas are wanted for multi-line parenthesized lists following a function call or keyword.</p>
+
+<p>Here are some points to note regarding adding and deleting trailing commas:</p>
+
+<ul>
+
+<li><p>For the implementation of these parameters, a <b>list</b> is basically taken to be a container of items (parens, square brackets, or braces), which is not a code block, with one or more commas. These parameters only apply to something that fits this definition of a list.</p>
+
+<p>Note that a paren-less list of parameters is not a list by this definition, so these parameters have no effect on a peren-less list.</p>
+
+<p>Another consequence is that if the only comma in a list is deleted, then it cannot later be added back with these parameters because the container no longer fits this definition of a list. For example, given</p>
+
+<pre><code>    my ( $self, ) = @_;</code></pre>
+
+<p>and if we remove the comma with</p>
+
+<pre><code>    # perltidy -wtc=m -dtc
+    my ( $self ) = @_;</code></pre>
+
+<p>then we cannot use these trailing comma controls to add this comma back.</p>
+
+</li>
+<li><p>By <b>multiline</b> list is meant a list for which the first comma and trailing comma are on different lines.</p>
+
+</li>
+<li><p>A <b>bare</b> trailing comma is a comma which is at the end of a line. That is, the closing container token follows on a different line. So a list with a bare trailing comma is a special case of a multi-line list.</p>
+
+</li>
+<li><p>The decision regarding whether or not a list is multi-line or bare is made based on the <b>input</b> stream. In some cases it may take an iteration or two to reach a final state.</p>
+
+</li>
+<li><p>When using these parameters for the first time it is a good idea to practice on some test scripts and verify that the results are as expected.</p>
+
+</li>
+<li><p>Since the default behavior is not to add or delete commas, these parameters can be useful on a temporary basis for reformatting a script.</p>
+
+</li>
+</ul>
+
+</dd>
+<dt id="dwic---delete-weld-interfering-commas"><b>-dwic</b>, <b>--delete-weld-interfering-commas</b></dt>
+<dd>
+
+<p>If the closing tokens of two nested containers are separated by a comma, then welding requested with <b>--weld-nested-containers</b> cannot occur. Any commas in this situation are optional trailing commas and can be removed with <b>-dwic</b>. For example, a comma in this scipt prevents welding:</p>
+
+<pre><code>    # perltidy -wn
+    skip_symbols(
+        [ qw(
+            Perl_dump_fds
+            Perl_ErrorNo
+            Perl_GetVars
+            PL_sys_intern
+        ) ],
+    );</code></pre>
+
+<p>Using <b>-dwic</b> removes the comma and allows welding:</p>
+
+<pre><code>    # perltidy -wn -dwic
+    skip_symbols( [ qw(
+        Perl_dump_fds
+        Perl_ErrorNo
+        Perl_GetVars
+        PL_sys_intern
+    ) ] );</code></pre>
+
+<p>Since the default is not to add or delete commas, this feature is off by default. Here are some points to note about the <b>-dwic</b> parameter</p>
+
+<ul>
+
+<li><p>This operation is not reversible, so please check results of using this parameter carefully.</p>
+
+</li>
+<li><p>Removing this type of isolated trailing comma is necessary for welding to be possible, but not sufficient. So welding will not always occur where these commas are removed.</p>
+
+</li>
+</ul>
+
+</dd>
+</dl>
+
 <h2 id="Retaining-or-Ignoring-Existing-Line-Breaks">Retaining or Ignoring Existing Line Breaks</h2>
 
 <p>Several additional parameters are available for controlling the extent to which line breaks in the input script influence the output script. In most cases, the default parameter values are set so that, if a choice is possible, the output style follows the input style. For example, if a short logical container is broken in the input script, then the default behavior is for it to remain broken in the output script.</p>
 <dt id="bom---break-at-old-method-breakpoints"><b>-bom</b>, <b>--break-at-old-method-breakpoints</b></dt>
 <dd>
 
-<p>By default, a method call arrow <code>-&gt;</code> is considered a candidate for a breakpoint, but method chains will fill to the line width before a break is considered. With <b>-bom</b>, breaks before the arrow are preserved, so if you have preformatted a method chain:</p>
+<p>By default, a method call arrow <code>-&gt;</code> is considered a candidate for a breakpoint, but method chains will fill to the line width before a break is considered. With <b>-bom</b>, breaks before the arrow are preserved, so if you have pre-formatted a method chain:</p>
 
 <pre><code>  my $q = $rs
     -&gt;related_resultset(&#39;CDs&#39;)
 
 <p>The following list shows all short parameter names which allow a prefix &#39;n&#39; to produce the negated form:</p>
 
-<pre><code> D      anl    asbl   asc    ast    asu    atnl   aws    b      baa
- baao   bar    bbao   bbb    bbc    bbs    bl     bli    boa    boc
- bok    bol    bom    bos    bot    cblx   ce     conv   cs     csc
- cscb   cscw   dac    dbc    dcbl   dcsc   ddf    dln    dnl    dop
- dp     dpro   dsc    dsm    dsn    dtt    dwls   dwrs   dws    eos
- f      fll    fpva   frm    fs     fso    gcs    hbc    hbcm   hbco
- hbh    hbhh   hbi    hbj    hbk    hbm    hbn    hbp    hbpd   hbpu
- hbq    hbs    hbsc   hbv    hbw    hent   hic    hicm   hico   hih
- hihh   hii    hij    hik    him    hin    hip    hipd   hipu   hiq
- his    hisc   hiv    hiw    hsc    html   ibc    icb    icp    iob
- isbc   iscl   kgb    kgbd   kgbi   kis    lal    log    lop    lp
- lsl    mem    nib    ohbr   okw    ola    olc    oll    olq    opr
- opt    osbc   osbr   otr    ple    pod    pvl    q      sac    sbc
- sbl    scbb   schb   scp    scsb   sct    se     sfp    sfs    skp
- sob    sobb   sohb   sop    sosb   sot    ssc    st     sts    t
- tac    tbc    toc    tp     tqw    trp    ts     tsc    tso    vbc
- vc     vmll   vsc    w      wn     x      xci    xlp    xs</code></pre>
+<pre><code> D      anl    asbl   asc    ast    asu    atc    atnl   aws    b
+ baa    baao   bar    bbao   bbb    bbc    bbs    bl     bli    boa
+ boc    bok    bol    bom    bos    bot    cblx   ce     conv   cs
+ csc    cscb   cscw   dac    dbc    dcbl   dcsc   ddf    dln    dnl
+ dop    dp     dpro   drc    dsc    dsm    dsn    dtc    dtt    dwic
+ dwls   dwrs   dws    eos    f      fll    fpva   frm    fs     fso
+ gcs    hbc    hbcm   hbco   hbh    hbhh   hbi    hbj    hbk    hbm
+ hbn    hbp    hbpd   hbpu   hbq    hbs    hbsc   hbv    hbw    hent
+ hic    hicm   hico   hih    hihh   hii    hij    hik    him    hin
+ hip    hipd   hipu   hiq    his    hisc   hiv    hiw    hsc    html
+ ibc    icb    icp    iob    isbc   iscl   kgb    kgbd   kgbi   kis
+ lal    log    lop    lp     lsl    mem    nib    ohbr   okw    ola
+ olc    oll    olq    opr    opt    osbc   osbr   otr    ple    pod
+ pvl    q      sac    sbc    sbl    scbb   schb   scp    scsb   sct
+ se     sfp    sfs    skp    sob    sobb   sohb   sop    sosb   sot
+ ssc    st     sts    t      tac    tbc    toc    tp     tqw    trp
+ ts     tsc    tso    vbc    vc     vmll   vsc    w      wfc    wn
+ x      xci    xlp    xs</code></pre>
 
 <p>Equivalently, the prefix &#39;no&#39; or &#39;no-&#39; on the corresponding long names may be used.</p>
 
 
 <h1 id="VERSION">VERSION</h1>
 
-<p>This man page documents perltidy version 20220613</p>
+<p>This man page documents perltidy version 20221112</p>
 
 <h1 id="BUG-REPORTS">BUG REPORTS</h1>
 
index 211b8ad7d6c14a3c0f62856f975402b222e622ff..88f7c43ef002f233fa0bcbbb86f6c7a86f1a3b67 100644 (file)
@@ -104,16 +104,16 @@ use File::Temp qw(tempfile);
 
 BEGIN {
 
-    # Release version is the approximate YYMMDD of the release.
+    # Release version is the approximate YYYYMMDD of the release.
     # Development version is (Last Release).(Development Number)
 
     # To make the number continually increasing, the Development Number is a 2
-    # digit number starting at 01 after a release is continually bumped along
-    # at significant points during development. If it ever reaches 99 then the
-    # Release version must be bumped, and it is probably past time for a
-    # release anyway.
+    # digit number starting at 01 after a release. It is continually bumped
+    # along at significant points during development. If it ever reaches 99
+    # then the Release version must be bumped, and it is probably past time for
+    # release anyway.
 
-    $VERSION = '20220613';
+    $VERSION = '20221112';
 }
 
 sub DESTROY {
@@ -317,8 +317,7 @@ sub find_input_line_ending {
     my $missing_file_spec;
 
     BEGIN {
-        eval { require File::Spec };
-        $missing_file_spec = $EVAL_ERROR;
+        $missing_file_spec = !eval { require File::Spec; 1 };
     }
 
     sub catfile {
@@ -386,10 +385,12 @@ sub find_input_line_ending {
 # messages.  It writes a .LOG file, which may be saved with a
 # '-log' or a '-g' flag.
 
-{ #<<<
+{ #<<<  (this side comment avoids excessive indentation in a closure)
 
 my $Warn_count;
 my $fh_stderr;
+my $loaded_unicode_gcstring;
+my $rstatus;
 
 # Bump Warn_count only: it is essential to bump the count on all warnings, even
 # if no message goes out, so that the correct exit status is set.
@@ -420,6 +421,54 @@ sub is_char_mode {
     return;
 } ## end sub is_char_mode
 
+my $md5_hex = sub {
+    my ($buf) = @_;
+
+    # Evaluate the MD5 sum for a string
+    # Patch for [rt.cpan.org #88020]
+    # Use utf8::encode since md5_hex() only operates on bytes.
+    # my $digest = md5_hex( utf8::encode($sink_buffer) );
+
+    # Note added 20180114: the above patch did not work correctly.  I'm not
+    # sure why.  But switching to the method recommended in the Perl 5
+    # documentation for Encode worked.  According to this we can either use
+    #    $octets = encode_utf8($string)  or equivalently
+    #    $octets = encode("utf8",$string)
+    # and then calculate the checksum.  So:
+    my $octets = Encode::encode( "utf8", $buf );
+    my $digest = md5_hex($octets);
+    return $digest;
+};
+
+BEGIN {
+
+    # Array index names for $self.
+    # Do not combine with other BEGIN blocks (c101).
+    my $i = 0;
+    use constant {
+        _actual_output_extension_  => $i++,
+        _debugfile_stream_         => $i++,
+        _decoded_input_as_         => $i++,
+        _destination_stream_       => $i++,
+        _diagnostics_object_       => $i++,
+        _display_name_             => $i++,
+        _file_extension_separator_ => $i++,
+        _fileroot_                 => $i++,
+        _is_encoded_data_          => $i++,
+        _length_function_          => $i++,
+        _line_separator_           => $i++,
+        _logger_object_            => $i++,
+        _output_file_              => $i++,
+        _postfilter_               => $i++,
+        _prefilter_                => $i++,
+        _rOpts_                    => $i++,
+        _saw_pbp_                  => $i++,
+        _tabsize_                  => $i++,
+        _teefile_stream_           => $i++,
+        _user_formatter_           => $i++,
+    };
+}
+
 sub perltidy {
 
     my %input_hash = @_;
@@ -446,7 +495,7 @@ sub perltidy {
     );
 
     # Status information which can be returned for diagnostic purposes.
-    # This is intended for testing and subject to change.
+    # NOTE: This is intended only for testing and subject to change.
 
     # List of "key => value" hash entries:
 
@@ -486,7 +535,7 @@ sub perltidy {
     # blinking           => true if stopped on blinking states
     #                       ( i.e., unstable formatting, should not happen )
 
-    my $rstatus = {
+    $rstatus = {
 
         file_count         => 0,
         opt_format         => EMPTY_STRING,
@@ -575,6 +624,9 @@ EOM
         $fh_stderr = *STDERR;
     }
 
+    my $self = [];
+    bless $self, __PACKAGE__;
+
     sub Exit {
         my $flag = shift;
         if   ($flag) { goto ERROR_EXIT }
@@ -589,24 +641,34 @@ EOM
         croak "unexpected return to Die";
     }
 
-    my $md5_hex = sub {
-        my ($buf) = @_;
+    sub Fault {
+        my ($msg) = @_;
 
-        # Evaluate the MD5 sum for a string
-        # Patch for [rt.cpan.org #88020]
-        # Use utf8::encode since md5_hex() only operates on bytes.
-        # my $digest = md5_hex( utf8::encode($sink_buffer) );
+        # This routine is called for errors that really should not occur
+        # except if there has been a bug introduced by a recent program change.
+        # Please add comments at calls to Fault to explain why the call
+        # should not occur, and where to look to fix it.
+        my ( $package0, $filename0, $line0, $subroutine0 ) = caller(0);
+        my ( $package1, $filename1, $line1, $subroutine1 ) = caller(1);
+        my ( $package2, $filename2, $line2, $subroutine2 ) = caller(2);
 
-        # Note added 20180114: the above patch did not work correctly.  I'm not
-        # sure why.  But switching to the method recommended in the Perl 5
-        # documentation for Encode worked.  According to this we can either use
-        #    $octets = encode_utf8($string)  or equivalently
-        #    $octets = encode("utf8",$string)
-        # and then calculate the checksum.  So:
-        my $octets = Encode::encode( "utf8", $buf );
-        my $digest = md5_hex($octets);
-        return $digest;
-    };
+        my $input_stream_name = $rstatus->{'input_name'};
+        $input_stream_name = '(unknown)' unless ($input_stream_name);
+        Die(<<EOM);
+==============================================================================
+While operating on input stream with name: '$input_stream_name'
+A fault was detected at line $line0 of sub '$subroutine1'
+in file '$filename1'
+which was called from line $line1 of sub '$subroutine2'
+Message: '$msg'
+This is probably an error introduced by a recent programming change.
+Perl::Tidy.pm reports VERSION='$VERSION'.
+==============================================================================
+EOM
+
+        # This return is to keep Perl-Critic from complaining.
+        return;
+    }
 
     # extract various dump parameters
     my $dump_options_type     = $input_hash{'dump_options_type'};
@@ -679,8 +741,11 @@ EOM
         }
     }
 
+    # These string refs will hold any warnings and error messages to be written
+    # to the logfile object when it eventually gets created.
     my $rpending_complaint;
     ${$rpending_complaint} = EMPTY_STRING;
+
     my $rpending_logfile_message;
     ${$rpending_logfile_message} = EMPTY_STRING;
 
@@ -698,10 +763,11 @@ EOM
         $dot         = '.';
         $dot_pattern = '\.';    # must escape for use in regex
     }
+    $self->[_file_extension_separator_] = $dot;
 
-    #---------------------------------------------------------------
+    #-------------------------
     # get command line options
-    #---------------------------------------------------------------
+    #-------------------------
     my ( $rOpts, $config_file, $rraw_options, $roption_string,
         $rexpansion, $roption_category, $roption_range )
       = process_command_line(
@@ -709,12 +775,18 @@ EOM
         $rpending_complaint, $dump_options_type,
       );
 
+    # Only filenames should remain in @ARGV
+    my @Arg_files = @ARGV;
+
+    $self->[_rOpts_] = $rOpts;
+
     my $saw_pbp =
       grep { $_ eq '-pbp' || $_ eq '-perl-best-practices' } @{$rraw_options};
+    $self->[_saw_pbp_] = $saw_pbp;
 
-    #---------------------------------------------------------------
+    #------------------------------------
     # Handle requests to dump information
-    #---------------------------------------------------------------
+    #------------------------------------
 
     # return or exit immediately after all dumps
     my $quit_now = 0;
@@ -770,11 +842,12 @@ EOM
         Exit(0);
     }
 
-    #---------------------------------------------------------------
+    #----------------------------------------
     # check parameters and their interactions
-    #---------------------------------------------------------------
+    #----------------------------------------
     my $tabsize =
       check_options( $rOpts, $is_Windows, $Windows_type, $rpending_complaint );
+    $self->[_tabsize_] = $tabsize;
 
     if ($user_formatter) {
         $rOpts->{'format'} = 'user';
@@ -800,73 +873,13 @@ EOM
         Die("-format='$fmt' but must be one of: $formats\n");
     }
 
-    my $output_extension = make_extension( $rOpts->{'output-file-extension'},
-        $default_file_extension{ $rOpts->{'format'} }, $dot );
-
-    # If the backup extension contains a / character then the backup should
-    # be deleted when the -b option is used.   On older versions of
-    # perltidy this will generate an error message due to an illegal
-    # file name.
-    #
-    # A backup file will still be generated but will be deleted
-    # at the end.  If -bext='/' then this extension will be
-    # the default 'bak'.  Otherwise it will be whatever characters
-    # remains after all '/' characters are removed.  For example:
-    # -bext         extension     slashes
-    #  '/'          bak           1
-    #  '/delete'    delete        1
-    #  'delete/'    delete        1
-    #  '/dev/null'  devnull       2    (Currently not allowed)
-    my $bext          = $rOpts->{'backup-file-extension'};
-    my $delete_backup = ( $rOpts->{'backup-file-extension'} =~ s/\///g );
-
-    # At present only one forward slash is allowed.  In the future multiple
-    # slashes may be allowed to allow for other options
-    if ( $delete_backup > 1 ) {
-        Die("-bext=$bext contains more than one '/'\n");
-    }
-
-    my $backup_extension =
-      make_extension( $rOpts->{'backup-file-extension'}, 'bak', $dot );
-
-    my $html_toc_extension =
-      make_extension( $rOpts->{'html-toc-extension'}, 'toc', $dot );
-
-    my $html_src_extension =
-      make_extension( $rOpts->{'html-src-extension'}, 'src', $dot );
-
-    # check for -b option;
-    # silently ignore unless beautify mode
-    my $in_place_modify = $rOpts->{'backup-and-modify-in-place'}
-      && $rOpts->{'format'} eq 'tidy';
-
-    # Turn off -b with warnings in case of conflicts with other options.
-    # NOTE: Do this silently, without warnings, if there is a source or
-    # destination stream, or standard output is used.  This is because the -b
-    # flag may have been in a .perltidyrc file and warnings break
-    # Test::NoWarnings.  See email discussion with Merijn Brand 26 Feb 2014.
-    if ($in_place_modify) {
-        if (   $rOpts->{'standard-output'}
-            || $destination_stream
-            || ref $source_stream
-            || $rOpts->{'outfile'}
-            || defined( $rOpts->{'output-path'} ) )
-        {
-            $in_place_modify = 0;
-        }
-    }
+    my $output_extension =
+      $self->make_file_extension( $rOpts->{'output-file-extension'},
+        $default_file_extension{ $rOpts->{'format'} } );
 
-    # Turn off assert-tidy and assert-untidy unless we are tidying files
-    if ( $rOpts->{'format'} ne 'tidy' ) {
-        if ( $rOpts->{'assert-tidy'} ) {
-            $rOpts->{'assert-tidy'} = 0;
-            Warn("ignoring --assert-tidy, --format is not 'tidy'\n");
-        }
-        if ( $rOpts->{'assert-untidy'} ) {
-            $rOpts->{'assert-untidy'} = 0;
-            Warn("ignoring --assert-untidy, --format is not 'tidy'\n");
-        }
-    }
+    # get parameters associated with the -b option
+    my ( $in_place_modify, $backup_extension, $delete_backup ) =
+      $self->check_in_place_modify( $source_stream, $destination_stream );
 
     Perl::Tidy::Formatter::check_options($rOpts);
     Perl::Tidy::Tokenizer::check_options($rOpts);
@@ -896,14 +909,14 @@ EOM
 
     # no filenames should be given if input is from an array
     if ($source_stream) {
-        if ( @ARGV > 0 ) {
+        if ( @Arg_files > 0 ) {
             Die(
 "You may not specify any filenames when a source array is given\n"
             );
         }
 
-        # we'll stuff the source array into ARGV
-        unshift( @ARGV, $source_stream );
+        # we'll stuff the source array into Arg_files
+        unshift( @Arg_files, $source_stream );
 
         # No special treatment for source stream which is a filename.
         # This will enable checks for binary files and other bad stuff.
@@ -912,7 +925,7 @@ EOM
 
     # use stdin by default if no source array and no args
     else {
-        unshift( @ARGV, '-' ) unless @ARGV;
+        unshift( @Arg_files, '-' ) unless @Arg_files;
     }
 
     # Flag for loading module Unicode::GCString for evaluating text width:
@@ -920,330 +933,708 @@ EOM
     #       0 = do not use; failed to load or not wanted
     #       1 = successfully loaded and ok to use
     # The module is not actually loaded unless/until it is needed
-    my $loaded_unicode_gcstring;
     if ( !$rOpts->{'use-unicode-gcstring'} ) {
         $loaded_unicode_gcstring = 0;
     }
 
-    #---------------------------------------------------------------
-    # Ready to go...
-    # main loop to process all files in argument list
-    #---------------------------------------------------------------
-    my $formatter = undef;
-    my $tokenizer = undef;
-
     # Remove duplicate filenames.  Otherwise, for example if the user entered
     #     perltidy -b myfile.pl myfile.pl
     # the backup version of the original would be lost.
-    if ( @ARGV > 1 ) {
+    if ( @Arg_files > 1 ) {
         my %seen = ();
-        @ARGV = grep { !$seen{$_}++ } @ARGV;
+        @Arg_files = grep { !$seen{$_}++ } @Arg_files;
     }
 
     # If requested, process in order of increasing file size
     # This can significantly reduce perl's virtual memory usage during testing.
-    if ( @ARGV > 1 && $rOpts->{'file-size-order'} ) {
-        @ARGV =
+    if ( @Arg_files > 1 && $rOpts->{'file-size-order'} ) {
+        @Arg_files =
           map  { $_->[0] }
           sort { $a->[1] <=> $b->[1] }
-          map  { [ $_, -e $_ ? -s $_ : 0 ] } @ARGV;
+          map  { [ $_, -e $_ ? -s $_ : 0 ] } @Arg_files;
     }
 
-    my $number_of_files = @ARGV;
-    while ( my $input_file = shift @ARGV ) {
-        my $fileroot;
-        my @input_file_stat;
-        my $display_name;
+    my $logfile_header = make_logfile_header( $rOpts, $config_file,
+        $rraw_options, $Windows_type, $readable_options, );
 
-        #---------------------------------------------------------------
-        # prepare this input stream
-        #---------------------------------------------------------------
-        if ($source_stream) {
-            $fileroot     = "perltidy";
-            $display_name = "<source_stream>";
+    # Store some values needed by lower level routines
+    $self->[_diagnostics_object_] = $diagnostics_object;
+    $self->[_postfilter_]         = $postfilter;
+    $self->[_prefilter_]          = $prefilter;
+    $self->[_user_formatter_]     = $user_formatter;
 
-            # If the source is from an array or string, then .LOG output
-            # is only possible if a logfile stream is specified.  This prevents
-            # unexpected perltidy.LOG files.
-            if ( !defined($logfile_stream) ) {
-                $logfile_stream = Perl::Tidy::DevNull->new();
+    #--------------------------
+    # loop to process all files
+    #--------------------------
+    $self->process_all_files(
 
-                # Likewise for .TEE and .DEBUG output
-            }
-            if ( !defined($teefile_stream) ) {
-                $teefile_stream = Perl::Tidy::DevNull->new();
-            }
-            if ( !defined($debugfile_stream) ) {
-                $debugfile_stream = Perl::Tidy::DevNull->new();
-            }
-        }
-        elsif ( $input_file eq '-' ) {    # '-' indicates input from STDIN
-            $fileroot     = "perltidy";   # root name to use for .ERR, .LOG, etc
-            $display_name = "<stdin>";
-            $in_place_modify = 0;
-        }
-        else {
-            $fileroot     = $input_file;
-            $display_name = $input_file;
-            unless ( -e $input_file ) {
+        \%input_hash,
+        \@Arg_files,
 
-                # file doesn't exist - check for a file glob
-                if ( $input_file =~ /([\?\*\[\{])/ ) {
+        # filename stuff...
+        $output_extension,
+        $forbidden_file_extensions,
+        $in_place_modify,
+        $backup_extension,
+        $delete_backup,
 
-                    # Windows shell may not remove quotes, so do it
-                    my $input_file = $input_file;
-                    if ( $input_file =~ /^\'(.+)\'$/ ) { $input_file = $1 }
-                    if ( $input_file =~ /^\"(.+)\"$/ ) { $input_file = $1 }
-                    my $pattern = fileglob_to_re($input_file);
-                    my $dh;
-                    if ( opendir( $dh, './' ) ) {
-                        my @files =
-                          grep { /$pattern/ && !-d } readdir($dh);
-                        ##grep { /$pattern/ && !-d $_ } readdir($dh);
-                        closedir($dh);
-                        if (@files) {
-                            unshift @ARGV, @files;
-                            next;
-                        }
-                    }
-                }
-                Warn("skipping file: '$input_file': no matches found\n");
-                next;
-            }
+        # logfile stuff...
+        $logfile_header,
+        $rpending_complaint,
+        $rpending_logfile_message,
 
-            unless ( -f $input_file ) {
-                Warn("skipping file: $input_file: not a regular file\n");
-                next;
-            }
+    );
 
-            # As a safety precaution, skip zero length files.
-            # If for example a source file got clobbered somehow,
-            # the old .tdy or .bak files might still exist so we
-            # shouldn't overwrite them with zero length files.
-            unless ( -s $input_file ) {
-                Warn("skipping file: $input_file: Zero size\n");
-                next;
-            }
+    #-----
+    # Exit
+    #-----
 
-            # And avoid formatting extremely large files. Since perltidy reads
-            # files into memory, trying to process an extremely large file
-            # could cause system problems.
-            my $size_in_mb = ( -s $input_file ) / ( 1024 * 1024 );
-            if ( $size_in_mb > $rOpts->{'maximum-file-size-mb'} ) {
-                $size_in_mb = sprintf( "%0.1f", $size_in_mb );
-                Warn(
-"skipping file: $input_file: size $size_in_mb MB exceeds limit $rOpts->{'maximum-file-size-mb'}; use -mfs=i to change\n"
-                );
-                next;
-            }
+    # Fix for RT #130297: return a true value if anything was written to the
+    # standard error output, even non-fatal warning messages, otherwise return
+    # false.
 
-            unless ( ( -T $input_file ) || $rOpts->{'force-read-binary'} ) {
-                Warn(
-                    "skipping file: $input_file: Non-text (override with -f)\n"
-                );
-                next;
-            }
+    # These exit codes are returned:
+    #  0 = perltidy ran to completion with no errors
+    #  1 = perltidy could not run to completion due to errors
+    #  2 = perltidy ran to completion with error messages
 
-            # we should have a valid filename now
-            $fileroot        = $input_file;
-            @input_file_stat = stat($input_file);
+    # Note that if perltidy is run with multiple files, any single file with
+    # errors or warnings will write a line like
+    #        '## Please see file testing.t.ERR'
+    # to standard output for each file with errors, so the flag will be true,
+    # even if only some of the multiple files may have had errors.
 
-            if ( $OSNAME eq 'VMS' ) {
-                ( $fileroot, $dot ) = check_vms_filename($fileroot);
-            }
+  NORMAL_EXIT:
+    my $ret = $Warn_count ? 2 : 0;
+    return wantarray ? ( $ret, $rstatus ) : $ret;
 
-            # add option to change path here
-            if ( defined( $rOpts->{'output-path'} ) ) {
+  ERROR_EXIT:
+    return wantarray ? ( 1, $rstatus ) : 1;
 
-                my ( $base, $old_path ) = fileparse($fileroot);
-                my $new_path = $rOpts->{'output-path'};
-                unless ( -d $new_path ) {
-                    unless ( mkdir $new_path, 0777 ) {
-                        Die("unable to create directory $new_path: $ERRNO\n");
-                    }
-                }
-                my $path = $new_path;
-                $fileroot = catfile( $path, $base );
-                unless ($fileroot) {
-                    Die(<<EOM);
-------------------------------------------------------------------------
-Problem combining $new_path and $base to make a filename; check -opath
-------------------------------------------------------------------------
-EOM
-                }
-            }
-        }
+} ## end sub perltidy
 
-        # Skip files with same extension as the output files because
-        # this can lead to a messy situation with files like
-        # script.tdy.tdy.tdy ... or worse problems ...  when you
-        # rerun perltidy over and over with wildcard input.
-        if (
-            !$source_stream
-            && (   $input_file =~ /$forbidden_file_extensions/
-                || $input_file eq 'DIAGNOSTICS' )
-          )
-        {
-            Warn("skipping file: $input_file: wrong extension\n");
-            next;
-        }
+sub make_file_extension {
 
-        # the 'source_object' supplies a method to read the input file
-        my $source_object = Perl::Tidy::LineSource->new(
-            input_file               => $input_file,
-            rOpts                    => $rOpts,
-            rpending_logfile_message => $rpending_logfile_message,
-        );
-        next unless ($source_object);
-
-        my $max_iterations      = $rOpts->{'iterations'};
-        my $do_convergence_test = $max_iterations > 1;
-        my $convergence_log_message;
-        my %saw_md5;
-        my $digest_input = 0;
-
-        my $buf = EMPTY_STRING;
-        while ( my $line = $source_object->get_line() ) {
-            $buf .= $line;
-        }
-
-        my $remove_terminal_newline =
-          !$rOpts->{'add-terminal-newline'} && substr( $buf, -1, 1 ) !~ /\n/;
-
-        # Decode the input stream if necessary or requested
-        my $encoding_in              = EMPTY_STRING;
-        my $rOpts_character_encoding = $rOpts->{'character-encoding'};
-        my $encoding_log_message;
-        my $decoded_input_as = EMPTY_STRING;
-        $rstatus->{'char_mode_source'} = 0;
-
-        # Case 1: If Perl is already in a character-oriented mode for this
-        # string rather than a byte-oriented mode.  Normally, this happens if
-        # the caller has decoded a utf8 string before calling perltidy.  But it
-        # could also happen if the user has done some unusual manipulations of
-        # the source.  In any case, we will not attempt to decode it because
-        # that could result in an output string in a different mode.
-        if ( is_char_mode($buf) ) {
-            $encoding_in = "utf8";
-            $rstatus->{'char_mode_source'} = 1;
-        }
-
-        # Case 2. No input stream encoding requested.  This is appropriate
-        # for single-byte encodings like ascii, latin-1, etc
-        elsif ( !$rOpts_character_encoding
-            || $rOpts_character_encoding eq 'none' )
-        {
+    # Make a file extension, adding any leading '.' if necessary.
+    # (the '.' may actually be an '_' under VMS).
+    my ( $self, $extension, $default ) = @_;
 
-            # nothing to do
-        }
+    # '$extension' is the first choice (usually a user entry)
+    # '$default'   is a backup extension
 
-        # Case 3. guess input stream encoding if requested
-        elsif ( lc($rOpts_character_encoding) eq 'guess' ) {
+    $extension = EMPTY_STRING unless defined($extension);
+    $extension =~ s/^\s+//;
+    $extension =~ s/\s+$//;
 
-            # The guessing strategy is simple: use Encode::Guess to guess
-            # an encoding.  If and only if the guess is utf8, try decoding and
-            # use it if successful.  Otherwise, we proceed assuming the
-            # characters are encoded as single bytes (same as if 'none' had
-            # been specified as the encoding).
+    # Use default extension if nothing remains of the first choice
+    #
+    if ( length($extension) == 0 ) {
+        $extension = $default;
+        $extension = EMPTY_STRING unless defined($extension);
+        $extension =~ s/^\s+//;
+        $extension =~ s/\s+$//;
+    }
 
-            # In testing I have found that including additional guess 'suspect'
-            # encodings sometimes works but can sometimes lead to disaster by
-            # using an incorrect decoding.  The user can always specify a
-            # specific input encoding.
-            my $buf_in = $buf;
+    # Only extensions with these leading characters get a '.'
+    # This rule gives the user some freedom.
+    if ( $extension =~ /^[a-zA-Z0-9]/ ) {
+        my $dot = $self->[_file_extension_separator_];
+        $extension = $dot . $extension;
+    }
+    return $extension;
+} ## end sub make_file_extension
 
-            my $decoder = guess_encoding( $buf_in, 'utf8' );
-            if ( ref($decoder) ) {
-                $encoding_in = $decoder->name;
-                if ( $encoding_in ne 'UTF-8' && $encoding_in ne 'utf8' ) {
-                    $encoding_in = EMPTY_STRING;
-                    $buf         = $buf_in;
-                    $encoding_log_message .= <<EOM;
-Guessed encoding '$encoding_in' is not utf8; no encoding will be used
-EOM
-                }
-                else {
+sub check_in_place_modify {
 
-                    eval { $buf = $decoder->decode($buf_in); };
-                    if ($EVAL_ERROR) {
+    my ( $self, $source_stream, $destination_stream ) = @_;
 
-                        $encoding_log_message .= <<EOM;
-Guessed encoding '$encoding_in' but decoding was unsuccessful; no encoding is used
-EOM
+    # get parameters associated with the -b option
+    my $rOpts = $self->[_rOpts_];
 
-                        # Note that a guess failed, but keep going
-                        # This warning can eventually be removed
-                        Warn(
-"file: $input_file: bad guess to decode source as $encoding_in\n"
-                        );
-                        $encoding_in = EMPTY_STRING;
-                        $buf         = $buf_in;
-                    }
-                    else {
-                        $encoding_log_message .= <<EOM;
-Guessed encoding '$encoding_in' successfully decoded
-EOM
-                        $decoded_input_as = $encoding_in;
-                    }
-                }
-            }
-            else {
-                $encoding_log_message .= <<EOM;
-Does not look like utf8 encoded text so processing as raw bytes
-EOM
-            }
-        }
+    # check for -b option;
+    # silently ignore unless beautify mode
+    my $in_place_modify = $rOpts->{'backup-and-modify-in-place'}
+      && $rOpts->{'format'} eq 'tidy';
 
-        # Case 4. Decode with a specific encoding
-        else {
-            $encoding_in = $rOpts_character_encoding;
-            eval {
-                $buf = Encode::decode( $encoding_in, $buf,
-                    Encode::FB_CROAK | Encode::LEAVE_SRC );
-            };
-            if ($EVAL_ERROR) {
+    my ( $backup_extension, $delete_backup );
 
-                # Quit if we cannot decode by the requested encoding;
-                # Something is not right.
-                Warn(
-"skipping file: $display_name: Unable to decode source as $encoding_in\n"
-                );
-                next;
-            }
-            else {
-                $encoding_log_message .= <<EOM;
-Specified encoding '$encoding_in' successfully decoded
-EOM
-                $decoded_input_as = $encoding_in;
-            }
+    # Turn off -b with warnings in case of conflicts with other options.
+    # NOTE: Do this silently, without warnings, if there is a source or
+    # destination stream, or standard output is used.  This is because the -b
+    # flag may have been in a .perltidyrc file and warnings break
+    # Test::NoWarnings.  See email discussion with Merijn Brand 26 Feb 2014.
+    if ($in_place_modify) {
+        if (   $rOpts->{'standard-output'}
+            || $destination_stream
+            || ref $source_stream
+            || $rOpts->{'outfile'}
+            || defined( $rOpts->{'output-path'} ) )
+        {
+            $in_place_modify = 0;
         }
+    }
 
-        # Set the encoding to be used for all further i/o: If we have
-        # decoded the data with any format, then we must continue to
-        # read and write it as encoded data, and we will normalize these
-        # operations with utf8.  If we have not decoded the data, then
-        # we must not treat it as encoded data.
-        my $is_encoded_data = $encoding_in ? 'utf8' : EMPTY_STRING;
-
-        $rstatus->{'input_name'}       = $display_name;
-        $rstatus->{'opt_encoding'}     = $rOpts_character_encoding;
-        $rstatus->{'char_mode_used'}   = $encoding_in ? 1 : 0;
-        $rstatus->{'input_decoded_as'} = $decoded_input_as;
+    if ($in_place_modify) {
 
-        # Define the function to determine the display width of character
-        # strings
-        my $length_function = sub { return length( $_[0] ) };
-        if ($is_encoded_data) {
+        # If the backup extension contains a / character then the backup should
+        # be deleted when the -b option is used.   On older versions of
+        # perltidy this will generate an error message due to an illegal
+        # file name.
+        #
+        # A backup file will still be generated but will be deleted
+        # at the end.  If -bext='/' then this extension will be
+        # the default 'bak'.  Otherwise it will be whatever characters
+        # remains after all '/' characters are removed.  For example:
+        # -bext         extension     slashes
+        #  '/'          bak           1
+        #  '/delete'    delete        1
+        #  'delete/'    delete        1
+        #  '/dev/null'  devnull       2    (Currently not allowed)
+        my $bext = $rOpts->{'backup-file-extension'};
+        $delete_backup = ( $rOpts->{'backup-file-extension'} =~ s/\///g );
+
+        # At present only one forward slash is allowed.  In the future multiple
+        # slashes may be allowed to allow for other options
+        if ( $delete_backup > 1 ) {
+            Die("-bext=$bext contains more than one '/'\n");
+        }
 
-            # Delete any Byte Order Mark (BOM), which can cause trouble
-            $buf =~ s/^\x{FEFF}//;
+        $backup_extension =
+          $self->make_file_extension( $rOpts->{'backup-file-extension'},
+            'bak' );
+    }
 
-            # Try to load Unicode::GCString for defining text display width, if
-            # requested, when the first encoded file is encountered
-            if ( !defined($loaded_unicode_gcstring) ) {
-                eval { require Unicode::GCString };
-                $loaded_unicode_gcstring = !$EVAL_ERROR;
-                if ( $EVAL_ERROR && $rOpts->{'use-unicode-gcstring'} ) {
+    my $backup_method = $rOpts->{'backup-method'};
+    if (   defined($backup_method)
+        && $backup_method ne 'copy'
+        && $backup_method ne 'move' )
+    {
+        Die(
+"Unexpected --backup-method='$backup_method'; must be one of: 'move', 'copy'\n"
+        );
+    }
+
+    return ( $in_place_modify, $backup_extension, $delete_backup );
+}
+
+sub backup_method_copy {
+
+    my ( $self, $input_file, $output_file, $backup_extension, $delete_backup )
+      = @_;
+
+    # Handle the -b (--backup-and-modify-in-place) option with -bm='copy':
+    # - First copy $input file to $backup_name.
+    # - Then open input file and rewrite with contents of $output_file
+    # - Then delete the backup if requested
+
+    # NOTES:
+    # - Die immediately on any error.
+    # - $output_file is actually an ARRAY ref
+
+    my $backup_file = $input_file . $backup_extension;
+
+    unless ( -f $input_file ) {
+
+        # no real file to backup ..
+        # This shouldn't happen because of numerous preliminary checks
+        Die(
+            "problem with -b backing up input file '$input_file': not a file\n"
+        );
+    }
+
+    if ( -f $backup_file ) {
+        unlink($backup_file)
+          or Die(
+"unable to remove previous '$backup_file' for -b option; check permissions: $ERRNO\n"
+          );
+    }
+
+    # Copy input file to backup
+    File::Copy::copy( $input_file, $backup_file )
+      or Die("File::Copy failed trying to backup source: $ERRNO");
+
+    # set permissions of the backup file to match the input file
+    my @input_file_stat = stat($input_file);
+    my $in_place_modify = 1;
+    $self->set_output_file_permissions( $backup_file, \@input_file_stat,
+        $in_place_modify );
+
+    # Open the original input file for writing ... opening with ">" will
+    # truncate the existing data.
+    open( my $fout, ">", $input_file )
+      || Die(
+"problem re-opening $input_file for write for -b option; check file and directory permissions: $ERRNO\n"
+      );
+
+    if ( $self->[_is_encoded_data_] ) {
+        binmode $fout, ":raw:encoding(UTF-8)";
+    }
+
+    # Now copy the formatted output to it..
+
+    # if formatted output is in an ARRAY ref (normally this is true)...
+    if ( ref($output_file) eq 'ARRAY' ) {
+        foreach my $line ( @{$output_file} ) {
+            $fout->print($line)
+              or
+              Die("cannot print to '$input_file' with -b option: $OS_ERROR\n");
+        }
+    }
+
+    # or in a SCALAR ref (less efficient, and only used for testing)
+    elsif ( ref($output_file) eq 'SCALAR' ) {
+        foreach my $line ( split /^/, ${$output_file} ) {
+            $fout->print($line)
+              or
+              Die("cannot print to '$input_file' with -b option: $OS_ERROR\n");
+        }
+    }
+
+    # Error if anything else ...
+    # This can only happen if the output was changed from \@tmp_buff
+    else {
+        my $ref = ref($output_file);
+        Die(<<EOM);
+Programming error: unable to print to '$input_file' with -b option:
+unexpected ref type '$ref'; expecting 'ARRAY' or 'SCALAR'
+EOM
+    }
+
+    $fout->close()
+      or Die("cannot close '$input_file' with -b option: $OS_ERROR\n");
+
+    # Set permissions of the output file to match the input file. This is
+    # necessary even if the inode remains unchanged because suid/sgid bits may
+    # have been reset.
+    $self->set_output_file_permissions( $input_file, \@input_file_stat,
+        $in_place_modify );
+
+    #---------------------------------------------------------
+    # remove the original file for in-place modify as follows:
+    #   $delete_backup=0 never
+    #   $delete_backup=1 only if no errors
+    #   $delete_backup>1 always  : NOT ALLOWED, too risky
+    #---------------------------------------------------------
+    if ( $delete_backup && -f $backup_file ) {
+
+        # Currently, $delete_backup may only be 1. But if a future update
+        # allows a value > 1, then reduce it to 1 if there were warnings.
+        if (   $delete_backup > 1
+            && $self->[_logger_object_]->get_warning_count() )
+        {
+            $delete_backup = 1;
+        }
+
+        # As an added safety precaution, do not delete the source file
+        # if its size has dropped from positive to zero, since this
+        # could indicate a disaster of some kind, including a hardware
+        # failure.  Actually, this could happen if you had a file of
+        # all comments (or pod) and deleted everything with -dac (-dap)
+        # for some reason.
+        if ( !-s $input_file && -s $backup_file && $delete_backup == 1 ) {
+            Warn(
+"output file '$input_file' missing or zero length; original '$backup_file' not deleted\n"
+            );
+        }
+        else {
+            unlink($backup_file)
+              or Die(
+"unable to remove backup file '$backup_file' for -b option; check permissions: $ERRNO\n"
+              );
+        }
+    }
+
+    # Verify that inode is unchanged during development
+    if (DEVEL_MODE) {
+        my @output_file_stat = stat($input_file);
+        my $inode_input      = $input_file_stat[1];
+        my $inode_output     = $output_file_stat[1];
+        if ( $inode_input != $inode_output ) {
+            Fault(<<EOM);
+inode changed with -bm=copy for file '$input_file': inode_input=$inode_input inode_output=$inode_output
+EOM
+        }
+    }
+
+    return;
+} ## end sub backup_method_copy
+
+sub backup_method_move {
+
+    my ( $self, $input_file, $output_file, $backup_extension, $delete_backup )
+      = @_;
+
+    # Handle the -b (--backup-and-modify-in-place) option with -bm='move':
+    # - First move $input file to $backup_name.
+    # - Then copy $output_file to $input_file.
+    # - Then delete the backup if requested
+
+    # NOTES:
+    # - Die immediately on any error.
+    # - $output_file is actually an ARRAY ref
+    # - $input_file permissions will be set by sub set_output_file_permissions
+
+    my $backup_name = $input_file . $backup_extension;
+
+    unless ( -f $input_file ) {
+
+        # oh, oh, no real file to backup ..
+        # shouldn't happen because of numerous preliminary checks
+        Die(
+            "problem with -b backing up input file '$input_file': not a file\n"
+        );
+    }
+    if ( -f $backup_name ) {
+        unlink($backup_name)
+          or Die(
+"unable to remove previous '$backup_name' for -b option; check permissions: $ERRNO\n"
+          );
+    }
+
+    my @input_file_stat = stat($input_file);
+
+    # backup the input file
+    # we use copy for symlinks, move for regular files
+    if ( -l $input_file ) {
+        File::Copy::copy( $input_file, $backup_name )
+          or Die("File::Copy failed trying to backup source: $ERRNO");
+    }
+    else {
+        rename( $input_file, $backup_name )
+          or Die(
+"problem renaming $input_file to $backup_name for -b option: $ERRNO\n"
+          );
+    }
+
+    # Open a file with the original input file name for writing ...
+    my $is_encoded_data = $self->[_is_encoded_data_];
+    my ( $fout, $iname ) =
+      Perl::Tidy::streamhandle( $input_file, 'w', $is_encoded_data );
+    if ( !$fout ) {
+        Die(
+"problem re-opening $input_file for write for -b option; check file and directory permissions: $ERRNO\n"
+        );
+    }
+
+    # Now copy the formatted output to it..
+
+    # if formatted output is in an ARRAY ref ...
+    if ( ref($output_file) eq 'ARRAY' ) {
+        foreach my $line ( @{$output_file} ) {
+            $fout->print($line)
+              or
+              Die("cannot print to '$input_file' with -b option: $OS_ERROR\n");
+        }
+    }
+
+    # or in a SCALAR ref (less efficient, for testing only)
+    elsif ( ref($output_file) eq 'SCALAR' ) {
+        foreach my $line ( split /^/, ${$output_file} ) {
+            $fout->print($line)
+              or
+              Die("cannot print to '$input_file' with -b option: $OS_ERROR\n");
+        }
+    }
+
+    # Error if anything else ...
+    # This can only happen if the output was changed from \@tmp_buff
+    else {
+        my $ref = ref($output_file);
+        Die(<<EOM);
+Programming error: unable to print to '$input_file' with -b option:
+unexpected ref type '$ref'; expecting 'ARRAY' or 'SCALAR'
+EOM
+    }
+
+    $fout->close()
+      or Die("cannot close '$input_file' with -b option: $OS_ERROR\n");
+
+    # set permissions of the output file to match the input file
+    my $in_place_modify = 1;
+    $self->set_output_file_permissions( $input_file, \@input_file_stat,
+        $in_place_modify );
+
+    #---------------------------------------------------------
+    # remove the original file for in-place modify as follows:
+    #   $delete_backup=0 never
+    #   $delete_backup=1 only if no errors
+    #   $delete_backup>1 always  : NOT ALLOWED, too risky
+    #---------------------------------------------------------
+    if ( $delete_backup && -f $backup_name ) {
+
+        # Currently, $delete_backup may only be 1. But if a future update
+        # allows a value > 1, then reduce it to 1 if there were warnings.
+        if (   $delete_backup > 1
+            && $self->[_logger_object_]->get_warning_count() )
+        {
+            $delete_backup = 1;
+        }
+
+        # As an added safety precaution, do not delete the source file
+        # if its size has dropped from positive to zero, since this
+        # could indicate a disaster of some kind, including a hardware
+        # failure.  Actually, this could happen if you had a file of
+        # all comments (or pod) and deleted everything with -dac (-dap)
+        # for some reason.
+        if ( !-s $input_file && -s $backup_name && $delete_backup == 1 ) {
+            Warn(
+"output file '$input_file' missing or zero length; original '$backup_name' not deleted\n"
+            );
+        }
+        else {
+            unlink($backup_name)
+              or Die(
+"unable to remove previous '$backup_name' for -b option; check permissions: $ERRNO\n"
+              );
+        }
+    }
+
+    return;
+
+} ## end sub backup_method_move
+
+sub set_output_file_permissions {
+
+    my ( $self, $output_file, $rinput_file_stat, $in_place_modify ) = @_;
+
+    # Given:
+    #  $output_file      = the file whose permissions we will set
+    #  $rinput_file_stat = the result of stat($input_file)
+    #  $in_place_modify  = true if --backup-and-modify-in-place is set
+
+    my ( $mode_i, $uid_i, $gid_i ) = @{$rinput_file_stat}[ 2, 4, 5 ];
+    my ( $uid_o, $gid_o ) = ( stat($output_file) )[ 4, 5 ];
+    my $input_file_permissions  = $mode_i & oct(7777);
+    my $output_file_permissions = $input_file_permissions;
+
+    #rt128477: avoid inconsistent owner/group and suid/sgid
+    if ( $uid_i != $uid_o || $gid_i != $gid_o ) {
+
+        # try to change owner and group to match input file if
+        # in -b mode.  Note: chown returns number of files
+        # successfully changed.
+        if ( $in_place_modify
+            && chown( $uid_i, $gid_i, $output_file ) )
+        {
+            # owner/group successfully changed
+        }
+        else {
+
+            # owner or group differ: do not copy suid and sgid
+            $output_file_permissions = $mode_i & oct(777);
+            if ( $input_file_permissions != $output_file_permissions ) {
+                Warn(
+"Unable to copy setuid and/or setgid bits for output file '$output_file'\n"
+                );
+            }
+        }
+    }
+
+    # Mark the output file for rw unless we are in -b mode.
+    # Explanation: perltidy does not unlink existing output
+    # files before writing to them, for safety.  If a
+    # designated output file exists and is not writable,
+    # perltidy will halt.  This can prevent a data loss if a
+    # user accidentally enters "perltidy infile -o
+    # important_ro_file", or "perltidy infile -st
+    # >important_ro_file". But it also means that perltidy can
+    # get locked out of rerunning unless it marks its own
+    # output files writable. The alternative, of always
+    # unlinking the designated output file, is less safe and
+    # not always possible, except in -b mode, where there is an
+    # assumption that a previous backup can be unlinked even if
+    # not writable.
+    if ( !$in_place_modify ) {
+        $output_file_permissions |= oct(600);
+    }
+
+    if ( !chmod( $output_file_permissions, $output_file ) ) {
+
+        # couldn't change file permissions
+        my $operm = sprintf "%04o", $output_file_permissions;
+        Warn(
+"Unable to set permissions for output file '$output_file' to $operm\n"
+        );
+    }
+    return;
+} ## end sub set_output_file_permissions
+
+sub get_decoded_string_buffer {
+    my ( $self, $input_file, $display_name, $rpending_logfile_message ) = @_;
+
+    # Decode the input buffer if necessary or requested
+
+    # Given
+    #   $input_file   = the input file or stream
+    #   $display_name = its name to use in error messages
+
+    # Return
+    #   $buf = string buffer with input, decoded from utf8 if necessary
+    #   $is_encoded_data  = true if $buf is decoded from utf8
+    #   $decoded_input_as = true if perltidy decoded input buf
+    #   $encoding_log_message = messages for log file,
+    #   $length_function  = function to use for measuring string width
+
+    # Return nothing on any error; this is a signal to skip this file
+
+    my $rOpts = $self->[_rOpts_];
+
+    my $source_object = Perl::Tidy::LineSource->new(
+        input_file => $input_file,
+        rOpts      => $rOpts,
+    );
+
+    # return nothing if error
+    return unless ($source_object);
+
+    my $buf = EMPTY_STRING;
+    while ( my $line = $source_object->get_line() ) {
+        $buf .= $line;
+    }
+
+    my $encoding_in              = EMPTY_STRING;
+    my $rOpts_character_encoding = $rOpts->{'character-encoding'};
+    my $encoding_log_message;
+    my $decoded_input_as = EMPTY_STRING;
+    $rstatus->{'char_mode_source'} = 0;
+
+    # Case 1: If Perl is already in a character-oriented mode for this
+    # string rather than a byte-oriented mode.  Normally, this happens if
+    # the caller has decoded a utf8 string before calling perltidy.  But it
+    # could also happen if the user has done some unusual manipulations of
+    # the source.  In any case, we will not attempt to decode it because
+    # that could result in an output string in a different mode.
+    if ( is_char_mode($buf) ) {
+        $encoding_in = "utf8";
+        $rstatus->{'char_mode_source'} = 1;
+    }
+
+    # Case 2. No input stream encoding requested.  This is appropriate
+    # for single-byte encodings like ascii, latin-1, etc
+    elsif ( !$rOpts_character_encoding
+        || $rOpts_character_encoding eq 'none' )
+    {
+
+        # nothing to do
+    }
+
+    # Case 3. guess input stream encoding if requested
+    elsif ( lc($rOpts_character_encoding) eq 'guess' ) {
+
+        # The guessing strategy is simple: use Encode::Guess to guess
+        # an encoding.  If and only if the guess is utf8, try decoding and
+        # use it if successful.  Otherwise, we proceed assuming the
+        # characters are encoded as single bytes (same as if 'none' had
+        # been specified as the encoding).
+
+        # In testing I have found that including additional guess 'suspect'
+        # encodings sometimes works but can sometimes lead to disaster by
+        # using an incorrect decoding.  The user can always specify a
+        # specific input encoding.
+        my $buf_in = $buf;
+
+        my $decoder = guess_encoding( $buf_in, 'utf8' );
+        if ( ref($decoder) ) {
+            $encoding_in = $decoder->name;
+            if ( $encoding_in ne 'UTF-8' && $encoding_in ne 'utf8' ) {
+                $encoding_in = EMPTY_STRING;
+                $buf         = $buf_in;
+                $encoding_log_message .= <<EOM;
+Guessed encoding '$encoding_in' is not utf8; no encoding will be used
+EOM
+            }
+            else {
+
+                if ( !eval { $buf = $decoder->decode($buf_in); 1 } ) {
+
+                    $encoding_log_message .= <<EOM;
+Guessed encoding '$encoding_in' but decoding was unsuccessful; no encoding is used
+EOM
+
+                    # Note that a guess failed, but keep going
+                    # This warning can eventually be removed
+                    Warn(
+"file: $display_name: bad guess to decode source as $encoding_in\n"
+                    );
+                    $encoding_in = EMPTY_STRING;
+                    $buf         = $buf_in;
+                }
+                else {
+                    $encoding_log_message .= <<EOM;
+Guessed encoding '$encoding_in' successfully decoded
+EOM
+                    $decoded_input_as = $encoding_in;
+                }
+            }
+        }
+        else {
+            $encoding_log_message .= <<EOM;
+Does not look like utf8 encoded text so processing as raw bytes
+EOM
+        }
+    }
+
+    # Case 4. Decode with a specific encoding
+    else {
+        $encoding_in = $rOpts_character_encoding;
+        if (
+            !eval {
+                $buf = Encode::decode( $encoding_in, $buf,
+                    Encode::FB_CROAK | Encode::LEAVE_SRC );
+                1;
+            }
+          )
+        {
+
+            # Quit if we cannot decode by the requested encoding;
+            # Something is not right.
+            Warn(
+"skipping file: $display_name: Unable to decode source as $encoding_in\n"
+            );
+
+            # return nothing on error
+            return;
+        }
+        else {
+            $encoding_log_message .= <<EOM;
+Specified encoding '$encoding_in' successfully decoded
+EOM
+            $decoded_input_as = $encoding_in;
+        }
+    }
+
+    # Set the encoding to be used for all further i/o: If we have
+    # decoded the data with any format, then we must continue to
+    # read and write it as encoded data, and we will normalize these
+    # operations with utf8.  If we have not decoded the data, then
+    # we must not treat it as encoded data.
+    my $is_encoded_data = $encoding_in ? 'utf8' : EMPTY_STRING;
+    $self->[_is_encoded_data_] = $is_encoded_data;
+
+    # Delete any Byte Order Mark (BOM), which can cause trouble
+    if ($is_encoded_data) {
+        $buf =~ s/^\x{FEFF}//;
+    }
+
+    $rstatus->{'input_name'}       = $display_name;
+    $rstatus->{'opt_encoding'}     = $rOpts_character_encoding;
+    $rstatus->{'char_mode_used'}   = $encoding_in ? 1 : 0;
+    $rstatus->{'input_decoded_as'} = $decoded_input_as;
+
+    # Define the function to determine the display width of character
+    # strings
+    my $length_function = sub { return length( $_[0] ) };
+    if ($is_encoded_data) {
+
+        # Try to load Unicode::GCString for defining text display width, if
+        # requested, when the first encoded file is encountered
+        if ( !defined($loaded_unicode_gcstring) ) {
+            if ( eval { require Unicode::GCString; 1 } ) {
+                $loaded_unicode_gcstring = 1;
+            }
+            else {
+                $loaded_unicode_gcstring = 0;
+                if ( $rOpts->{'use-unicode-gcstring'} ) {
                     Warn(<<EOM);
 ----------------------
 Unable to load Unicode::GCString: $EVAL_ERROR
@@ -1255,49 +1646,241 @@ To prevent this warning message, you can either:
 EOM
                 }
             }
-            if ($loaded_unicode_gcstring) {
-                $length_function = sub {
-                    return Unicode::GCString->new( $_[0] )->columns;
-                };
-                $encoding_log_message .= <<EOM;
+        }
+        if ($loaded_unicode_gcstring) {
+            $length_function = sub {
+                return Unicode::GCString->new( $_[0] )->columns;
+            };
+            $encoding_log_message .= <<EOM;
 Using 'Unicode::GCString' to measure horizontal character widths
 EOM
-                $rstatus->{'gcs_used'} = 1;
-            }
+            $rstatus->{'gcs_used'} = 1;
         }
+    }
+    return (
+        $buf,
+        $is_encoded_data,
+        $decoded_input_as,
+        $encoding_log_message,
+        $length_function,
 
-        # MD5 sum of input file is evaluated before any prefilter
-        my $saved_input_buf;
-        if ( $rOpts->{'assert-tidy'} || $rOpts->{'assert-untidy'} ) {
-            $digest_input    = $md5_hex->($buf);
-            $saved_input_buf = $buf;
+    );
+} ## end sub get_decoded_string_buffer
+
+sub process_all_files {
+
+    my (
+
+        $self,
+        $rinput_hash,
+        $rfiles,
+
+        $output_extension,
+        $forbidden_file_extensions,
+        $in_place_modify,
+        $backup_extension,
+        $delete_backup,
+
+        $logfile_header,
+        $rpending_complaint,
+        $rpending_logfile_message,
+
+    ) = @_;
+
+    # This routine is the main loop to process all files.
+    # Total formatting is done with these layers of subroutines:
+    #   perltidy                - main routine; checks run parameters
+    #  *process_all_files       - main loop to process all files; *THIS LAYER
+    #   process_filter_layer    - do any pre and post processing;
+    #   process_iteration_layer - handle any iterations on formatting
+    #   process_single_case     - solves one formatting problem
+
+    my $rOpts              = $self->[_rOpts_];
+    my $dot                = $self->[_file_extension_separator_];
+    my $diagnostics_object = $self->[_diagnostics_object_];
+
+    my $destination_stream = $rinput_hash->{'destination'};
+    my $errorfile_stream   = $rinput_hash->{'errorfile'};
+    my $logfile_stream     = $rinput_hash->{'logfile'};
+    my $teefile_stream     = $rinput_hash->{'teefile'};
+    my $debugfile_stream   = $rinput_hash->{'debugfile'};
+    my $source_stream      = $rinput_hash->{'source'};
+    my $stderr_stream      = $rinput_hash->{'stderr'};
+
+    my $number_of_files = @{$rfiles};
+    while ( my $input_file = shift @{$rfiles} ) {
+
+        my $fileroot;
+        my @input_file_stat;
+        my $display_name;
+
+        #--------------------------
+        # prepare this input stream
+        #--------------------------
+        if ($source_stream) {
+            $fileroot     = "perltidy";
+            $display_name = "<source_stream>";
+
+            # If the source is from an array or string, then .LOG output
+            # is only possible if a logfile stream is specified.  This prevents
+            # unexpected perltidy.LOG files.
+            if ( !defined($logfile_stream) ) {
+                $logfile_stream = Perl::Tidy::DevNull->new();
+
+                # Likewise for .TEE and .DEBUG output
+            }
+            if ( !defined($teefile_stream) ) {
+                $teefile_stream = Perl::Tidy::DevNull->new();
+            }
+            if ( !defined($debugfile_stream) ) {
+                $debugfile_stream = Perl::Tidy::DevNull->new();
+            }
+        }
+        elsif ( $input_file eq '-' ) {    # '-' indicates input from STDIN
+            $fileroot     = "perltidy";   # root name to use for .ERR, .LOG, etc
+            $display_name = "<stdin>";
+            $in_place_modify = 0;
         }
+        else {
+            $fileroot     = $input_file;
+            $display_name = $input_file;
+            unless ( -e $input_file ) {
 
-        # Prefilters and postfilters: The prefilter is a code reference
-        # that will be applied to the source before tidying, and the
-        # postfilter is a code reference to the result before outputting.
+                # file doesn't exist - check for a file glob
+                if ( $input_file =~ /([\?\*\[\{])/ ) {
 
-        $buf = $prefilter->($buf) if $prefilter;
+                    # Windows shell may not remove quotes, so do it
+                    my $input_file = $input_file;
+                    if ( $input_file =~ /^\'(.+)\'$/ ) { $input_file = $1 }
+                    if ( $input_file =~ /^\"(.+)\"$/ ) { $input_file = $1 }
+                    my $pattern = fileglob_to_re($input_file);
+                    my $dh;
+                    if ( opendir( $dh, './' ) ) {
+                        my @files =
+                          grep { /$pattern/ && !-d } readdir($dh);
+                        closedir($dh);
+                        next unless (@files);
+                        unshift @{$rfiles}, @files;
+                        next;
+                    }
+                }
+                Warn("skipping file: '$input_file': no matches found\n");
+                next;
+            }
 
-        # starting MD5 sum for convergence test is evaluated after any prefilter
-        if ($do_convergence_test) {
-            my $digest = $md5_hex->($buf);
-            $saw_md5{$digest} = 0;
+            unless ( -f $input_file ) {
+                Warn("skipping file: $input_file: not a regular file\n");
+                next;
+            }
+
+            # As a safety precaution, skip zero length files.
+            # If for example a source file got clobbered somehow,
+            # the old .tdy or .bak files might still exist so we
+            # shouldn't overwrite them with zero length files.
+            unless ( -s $input_file ) {
+                Warn("skipping file: $input_file: Zero size\n");
+                next;
+            }
+
+            # And avoid formatting extremely large files. Since perltidy reads
+            # files into memory, trying to process an extremely large file
+            # could cause system problems.
+            my $size_in_mb = ( -s $input_file ) / ( 1024 * 1024 );
+            if ( $size_in_mb > $rOpts->{'maximum-file-size-mb'} ) {
+                $size_in_mb = sprintf( "%0.1f", $size_in_mb );
+                Warn(
+"skipping file: $input_file: size $size_in_mb MB exceeds limit $rOpts->{'maximum-file-size-mb'}; use -mfs=i to change\n"
+                );
+                next;
+            }
+
+            unless ( ( -T $input_file ) || $rOpts->{'force-read-binary'} ) {
+                Warn("skipping file: $input_file: Non-text (override with -f)\n"
+                );
+                next;
+            }
+
+            # Input file must be writable for -b -bm='copy'.  We must catch
+            # this early to prevent encountering trouble after unlinking the
+            # previous backup.
+            if ( $in_place_modify && !-w $input_file ) {
+                my $backup_method = $rOpts->{'backup-method'};
+                if ( defined($backup_method) && $backup_method eq 'copy' ) {
+                    Warn
+"skipping file '$input_file' for -b option: file reported as non-writable\n";
+                    next;
+                }
+            }
+
+            # we should have a valid filename now
+            $fileroot        = $input_file;
+            @input_file_stat = stat($input_file);
+
+            if ( $OSNAME eq 'VMS' ) {
+                ( $fileroot, $dot ) = check_vms_filename($fileroot);
+                $self->[_file_extension_separator_] = $dot;
+            }
+
+            # add option to change path here
+            if ( defined( $rOpts->{'output-path'} ) ) {
+
+                my ( $base, $old_path ) = fileparse($fileroot);
+                my $new_path = $rOpts->{'output-path'};
+                unless ( -d $new_path ) {
+                    unless ( mkdir $new_path, 0777 ) {
+                        Die("unable to create directory $new_path: $ERRNO\n");
+                    }
+                }
+                my $path = $new_path;
+                $fileroot = catfile( $path, $base );
+                unless ($fileroot) {
+                    Die(<<EOM);
+------------------------------------------------------------------------
+Problem combining $new_path and $base to make a filename; check -opath
+------------------------------------------------------------------------
+EOM
+                }
+            }
         }
 
-        $source_object = Perl::Tidy::LineSource->new(
-            input_file               => \$buf,
-            rOpts                    => $rOpts,
-            rpending_logfile_message => $rpending_logfile_message,
-        );
+        # Skip files with same extension as the output files because
+        # this can lead to a messy situation with files like
+        # script.tdy.tdy.tdy ... or worse problems ...  when you
+        # rerun perltidy over and over with wildcard input.
+        if (
+            !$source_stream
+            && (   $input_file =~ /$forbidden_file_extensions/
+                || $input_file eq 'DIAGNOSTICS' )
+          )
+        {
+            Warn("skipping file: $input_file: wrong extension\n");
+            next;
+        }
+
+        # copy source to a string buffer, decoding from utf8 if necessary
+        my (
+            $buf,
+            $is_encoded_data,
+            $decoded_input_as,
+            $encoding_log_message,
+            $length_function,
 
-        # register this file name with the Diagnostics package
+        ) = $self->get_decoded_string_buffer( $input_file, $display_name,
+            $rpending_logfile_message );
+
+        # Skip this file on any error
+        next if ( !defined($buf) );
+
+        # Register this file name with the Diagnostics package, if any.
         $diagnostics_object->set_input_file($input_file)
           if $diagnostics_object;
 
-        #---------------------------------------------------------------
+        # OK: the (possibly decoded) input is now in string $buf. We just need
+        # to to prepare the output and error logger before formatting it.
+
+        #--------------------------
         # prepare the output stream
-        #---------------------------------------------------------------
+        #--------------------------
         my $output_file = undef;
         my $output_name = EMPTY_STRING;
         my $actual_output_extension;
@@ -1307,7 +1890,8 @@ EOM
             if ( $number_of_files <= 1 ) {
 
                 if ( $rOpts->{'standard-output'} ) {
-                    my $msg = "You may not use -o and -st together";
+                    my $saw_pbp = $self->[_saw_pbp_];
+                    my $msg     = "You may not use -o and -st together";
                     $msg .= " (-pbp contains -st; see manual)" if ($saw_pbp);
                     Die("$msg\n");
                 }
@@ -1341,6 +1925,7 @@ EOM
         }
         elsif ( $rOpts->{'standard-output'} ) {
             if ($destination_stream) {
+                my $saw_pbp = $self->[_saw_pbp_];
                 my $msg =
                   "You may not specify a destination array and -st together\n";
                 $msg .= " (-pbp contains -st; see manual)" if ($saw_pbp);
@@ -1370,8 +1955,12 @@ EOM
         }
         else {
             if ($in_place_modify) {
-                $output_file = IO::File->new_tmpfile()
-                  or Die("cannot open temp file for -b option: $ERRNO\n");
+
+                # Send output to a temporary array buffer. This will
+                # allow efficient copying back to the input by
+                # sub backup_and_modify_in_place, below.
+                my @tmp_buff;
+                $output_file = \@tmp_buff;
                 $output_name = $display_name;
             }
             else {
@@ -1386,44 +1975,214 @@ EOM
         $rstatus->{'iteration_count'} = 0;
         $rstatus->{'converged'}       = 0;
 
-        my $fh_tee;
-        my $tee_file = $fileroot . $dot . "TEE";
-        if ($teefile_stream) { $tee_file = $teefile_stream }
-        if (   $rOpts->{'tee-pod'}
-            || $rOpts->{'tee-block-comments'}
-            || $rOpts->{'tee-side-comments'} )
-        {
-            ( $fh_tee, my $tee_filename ) =
-              Perl::Tidy::streamhandle( $tee_file, 'w', $is_encoded_data );
-            if ( !$fh_tee ) {
-                Warn("couldn't open TEE file $tee_file: $ERRNO\n");
-            }
+        #------------------------------------------
+        # initialize the error logger for this file
+        #------------------------------------------
+        my $warning_file = $fileroot . $dot . "ERR";
+        if ($errorfile_stream) { $warning_file = $errorfile_stream }
+        my $log_file = $fileroot . $dot . "LOG";
+        if ($logfile_stream) { $log_file = $logfile_stream }
+
+        my $logger_object = Perl::Tidy::Logger->new(
+            rOpts           => $rOpts,
+            log_file        => $log_file,
+            warning_file    => $warning_file,
+            fh_stderr       => $fh_stderr,
+            display_name    => $display_name,
+            is_encoded_data => $is_encoded_data,
+        );
+        $logger_object->write_logfile_entry($logfile_header);
+        $logger_object->write_logfile_entry($encoding_log_message)
+          if $encoding_log_message;
+
+        # Now we can add any pending messages to the log
+        if ( ${$rpending_logfile_message} ) {
+            $logger_object->write_logfile_entry( ${$rpending_logfile_message} );
+        }
+        if ( ${$rpending_complaint} ) {
+            $logger_object->complain( ${$rpending_complaint} );
         }
 
         my $line_separator = $rOpts->{'output-line-ending'};
         if ( $rOpts->{'preserve-line-endings'} ) {
             $line_separator = find_input_line_ending($input_file);
         }
-
         $line_separator = "\n" unless defined($line_separator);
 
-        # the 'sink_object' knows how to write the output file
-        my ( $sink_object, $postfilter_buffer );
-        my $use_buffer =
+        # additional parameters needed by lower level routines
+        $self->[_actual_output_extension_] = $actual_output_extension;
+        $self->[_debugfile_stream_]        = $debugfile_stream;
+        $self->[_decoded_input_as_]        = $decoded_input_as;
+        $self->[_destination_stream_]      = $destination_stream;
+        $self->[_display_name_]            = $display_name;
+        $self->[_fileroot_]                = $fileroot;
+        $self->[_is_encoded_data_]         = $is_encoded_data;
+        $self->[_length_function_]         = $length_function;
+        $self->[_line_separator_]          = $line_separator;
+        $self->[_logger_object_]           = $logger_object;
+        $self->[_output_file_]             = $output_file;
+        $self->[_teefile_stream_]          = $teefile_stream;
+
+        #----------------------------------------------------------
+        # Do all formatting of this buffer.
+        # Results will go to the selected output file or streams(s)
+        #----------------------------------------------------------
+        $self->process_filter_layer($buf);
+
+        #--------------------------------------------------
+        # Handle the -b option (backup and modify in-place)
+        #--------------------------------------------------
+        if ($in_place_modify) {
+
+            my $backup_method = $rOpts->{'backup-method'};
+
+            # Option 1, -bm='copy': uses newer version in which original is
+            # copied to the backup and rewritten; see git #103.
+            if ( defined($backup_method) && $backup_method eq 'copy' ) {
+                $self->backup_method_copy(
+                    $input_file,       $output_file,
+                    $backup_extension, $delete_backup
+                );
+            }
+
+            # Option 2, -bm='move': uses older version, where original is moved
+            # to the backup and formatted output goes to a new file.
+            else {
+                $self->backup_method_move(
+                    $input_file,       $output_file,
+                    $backup_extension, $delete_backup
+                );
+            }
+            $output_file = $input_file;
+        }
+
+        #-------------------------------------------------------------------
+        # Otherwise set output file ownership and permissions if appropriate
+        #-------------------------------------------------------------------
+        elsif ( $output_file && -f $output_file && !-l $output_file ) {
+            if (@input_file_stat) {
+                if ( $rOpts->{'format'} eq 'tidy' ) {
+                    $self->set_output_file_permissions( $output_file,
+                        \@input_file_stat, $in_place_modify );
+                }
+
+                # else use default permissions for html and any other format
+            }
+        }
+
+        $logger_object->finish()
+          if $logger_object;
+    } ## end of main loop to process all files
+
+    return;
+} ## end sub process_all_files
+
+sub process_filter_layer {
+
+    my ( $self, $buf ) = @_;
+
+    # This is the filter layer of processing.
+    # Do all requested formatting on the string '$buf', including any
+    # pre- and post-processing with filters.
+    # Store the results in the selected output file(s) or stream(s).
+
+    # Total formatting is done with these layers of subroutines:
+    #   perltidy                - main routine; checks run parameters
+    #   process_all_files       - main loop to process all files;
+    #  *process_filter_layer    - do any pre and post processing; *THIS LAYER
+    #   process_iteration_layer - handle any iterations on formatting
+    #   process_single_case     - solves one formatting problem
+
+    # Data Flow in this layer:
+    #  $buf
+    #   -> optional prefilter operation
+    #     -> [ formatting by sub process_iteration_layer ]
+    #       -> ( optional postfilter_buffer for postfilter, other operations )
+    #         -> ( optional destination_buffer for encoding )
+    #           -> final sink_object
+
+    # What is done based on format type:
+    #  utf8 decoding is done for all format types
+    #  prefiltering is applied to all format types
+    #   - because it may be needed to get through the tokenizer
+    #  postfiltering is only done for format='tidy'
+    #   - might cause problems operating on html text
+    #  encoding of decoded output is only done for format='tidy'
+    #   - because html does its own encoding; user formatter does what it wants
+
+    my $rOpts              = $self->[_rOpts_];
+    my $is_encoded_data    = $self->[_is_encoded_data_];
+    my $logger_object      = $self->[_logger_object_];
+    my $output_file        = $self->[_output_file_];
+    my $user_formatter     = $self->[_user_formatter_];
+    my $destination_stream = $self->[_destination_stream_];
+    my $prefilter          = $self->[_prefilter_];
+    my $postfilter         = $self->[_postfilter_];
+    my $decoded_input_as   = $self->[_decoded_input_as_];
+    my $line_separator     = $self->[_line_separator_];
+
+    my $remove_terminal_newline =
+      !$rOpts->{'add-terminal-newline'} && substr( $buf, -1, 1 ) !~ /\n/;
+
+    # vars for postfilter, if used
+    my $use_postfilter_buffer;
+    my $postfilter_buffer;
+
+    # vars for destination buffer, if used
+    my $destination_buffer;
+    my $use_destination_buffer;
+    my $encode_destination_buffer;
+
+    # vars for iterations, if done
+    my $sink_object;
+
+    # vars for checking assertions, if needed
+    my $digest_input = 0;
+    my $saved_input_buf;
+
+    my $ref_destination_stream = ref($destination_stream);
+
+    # Setup vars for postfilter, destination buffer, assertions and sink object
+    # if needed.  These are only used for 'tidy' formatting.
+    if ( $rOpts->{'format'} eq 'tidy' ) {
+
+        # evaluate MD5 sum of input file for assert tests before any prefilter
+        if ( $rOpts->{'assert-tidy'} || $rOpts->{'assert-untidy'} ) {
+            $digest_input    = $md5_hex->($buf);
+            $saved_input_buf = $buf;
+        }
+
+        #-----------------------
+        # Setup postfilter buffer
+        #-----------------------
+        # If we need access to the output for filtering or checking assertions
+        # before writing to its ultimate destination, then we will send it
+        # to a temporary buffer. The variables are:
+        #  $postfilter_buffer     = the buffer to capture the output
+        #  $use_postfilter_buffer = is a postfilter buffer used?
+        # These are used below, just after iterations are made.
+        $use_postfilter_buffer =
              $postfilter
           || $remove_terminal_newline
           || $rOpts->{'assert-tidy'}
           || $rOpts->{'assert-untidy'};
 
-        # Postpone final output to a destination SCALAR or ARRAY ref to allow
-        # possible encoding at the end of processing.
-        my $destination_buffer;
-        my $use_destination_buffer;
-        my $encode_destination_buffer;
-        my $ref_destination_stream = ref($destination_stream);
-        if ( $ref_destination_stream && !$user_formatter ) {
+        #-------------------------
+        # Setup destination_buffer
+        #-------------------------
+        # If the final output destination is not a file, then we might need to
+        # encode the result at the end of processing.  So in this case we will
+        # send the output to a temporary buffer.
+        # The key variables are:
+        #   $destination_buffer        - receives the formatted output
+        #   $use_destination_buffer    - is $destination_buffer used?
+        #   $encode_destination_buffer - encode $destination_buffer?
+        # These are used by sub 'copy_buffer_to_destination', below
+
+        if ($ref_destination_stream) {
             $use_destination_buffer = 1;
             $output_file            = \$destination_buffer;
+            $self->[_output_file_]  = $output_file;
 
             # Strings and arrays use special encoding rules
             if (   $ref_destination_stream eq 'SCALAR'
@@ -1447,641 +2206,558 @@ EOM
             }
         }
 
+        #-------------------------------------------
+        # Make a sink object for the iteration phase
+        #-------------------------------------------
         $sink_object = Perl::Tidy::LineSink->new(
-            output_file    => $use_buffer ? \$postfilter_buffer : $output_file,
-            line_separator => $line_separator,
-            rOpts          => $rOpts,
-            rpending_logfile_message => $rpending_logfile_message,
-            is_encoded_data          => $is_encoded_data,
+            output_file => $use_postfilter_buffer
+            ? \$postfilter_buffer
+            : $output_file,
+            line_separator  => $line_separator,
+            is_encoded_data => $is_encoded_data,
         );
+    }
 
-        #---------------------------------------------------------------
-        # initialize the error logger for this file
-        #---------------------------------------------------------------
-        my $warning_file = $fileroot . $dot . "ERR";
-        if ($errorfile_stream) { $warning_file = $errorfile_stream }
-        my $log_file = $fileroot . $dot . "LOG";
-        if ($logfile_stream) { $log_file = $logfile_stream }
-
-        my $logger_object = Perl::Tidy::Logger->new(
-            rOpts           => $rOpts,
-            log_file        => $log_file,
-            warning_file    => $warning_file,
-            fh_stderr       => $fh_stderr,
-            display_name    => $display_name,
+    #-----------------------------------------------------------------------
+    # Apply any prefilter. The prefilter is a code reference that will be
+    # applied to the source before tokenizing.  Note that we are doing this
+    # for all format types ('tidy', 'html', 'user') because it may be needed
+    # to avoid tokenization errors.
+    #-----------------------------------------------------------------------
+    $buf = $prefilter->($buf) if $prefilter;
+
+    #----------------------------------------------------------------------
+    # Format contents of string '$buf', iterating if requested.
+    # For 'tidy', formatted result will be written to '$sink_object'
+    # For 'html' and 'user', result goes directly to its ultimate destination.
+    #----------------------------------------------------------------------
+    $self->process_iteration_layer( $buf, $sink_object );
+
+    #--------------------------------
+    # Do postfilter buffer processing
+    #--------------------------------
+    if ($use_postfilter_buffer) {
+
+        my $sink_object_post = Perl::Tidy::LineSink->new(
+            output_file     => $output_file,
+            line_separator  => $line_separator,
             is_encoded_data => $is_encoded_data,
         );
-        write_logfile_header(
-            $rOpts,        $logger_object, $config_file,
-            $rraw_options, $Windows_type,  $readable_options,
-        );
-        $logger_object->write_logfile_entry($encoding_log_message)
-          if $encoding_log_message;
 
-        if ( ${$rpending_logfile_message} ) {
-            $logger_object->write_logfile_entry( ${$rpending_logfile_message} );
-        }
-        if ( ${$rpending_complaint} ) {
-            $logger_object->complain( ${$rpending_complaint} );
+        #----------------------------------------------------------------------
+        # Apply any postfilter. The postfilter is a code reference that will be
+        # applied to the source after tidying.
+        #----------------------------------------------------------------------
+        my $buf_post =
+            $postfilter
+          ? $postfilter->($postfilter_buffer)
+          : $postfilter_buffer;
+
+        # Check if file changed if requested, but only after any postfilter
+        if ( $rOpts->{'assert-tidy'} ) {
+            my $digest_output = $md5_hex->($buf_post);
+            if ( $digest_output ne $digest_input ) {
+                my $diff_msg =
+                  compare_string_buffers( $saved_input_buf, $buf_post,
+                    $is_encoded_data );
+                $logger_object->warning(<<EOM);
+assertion failure: '--assert-tidy' is set but output differs from input
+EOM
+                $logger_object->interrupt_logfile();
+                $logger_object->warning( $diff_msg . "\n" );
+                $logger_object->resume_logfile();
+            }
         }
 
-        #---------------------------------------------------------------
-        # initialize the debug object, if any
-        #---------------------------------------------------------------
-        my $debugger_object = undef;
-        if ( $rOpts->{DEBUG} ) {
-            my $debug_file = $fileroot . $dot . "DEBUG";
-            if ($debugfile_stream) { $debug_file = $debugfile_stream }
-            $debugger_object =
-              Perl::Tidy::Debugger->new( $debug_file, $is_encoded_data );
-        }
-
-        #---------------------------------------------------------------
-        # loop over iterations for one source stream
-        #---------------------------------------------------------------
-
-        # save objects to allow redirecting output during iterations
-        my $sink_object_final     = $sink_object;
-        my $debugger_object_final = $debugger_object;
-        my $logger_object_final   = $logger_object;
-        my $fh_tee_final          = $fh_tee;
-        my $iteration_of_formatter_convergence;
-
-        foreach my $iter ( 1 .. $max_iterations ) {
-
-            $rstatus->{'iteration_count'} += 1;
-
-            # send output stream to temp buffers until last iteration
-            my $sink_buffer;
-            if ( $iter < $max_iterations ) {
-                $sink_object = Perl::Tidy::LineSink->new(
-                    output_file              => \$sink_buffer,
-                    line_separator           => $line_separator,
-                    rOpts                    => $rOpts,
-                    rpending_logfile_message => $rpending_logfile_message,
-                    is_encoded_data          => $is_encoded_data,
+        if ( $rOpts->{'assert-untidy'} ) {
+            my $digest_output = $md5_hex->($buf_post);
+            if ( $digest_output eq $digest_input ) {
+                $logger_object->warning(
+"assertion failure: '--assert-untidy' is set but output equals input\n"
                 );
             }
-            else {
-                $sink_object = $sink_object_final;
+        }
+
+        my $source_object = Perl::Tidy::LineSource->new(
+            input_file => \$buf_post,
+            rOpts      => $rOpts,
+        );
+
+        # Copy the filtered buffer to the final destination
+        if ( !$remove_terminal_newline ) {
+            while ( my $line = $source_object->get_line() ) {
+                $sink_object_post->write_line($line);
+            }
+        }
+        else {
+
+            # Copy the filtered buffer but remove the newline char from the
+            # final line
+            my $line;
+            while ( my $next_line = $source_object->get_line() ) {
+                $sink_object_post->write_line($line) if ($line);
+                $line = $next_line;
+            }
+            if ($line) {
+                $sink_object_post->set_line_separator(undef);
+                chomp $line;
+                $sink_object_post->write_line($line);
             }
+        }
+        $sink_object_post->close_output_file();
+        $source_object->close_input_file();
+    }
+
+    #--------------------------------------------------------
+    # Do destination buffer processing, encoding if required.
+    #--------------------------------------------------------
+    if ($use_destination_buffer) {
+        $self->copy_buffer_to_destination( $destination_buffer,
+            $destination_stream, $encode_destination_buffer );
+    }
+    else {
+
+        # output went to a file in 'tidy' mode...
+        if ( $is_encoded_data && $rOpts->{'format'} eq 'tidy' ) {
+            $rstatus->{'output_encoded_as'} = 'UTF-8';
+        }
+    }
+
+    # The final formatted result should now be in the selected output file(s)
+    # or stream(s).
+    return;
+
+} ## end sub process_filter_layer
+
+sub process_iteration_layer {
+
+    my ( $self, $buf, $sink_object ) = @_;
+
+    # This is the iteration layer of processing.
+    # Do all formatting, iterating if requested, on the source string $buf.
+    # Output depends on format type:
+    #   For 'tidy' formatting, output goes to sink object
+    #   For 'html' formatting, output goes to the ultimate destination
+    #   For 'user' formatting, user formatter handles output
+
+    # Total formatting is done with these layers of subroutines:
+    #   perltidy                - main routine; checks run parameters
+    #   process_all_files       - main loop to process all files;
+    #   process_filter_layer    - do any pre and post processing
+    #  *process_iteration_layer - do any iterations on formatting; *THIS LAYER
+    #   process_single_case     - solves one formatting problem
+
+    # Data Flow in this layer:
+    #      $buf -> [ loop over iterations ] -> $sink_object
+
+    # Only 'tidy' formatting can use multiple iterations.
+
+    my $diagnostics_object = $self->[_diagnostics_object_];
+    my $display_name       = $self->[_display_name_];
+    my $fileroot           = $self->[_fileroot_];
+    my $is_encoded_data    = $self->[_is_encoded_data_];
+    my $length_function    = $self->[_length_function_];
+    my $line_separator     = $self->[_line_separator_];
+    my $logger_object      = $self->[_logger_object_];
+    my $rOpts              = $self->[_rOpts_];
+    my $tabsize            = $self->[_tabsize_];
+    my $user_formatter     = $self->[_user_formatter_];
+
+    # create a source object for the buffer
+    my $source_object = Perl::Tidy::LineSource->new(
+        input_file => \$buf,
+        rOpts      => $rOpts,
+    );
+
+    # make a debugger object if requested
+    my $debugger_object;
+    if ( $rOpts->{DEBUG} ) {
+        my $debug_file = $self->[_debugfile_stream_]
+          || $fileroot . $self->make_file_extension('DEBUG');
+        $debugger_object =
+          Perl::Tidy::Debugger->new( $debug_file, $is_encoded_data );
+    }
+
+    # make a tee file handle if requested
+    my $fh_tee;
+    if (   $rOpts->{'tee-pod'}
+        || $rOpts->{'tee-block-comments'}
+        || $rOpts->{'tee-side-comments'} )
+    {
+        my $tee_file = $self->[_teefile_stream_]
+          || $fileroot . $self->make_file_extension('TEE');
+        ( $fh_tee, my $tee_filename ) =
+          Perl::Tidy::streamhandle( $tee_file, 'w', $is_encoded_data );
+        if ( !$fh_tee ) {
+            Warn("couldn't open TEE file $tee_file: $ERRNO\n");
+        }
+    }
+
+    # vars for iterations and convergence test
+    my $max_iterations = 1;
+    my $convergence_log_message;
+    my $do_convergence_test;
+    my %saw_md5;
+
+    # Only 'tidy' formatting can use multiple iterations
+    if ( $rOpts->{'format'} eq 'tidy' ) {
+
+        # check iteration count and quietly fix if necessary:
+        # - iterations option only applies to code beautification mode
+        # - the convergence check should stop most runs on iteration 2, and
+        #   virtually all on iteration 3.  But we'll allow up to 6.
+        $max_iterations = $rOpts->{'iterations'};
+        if ( !defined($max_iterations)
+            || $max_iterations <= 0 )
+        {
+            $max_iterations = 1;
+        }
+        elsif ( $max_iterations > 6 ) {
+            $max_iterations = 6;
+        }
+
+        # get starting MD5 sum for convergence test
+        if ( $max_iterations > 1 ) {
+            $do_convergence_test = 1;
+            my $digest = $md5_hex->($buf);
+            $saw_md5{$digest} = 0;
+        }
+    }
 
-            # Save logger, debugger and tee output only on pass 1 because:
-            # (1) line number references must be to the starting
-            # source, not an intermediate result, and
-            # (2) we need to know if there are errors so we can stop the
-            # iterations early if necessary.
-            # (3) the tee option only works on first pass if comments are also
-            # being deleted.
-
-            if ( $iter > 1 ) {
-                $debugger_object = undef;
-                $logger_object   = undef;
-                $fh_tee          = undef;
-            }
+    # save objects to allow redirecting output during iterations
+    my $sink_object_final   = $sink_object;
+    my $logger_object_final = $logger_object;
+    my $iteration_of_formatter_convergence;
 
-            #------------------------------------------------------------
-            # create a formatter for this file : html writer or
-            # pretty printer
-            #------------------------------------------------------------
+    #---------------------
+    # Loop over iterations
+    #---------------------
+    foreach my $iter ( 1 .. $max_iterations ) {
 
-            # we have to delete any old formatter because, for safety,
-            # the formatter will check to see that there is only one.
-            $formatter = undef;
+        $rstatus->{'iteration_count'} += 1;
 
-            if ($user_formatter) {
-                $formatter = $user_formatter;
-            }
-            elsif ( $rOpts->{'format'} eq 'html' ) {
-                $formatter = Perl::Tidy::HtmlWriter->new(
-                    input_file         => $fileroot,
-                    html_file          => $output_file,
-                    extension          => $actual_output_extension,
-                    html_toc_extension => $html_toc_extension,
-                    html_src_extension => $html_src_extension,
-                );
-            }
-            elsif ( $rOpts->{'format'} eq 'tidy' ) {
-                $formatter = Perl::Tidy::Formatter->new(
-                    logger_object      => $logger_object,
-                    diagnostics_object => $diagnostics_object,
-                    sink_object        => $sink_object,
-                    length_function    => $length_function,
-                    is_encoded_data    => $is_encoded_data,
-                    fh_tee             => $fh_tee,
-                );
-            }
-            else {
-                Die("I don't know how to do -format=$rOpts->{'format'}\n");
-            }
+        # send output stream to temp buffers until last iteration
+        my $sink_buffer;
+        if ( $iter < $max_iterations ) {
+            $sink_object = Perl::Tidy::LineSink->new(
+                output_file     => \$sink_buffer,
+                line_separator  => $line_separator,
+                is_encoded_data => $is_encoded_data,
+            );
+        }
+        else {
+            $sink_object = $sink_object_final;
+        }
 
-            unless ($formatter) {
-                Die("Unable to continue with $rOpts->{'format'} formatting\n");
-            }
+        # Save logger, debugger and tee output only on pass 1 because:
+        # (1) line number references must be to the starting
+        # source, not an intermediate result, and
+        # (2) we need to know if there are errors so we can stop the
+        # iterations early if necessary.
+        # (3) the tee option only works on first pass if comments are also
+        # being deleted.
+        if ( $iter > 1 ) {
+
+            $debugger_object->close_debug_file() if ($debugger_object);
+            $fh_tee->close()                     if ($fh_tee);
+
+            $debugger_object = undef;
+            $logger_object   = undef;
+            $fh_tee          = undef;
+        }
+
+        #---------------------------------
+        # create a formatter for this file
+        #---------------------------------
 
-            #---------------------------------------------------------------
-            # create the tokenizer for this file
-            #---------------------------------------------------------------
-            $tokenizer = undef;                     # must destroy old tokenizer
-            $tokenizer = Perl::Tidy::Tokenizer->new(
-                source_object      => $source_object,
+        my $formatter;
+
+        if ($user_formatter) {
+            $formatter = $user_formatter;
+        }
+        elsif ( $rOpts->{'format'} eq 'html' ) {
+
+            my $html_toc_extension =
+              $self->make_file_extension( $rOpts->{'html-toc-extension'},
+                'toc' );
+
+            my $html_src_extension =
+              $self->make_file_extension( $rOpts->{'html-src-extension'},
+                'src' );
+
+            $formatter = Perl::Tidy::HtmlWriter->new(
+                input_file         => $fileroot,
+                html_file          => $self->[_output_file_],
+                extension          => $self->[_actual_output_extension_],
+                html_toc_extension => $html_toc_extension,
+                html_src_extension => $html_src_extension,
+            );
+        }
+        elsif ( $rOpts->{'format'} eq 'tidy' ) {
+            $formatter = Perl::Tidy::Formatter->new(
                 logger_object      => $logger_object,
-                debugger_object    => $debugger_object,
                 diagnostics_object => $diagnostics_object,
-                tabsize            => $tabsize,
-                rOpts              => $rOpts,
-
-                starting_level      => $rOpts->{'starting-indentation-level'},
-                indent_columns      => $rOpts->{'indent-columns'},
-                look_for_hash_bang  => $rOpts->{'look-for-hash-bang'},
-                look_for_autoloader => $rOpts->{'look-for-autoloader'},
-                look_for_selfloader => $rOpts->{'look-for-selfloader'},
-                trim_qw             => $rOpts->{'trim-qw'},
-                extended_syntax     => $rOpts->{'extended-syntax'},
-
-                continuation_indentation =>
-                  $rOpts->{'continuation-indentation'},
-                outdent_labels => $rOpts->{'outdent-labels'},
+                sink_object        => $sink_object,
+                length_function    => $length_function,
+                is_encoded_data    => $is_encoded_data,
+                fh_tee             => $fh_tee,
             );
+        }
+        else {
+            Die("I don't know how to do -format=$rOpts->{'format'}\n");
+        }
+
+        unless ($formatter) {
+            Die("Unable to continue with $rOpts->{'format'} formatting\n");
+        }
 
-            #---------------------------------------------------------------
-            # now we can do it
-            #---------------------------------------------------------------
-            process_this_file( $tokenizer, $formatter );
+        #-----------------------------------
+        # create the tokenizer for this file
+        #-----------------------------------
+        my $tokenizer = Perl::Tidy::Tokenizer->new(
+            source_object      => $source_object,
+            logger_object      => $logger_object,
+            debugger_object    => $debugger_object,
+            diagnostics_object => $diagnostics_object,
+            tabsize            => $tabsize,
+            rOpts              => $rOpts,
+
+            starting_level      => $rOpts->{'starting-indentation-level'},
+            indent_columns      => $rOpts->{'indent-columns'},
+            look_for_hash_bang  => $rOpts->{'look-for-hash-bang'},
+            look_for_autoloader => $rOpts->{'look-for-autoloader'},
+            look_for_selfloader => $rOpts->{'look-for-selfloader'},
+            trim_qw             => $rOpts->{'trim-qw'},
+            extended_syntax     => $rOpts->{'extended-syntax'},
+
+            continuation_indentation => $rOpts->{'continuation-indentation'},
+            outdent_labels           => $rOpts->{'outdent-labels'},
+        );
 
-            #---------------------------------------------------------------
-            # close the input source and report errors
-            #---------------------------------------------------------------
-            $source_object->close_input_file();
+        #---------------------------------
+        # do processing for this iteration
+        #---------------------------------
+        process_single_case( $tokenizer, $formatter );
 
-            # see if the formatter is converged
-            if (   $max_iterations > 1
-                && !defined($iteration_of_formatter_convergence)
-                && $formatter->can('get_convergence_check') )
-            {
-                if ( $formatter->get_convergence_check() ) {
-                    $iteration_of_formatter_convergence = $iter;
-                    $rstatus->{'converged'} = 1;
-                }
+        #-----------------------------------------
+        # close the input source and report errors
+        #-----------------------------------------
+        $source_object->close_input_file();
+
+        # see if the formatter is converged
+        if (   $max_iterations > 1
+            && !defined($iteration_of_formatter_convergence)
+            && $formatter->can('get_convergence_check') )
+        {
+            if ( $formatter->get_convergence_check() ) {
+                $iteration_of_formatter_convergence = $iter;
+                $rstatus->{'converged'} = 1;
             }
+        }
 
-            # line source for next iteration (if any) comes from the current
-            # temporary output buffer
-            if ( $iter < $max_iterations ) {
+        # line source for next iteration (if any) comes from the current
+        # temporary output buffer
+        if ( $iter < $max_iterations ) {
 
-                $sink_object->close_output_file();
-                $source_object = Perl::Tidy::LineSource->new(
-                    input_file               => \$sink_buffer,
-                    rOpts                    => $rOpts,
-                    rpending_logfile_message => $rpending_logfile_message,
-                );
+            $sink_object->close_output_file();
+            $source_object = Perl::Tidy::LineSource->new(
+                input_file => \$sink_buffer,
+                rOpts      => $rOpts,
+            );
 
-                # stop iterations if errors or converged
-                my $stop_now = $tokenizer->report_tokenization_errors();
-                $stop_now ||= $tokenizer->get_unexpected_error_count();
-                my $stopping_on_error = $stop_now;
-                if ($stop_now) {
-                    $convergence_log_message = <<EOM;
+            # stop iterations if errors or converged
+            my $stop_now = $tokenizer->report_tokenization_errors();
+            $stop_now ||= $tokenizer->get_unexpected_error_count();
+            my $stopping_on_error = $stop_now;
+            if ($stop_now) {
+                $convergence_log_message = <<EOM;
 Stopping iterations because of severe errors.                       
 EOM
-                }
-                elsif ($do_convergence_test) {
+            }
+            elsif ($do_convergence_test) {
 
-                    # stop if the formatter has converged
-                    $stop_now ||= defined($iteration_of_formatter_convergence);
+                # stop if the formatter has converged
+                $stop_now ||= defined($iteration_of_formatter_convergence);
 
-                    my $digest = $md5_hex->($sink_buffer);
-                    if ( !defined( $saw_md5{$digest} ) ) {
-                        $saw_md5{$digest} = $iter;
-                    }
-                    else {
+                my $digest = $md5_hex->($sink_buffer);
+                if ( !defined( $saw_md5{$digest} ) ) {
+                    $saw_md5{$digest} = $iter;
+                }
+                else {
 
-                        # Deja vu, stop iterating
-                        $stop_now = 1;
-                        my $iterm = $iter - 1;
-                        if ( $saw_md5{$digest} != $iterm ) {
-
-                            # Blinking (oscillating) between two or more stable
-                            # end states.  This is unlikely to occur with normal
-                            # parameters, but it can occur in stress testing
-                            # with extreme parameter values, such as very short
-                            # maximum line lengths.  We want to catch and fix
-                            # them when they happen.
-                            $rstatus->{'blinking'} = 1;
-                            $convergence_log_message = <<EOM;
+                    # Deja vu, stop iterating
+                    $stop_now = 1;
+                    my $iterm = $iter - 1;
+                    if ( $saw_md5{$digest} != $iterm ) {
+
+                        # Blinking (oscillating) between two or more stable
+                        # end states.  This is unlikely to occur with normal
+                        # parameters, but it can occur in stress testing
+                        # with extreme parameter values, such as very short
+                        # maximum line lengths.  We want to catch and fix
+                        # them when they happen.
+                        $rstatus->{'blinking'} = 1;
+                        $convergence_log_message = <<EOM;
 BLINKER. Output for iteration $iter same as for $saw_md5{$digest}. 
 EOM
-                            $stopping_on_error ||= $convergence_log_message;
-                            if (DEVEL_MODE) {
-                                print STDERR $convergence_log_message;
-                            }
-                            $diagnostics_object->write_diagnostics(
-                                $convergence_log_message)
-                              if $diagnostics_object;
+                        $stopping_on_error ||= $convergence_log_message;
+                        DEVEL_MODE
+                          && print STDERR $convergence_log_message;
+                        $diagnostics_object->write_diagnostics(
+                            $convergence_log_message)
+                          if $diagnostics_object;
 
 # Uncomment to search for blinking states
 # Warn( "$display_name: blinking; iter $iter same as for $saw_md5{$digest}\n" );
 
-                        }
-                        else {
-                            $convergence_log_message = <<EOM;
+                    }
+                    else {
+                        $convergence_log_message = <<EOM;
 Converged.  Output for iteration $iter same as for iter $iterm.
 EOM
-                            $diagnostics_object->write_diagnostics(
-                                $convergence_log_message)
-                              if $diagnostics_object && $iterm > 2;
-                            $rstatus->{'converged'} = 1;
-                        }
+                        $diagnostics_object->write_diagnostics(
+                            $convergence_log_message)
+                          if $diagnostics_object && $iterm > 2;
+                        $rstatus->{'converged'} = 1;
                     }
-                } ## end if ($do_convergence_test)
+                }
+            } ## end if ($do_convergence_test)
 
-                if ($stop_now) {
+            if ($stop_now) {
 
-                    if (DEVEL_MODE) {
+                if (DEVEL_MODE) {
 
-                        if ( defined($iteration_of_formatter_convergence) ) {
+                    if ( defined($iteration_of_formatter_convergence) ) {
 
-                            # This message cannot appear unless the formatter
-                            # convergence test above is temporarily skipped for
-                            # testing.
-                            if ( $iteration_of_formatter_convergence <
-                                $iter - 1 )
-                            {
-                                print STDERR
-"STRANGE Early conv in $display_name: Stopping on it=$iter, converged in formatter on $iteration_of_formatter_convergence\n";
-                            }
-                        }
-                        elsif ( !$stopping_on_error ) {
+                        # This message cannot appear unless the formatter
+                        # convergence test above is temporarily skipped for
+                        # testing.
+                        if ( $iteration_of_formatter_convergence < $iter - 1 ) {
                             print STDERR
-"STRANGE no conv in $display_name: stopping on it=$iter, but not converged in formatter\n";
+"STRANGE Early conv in $display_name: Stopping on it=$iter, converged in formatter on $iteration_of_formatter_convergence\n";
                         }
                     }
-
-                    # we are stopping the iterations early;
-                    # copy the output stream to its final destination
-                    $sink_object = $sink_object_final;
-                    while ( my $line = $source_object->get_line() ) {
-                        $sink_object->write_line($line);
+                    elsif ( !$stopping_on_error ) {
+                        print STDERR
+"STRANGE no conv in $display_name: stopping on it=$iter, but not converged in formatter\n";
                     }
-                    $source_object->close_input_file();
-                    last;
-                }
-            } ## end if ( $iter < $max_iterations)
-        } ## end loop over iterations for one source file
-
-        # restore objects which have been temporarily undefined
-        # for second and higher iterations
-        $debugger_object = $debugger_object_final;
-        $logger_object   = $logger_object_final;
-        $fh_tee          = $fh_tee_final;
-
-        $logger_object->write_logfile_entry($convergence_log_message)
-          if $convergence_log_message;
-
-        #---------------------------------------------------------------
-        # Perform any postfilter operation
-        #---------------------------------------------------------------
-        if ($use_buffer) {
-            $sink_object->close_output_file();
-            $sink_object = Perl::Tidy::LineSink->new(
-                output_file              => $output_file,
-                line_separator           => $line_separator,
-                rOpts                    => $rOpts,
-                rpending_logfile_message => $rpending_logfile_message,
-                is_encoded_data          => $is_encoded_data,
-            );
-
-            my $buf_post =
-                $postfilter
-              ? $postfilter->($postfilter_buffer)
-              : $postfilter_buffer;
-
-            # Check if file changed if requested, but only after any postfilter
-            if ( $rOpts->{'assert-tidy'} ) {
-                my $digest_output = $md5_hex->($buf_post);
-                if ( $digest_output ne $digest_input ) {
-                    my $diff_msg =
-                      compare_string_buffers( $saved_input_buf, $buf_post,
-                        $is_encoded_data );
-                    $logger_object->warning(<<EOM);
-assertion failure: '--assert-tidy' is set but output differs from input
-EOM
-                    $logger_object->interrupt_logfile();
-                    $logger_object->warning( $diff_msg . "\n" );
-                    $logger_object->resume_logfile();
-                    ## $Warn_count ||= 1;   # logger warning does this now
-                }
-            }
-            if ( $rOpts->{'assert-untidy'} ) {
-                my $digest_output = $md5_hex->($buf_post);
-                if ( $digest_output eq $digest_input ) {
-                    $logger_object->warning(
-"assertion failure: '--assert-untidy' is set but output equals input\n"
-                    );
-                    ## $Warn_count ||= 1;   # logger warning does this now
                 }
-            }
-
-            $source_object = Perl::Tidy::LineSource->new(
-                input_file               => \$buf_post,
-                rOpts                    => $rOpts,
-                rpending_logfile_message => $rpending_logfile_message,
-            );
 
-            # Copy the filtered buffer to the final destination
-            if ( !$remove_terminal_newline ) {
+                # we are stopping the iterations early;
+                # copy the output stream to its final destination
+                $sink_object = $sink_object_final;
                 while ( my $line = $source_object->get_line() ) {
                     $sink_object->write_line($line);
                 }
+                $source_object->close_input_file();
+                last;
             }
-            else {
-
-                # Copy the filtered buffer but remove the newline char from the
-                # final line
-                my $line;
-                while ( my $next_line = $source_object->get_line() ) {
-                    $sink_object->write_line($line) if ($line);
-                    $line = $next_line;
-                }
-                if ($line) {
-                    $sink_object->set_line_separator(undef);
-                    chomp $line;
-                    $sink_object->write_line($line);
-                }
-            }
+        } ## end if ( $iter < $max_iterations)
+    } ## end loop over iterations for one source file
 
-            $source_object->close_input_file();
-        }
+    $sink_object->close_output_file()    if $sink_object;
+    $debugger_object->close_debug_file() if $debugger_object;
+    $fh_tee->close()                     if $fh_tee;
 
-        #------------------------------------------------------------------
-        # For string output, store the result to the destination, encoding
-        # if requested. This is a fix for issue git #83 (tidyall issue)
-        #------------------------------------------------------------------
-        if ($use_destination_buffer) {
+    # leave logger object open for additional messages
+    $logger_object = $logger_object_final;
+    $logger_object->write_logfile_entry($convergence_log_message)
+      if $convergence_log_message;
 
-            # At this point, all necessary encoding has been done except for
-            # output to a string or array ref. We use the -eos flag to decide
-            # if we should encode.
+    return;
 
-            # -neos, DEFAULT: perltidy does not return encoded string output.
-            # This is a result of the code evolution but not very convenient for
-            # most applications.  It would be hard to change without breaking
-            # some programs.
+} ## end sub process_iteration_layer
 
-            # -eos flag set: If perltidy decodes a string, regardless of
-            # source, it encodes before returning.
-            $rstatus->{'output_encoded_as'} = EMPTY_STRING;
+sub process_single_case {
 
-            if ($encode_destination_buffer) {
-                my $encoded_buffer;
-                eval {
-                    $encoded_buffer =
-                      Encode::encode( "UTF-8", $destination_buffer,
-                        Encode::FB_CROAK | Encode::LEAVE_SRC );
-                };
-                if ($EVAL_ERROR) {
+    # run the formatter on a single defined case
+    my ( $tokenizer, $formatter ) = @_;
 
-                    Warn(
-"Error attempting to encode output string ref; encoding not done\n"
-                    );
-                }
-                else {
-                    $destination_buffer = $encoded_buffer;
-                    $rstatus->{'output_encoded_as'} = 'UTF-8';
-                }
-            }
+    # Total formatting is done with these layers of subroutines:
+    #   perltidy                - main routine; checks run parameters
+    #   process_all_files       - main loop to process all files;
+    #   process_filter_layer    - do any pre and post processing;
+    #   process_iteration_layer - do any iterations on formatting
+    #  *process_single_case     - solve one formatting problem; *THIS LAYER
 
-            # Send data for SCALAR, ARRAY & OBJ refs to its final destination
-            if ( ref($destination_stream) eq 'SCALAR' ) {
-                ${$destination_stream} = $destination_buffer;
-            }
-            elsif ($destination_buffer) {
-                my @lines = split /^/, $destination_buffer;
-                if ( ref($destination_stream) eq 'ARRAY' ) {
-                    @{$destination_stream} = @lines;
-                }
+    while ( my $line = $tokenizer->get_line() ) {
+        $formatter->write_line($line);
+    }
+    my $severe_error = $tokenizer->report_tokenization_errors();
 
-                # destination stream must be an object with print method
-                else {
-                    foreach my $line (@lines) {
-                        $destination_stream->print($line);
-                    }
-                    if ( $ref_destination_stream->can('close') ) {
-                        $destination_stream->close();
-                    }
-                }
-            }
-            else {
+    # user-defined formatters are possible, and may not have a
+    # sub 'finish_formatting', so we have to check
+    $formatter->finish_formatting($severe_error)
+      if $formatter->can('finish_formatting');
 
-                # Empty destination buffer not going to a string ... could
-                # happen for example if user deleted all pod or comments
-            }
-        }
-        else {
+    return;
+} ## end sub process_single_case
 
-            # output went to a file ...
-            if ($is_encoded_data) {
-                $rstatus->{'output_encoded_as'} = 'UTF-8';
-            }
-        }
+sub copy_buffer_to_destination {
 
-        # Save names of the input and output files
-        my $ifname = $input_file;
-        my $ofname = $output_file;
+    my ( $self, $destination_buffer, $destination_stream,
+        $encode_destination_buffer )
+      = @_;
 
-        #---------------------------------------------------------------
-        # handle the -b option (backup and modify in-place)
-        #---------------------------------------------------------------
-        if ($in_place_modify) {
-            unless ( -f $input_file ) {
+    # Copy $destination_buffer to the final $destination_stream,
+    # encoding if the flag $encode_destination_buffer is true.
 
-                # oh, oh, no real file to backup ..
-                # shouldn't happen because of numerous preliminary checks
-                Die(
-"problem with -b backing up input file '$input_file': not a file\n"
-                );
-            }
-            my $backup_name = $input_file . $backup_extension;
-            if ( -f $backup_name ) {
-                unlink($backup_name)
-                  or Die(
-"unable to remove previous '$backup_name' for -b option; check permissions: $ERRNO\n"
-                  );
-            }
+    # Data Flow:
+    #    $destination_buffer -> [ encode? ] -> $destination_stream
 
-            # backup the input file
-            # we use copy for symlinks, move for regular files
-            if ( -l $input_file ) {
-                File::Copy::copy( $input_file, $backup_name )
-                  or Die("File::Copy failed trying to backup source: $ERRNO");
-            }
-            else {
-                rename( $input_file, $backup_name )
-                  or Die(
-"problem renaming $input_file to $backup_name for -b option: $ERRNO\n"
-                  );
-            }
-            $ifname = $backup_name;
-
-            # copy the output to the original input file
-            # NOTE: it would be nice to just close $output_file and use
-            # File::Copy::copy here, but in this case $output_file is the
-            # handle of an open nameless temporary file so we would lose
-            # everything if we closed it.
-            seek( $output_file, 0, 0 )
-              or
-              Die("unable to rewind a temporary file for -b option: $ERRNO\n");
+    $rstatus->{'output_encoded_as'} = EMPTY_STRING;
 
-            my ( $fout, $iname ) =
-              Perl::Tidy::streamhandle( $input_file, 'w', $is_encoded_data );
-            if ( !$fout ) {
-                Die(
-"problem re-opening $input_file for write for -b option; check file and directory permissions: $ERRNO\n"
-                );
+    if ($encode_destination_buffer) {
+        my $encoded_buffer;
+        if (
+            !eval {
+                $encoded_buffer =
+                  Encode::encode( "UTF-8", $destination_buffer,
+                    Encode::FB_CROAK | Encode::LEAVE_SRC );
+                1;
             }
+          )
+        {
 
-            my $line;
-            while ( $line = $output_file->getline() ) {
-                $fout->print($line);
-            }
-            $fout->close();
-            $output_file = $input_file;
-            $ofname      = $input_file;
+            Warn(
+"Error attempting to encode output string ref; encoding not done\n"
+            );
         }
-
-        #---------------------------------------------------------------
-        # clean up and report errors
-        #---------------------------------------------------------------
-        $sink_object->close_output_file()    if $sink_object;
-        $debugger_object->close_debug_file() if $debugger_object;
-
-        # set output file permissions
-        if ( $output_file && -f $output_file && !-l $output_file ) {
-            if (@input_file_stat) {
-
-                # Set file ownership and permissions
-                if ( $rOpts->{'format'} eq 'tidy' ) {
-                    my ( $mode_i, $uid_i, $gid_i ) =
-                      @input_file_stat[ 2, 4, 5 ];
-                    my ( $uid_o, $gid_o ) = ( stat($output_file) )[ 4, 5 ];
-                    my $input_file_permissions  = $mode_i & oct(7777);
-                    my $output_file_permissions = $input_file_permissions;
-
-                    #rt128477: avoid inconsistent owner/group and suid/sgid
-                    if ( $uid_i != $uid_o || $gid_i != $gid_o ) {
-
-                        # try to change owner and group to match input file if
-                        # in -b mode.  Note: chown returns number of files
-                        # successfully changed.
-                        if ( $in_place_modify
-                            && chown( $uid_i, $gid_i, $output_file ) )
-                        {
-                            # owner/group successfully changed
-                        }
-                        else {
-
-                            # owner or group differ: do not copy suid and sgid
-                            $output_file_permissions = $mode_i & oct(777);
-                            if ( $input_file_permissions !=
-                                $output_file_permissions )
-                            {
-                                Warn(
-"Unable to copy setuid and/or setgid bits for output file '$output_file'\n"
-                                );
-                            }
-                        }
-                    }
-
-                    # Make the output file for rw unless we are in -b mode.
-                    # Explanation: perltidy does not unlink existing output
-                    # files before writing to them, for safety.  If a
-                    # designated output file exists and is not writable,
-                    # perltidy will halt.  This can prevent a data loss if a
-                    # user accidentally enters "perltidy infile -o
-                    # important_ro_file", or "perltidy infile -st
-                    # >important_ro_file". But it also means that perltidy can
-                    # get locked out of rerunning unless it marks its own
-                    # output files writable. The alternative, of always
-                    # unlinking the designated output file, is less safe and
-                    # not always possible, except in -b mode, where there is an
-                    # assumption that a previous backup can be unlinked even if
-                    # not writable.
-                    if ( !$in_place_modify ) {
-                        $output_file_permissions |= oct(600);
-                    }
-
-                    if ( !chmod( $output_file_permissions, $output_file ) ) {
-
-                        # couldn't change file permissions
-                        my $operm = sprintf "%04o", $output_file_permissions;
-                        Warn(
-"Unable to set permissions for output file '$output_file' to $operm\n"
-                        );
-                    }
-                }
-
-                # else use default permissions for html and any other format
-            }
+        else {
+            $destination_buffer = $encoded_buffer;
+            $rstatus->{'output_encoded_as'} = 'UTF-8';
         }
+    }
 
-        #---------------------------------------------------------------
-        # remove the original file for in-place modify as follows:
-        #   $delete_backup=0 never
-        #   $delete_backup=1 only if no errors
-        #   $delete_backup>1 always  : NOT ALLOWED, too risky, see above
-        #---------------------------------------------------------------
-        if (   $in_place_modify
-            && $delete_backup
-            && -f $ifname
-            && ( $delete_backup > 1 || !$logger_object->get_warning_count() ) )
-        {
+    # Send data for SCALAR, ARRAY & OBJ refs to its final destination
+    if ( ref($destination_stream) eq 'SCALAR' ) {
+        ${$destination_stream} = $destination_buffer;
+    }
+    elsif ($destination_buffer) {
+        my @lines = split /^/, $destination_buffer;
+        if ( ref($destination_stream) eq 'ARRAY' ) {
+            @{$destination_stream} = @lines;
+        }
 
-            # As an added safety precaution, do not delete the source file
-            # if its size has dropped from positive to zero, since this
-            # could indicate a disaster of some kind, including a hardware
-            # failure.  Actually, this could happen if you had a file of
-            # all comments (or pod) and deleted everything with -dac (-dap)
-            # for some reason.
-            if ( !-s $output_file && -s $ifname && $delete_backup == 1 ) {
-                Warn(
-"output file '$output_file' missing or zero length; original '$ifname' not deleted\n"
-                );
+        # destination stream must be an object with print method
+        else {
+            foreach my $line (@lines) {
+                $destination_stream->print($line);
             }
-            else {
-                unlink($ifname)
-                  or Die(
-"unable to remove previous '$ifname' for -b option; check permissions: $ERRNO\n"
-                  );
+            my $ref_destination_stream = ref($destination_stream);
+            if ( $ref_destination_stream->can('close') ) {
+                $destination_stream->close();
             }
         }
+    }
+    else {
 
-        $logger_object->finish($formatter)
-          if $logger_object;
-    } ## end of main loop to process all files
-
-    # Fix for RT #130297: return a true value if anything was written to the
-    # standard error output, even non-fatal warning messages, otherwise return
-    # false.
-
-    # These exit codes are returned:
-    #  0 = perltidy ran to completion with no errors
-    #  1 = perltidy could not run to completion due to errors
-    #  2 = perltidy ran to completion with error messages
-
-    # Note that if perltidy is run with multiple files, any single file with
-    # errors or warnings will write a line like
-    #        '## Please see file testing.t.ERR'
-    # to standard output for each file with errors, so the flag will be true,
-    # even if only some of the multiple files may have had errors.
-
-  NORMAL_EXIT:
-    my $ret = $Warn_count ? 2 : 0;
-    return wantarray ? ( $ret, $rstatus ) : $ret;
-
-  ERROR_EXIT:
-    return wantarray ? ( 1, $rstatus ) : 1;
+        # Empty destination buffer not going to a string ... could
+        # happen for example if user deleted all pod or comments
+    }
+    return;
+} ## end sub copy_buffer_to_destination
 
-} ## end sub perltidy
 } ## end of closure for sub perltidy
 
 sub line_diff {
@@ -2226,46 +2902,6 @@ EOM
     return $msg;
 } ## end sub compare_string_buffers
 
-sub get_stream_as_named_file {
-
-    # Return the name of a file containing a stream of data, creating
-    # a temporary file if necessary.
-    # Given:
-    #  $stream - the name of a file or stream
-    # Returns:
-    #  $fname = name of file if possible, or undef
-    #  $if_tmpfile = true if temp file, undef if not temp file
-    #
-    # NOTE: This routine was previously needed for passing actual files to Perl
-    # for a syntax check. It is not currently used.
-    my ($stream) = @_;
-    my $is_tmpfile;
-    my $fname;
-    if ($stream) {
-        if ( ref($stream) ) {
-            my ( $fh_stream, $fh_name ) =
-              Perl::Tidy::streamhandle( $stream, 'r' );
-            if ($fh_stream) {
-                my ( $fout, $tmpnam ) = File::Temp::tempfile();
-                if ($fout) {
-                    $fname      = $tmpnam;
-                    $is_tmpfile = 1;
-                    binmode $fout;
-                    while ( my $line = $fh_stream->getline() ) {
-                        $fout->print($line);
-                    }
-                    $fout->close();
-                }
-                $fh_stream->close();
-            }
-        }
-        elsif ( $stream ne '-' && -f $stream ) {
-            $fname = $stream;
-        }
-    }
-    return ( $fname, $is_tmpfile );
-} ## end sub get_stream_as_named_file
-
 sub fileglob_to_re {
 
     # modified (corrected) from version in find2perl
@@ -2276,61 +2912,48 @@ sub fileglob_to_re {
     return "^$x\\z";               # match whole word
 }
 
-sub make_extension {
-
-    # Make a file extension, including any leading '.' if necessary
-    # The '.' may actually be an '_' under VMS
-    my ( $extension, $default, $dot ) = @_;
-
-    # Use the default if none specified
-    $extension = $default unless ($extension);
-
-    # Only extensions with these leading characters get a '.'
-    # This rule gives the user some freedom
-    if ( $extension =~ /^[a-zA-Z0-9]/ ) {
-        $extension = $dot . $extension;
-    }
-    return $extension;
-} ## end sub make_extension
-
-sub write_logfile_header {
-    my (
-        $rOpts,        $logger_object, $config_file,
-        $rraw_options, $Windows_type,  $readable_options
-    ) = @_;
+sub make_logfile_header {
+    my ( $rOpts, $config_file, $rraw_options, $Windows_type, $readable_options )
+      = @_;
 
     # Note: the punctuation variable '$]' is not in older versions of
     # English.pm so leave it as is to avoid failing installation tests.
-    $logger_object->write_logfile_entry(
-"perltidy version $VERSION log file on a $OSNAME system, OLD_PERL_VERSION=$]\n"
-    );
+    my $msg =
+"perltidy version $VERSION log file on a $OSNAME system, OLD_PERL_VERSION=$]\n";
     if ($Windows_type) {
-        $logger_object->write_logfile_entry("Windows type is $Windows_type\n");
+        $msg .= "Windows type is $Windows_type\n";
     }
     my $options_string = join( SPACE, @{$rraw_options} );
 
     if ($config_file) {
-        $logger_object->write_logfile_entry(
-            "Found Configuration File >>> $config_file \n");
+        $msg .= "Found Configuration File >>> $config_file \n";
     }
-    $logger_object->write_logfile_entry(
-        "Configuration and command line parameters for this run:\n");
-    $logger_object->write_logfile_entry("$options_string\n");
+    $msg .= "Configuration and command line parameters for this run:\n";
+    $msg .= "$options_string\n";
 
     if ( $rOpts->{'DEBUG'} || $rOpts->{'show-options'} ) {
         $rOpts->{'logfile'} = 1;    # force logfile to be saved
-        $logger_object->write_logfile_entry(
-            "Final parameter set for this run\n");
-        $logger_object->write_logfile_entry(
-            "------------------------------------\n");
+        $msg .= "Final parameter set for this run\n";
+        $msg .= "------------------------------------\n";
 
-        $logger_object->write_logfile_entry($readable_options);
+        $msg .= $readable_options;
 
-        $logger_object->write_logfile_entry(
-            "------------------------------------\n");
+        $msg .= "------------------------------------\n";
     }
-    $logger_object->write_logfile_entry(
-        "To find error messages search for 'WARNING' with your editor\n");
+    $msg .= "To find error messages search for 'WARNING' with your editor\n";
+    return $msg;
+} ## end sub make_logfile_header
+
+sub write_logfile_header {
+    my (
+        $rOpts,        $logger_object, $config_file,
+        $rraw_options, $Windows_type,  $readable_options
+    ) = @_;
+
+    my $msg = make_logfile_header( $rOpts, $config_file,
+        $rraw_options, $Windows_type, $readable_options );
+
+    $logger_object->write_logfile_entry($msg);
     return;
 } ## end sub write_logfile_header
 
@@ -2370,9 +2993,9 @@ sub generate_options {
     #  i.e., -foo and -nofoo are allowed
     # a double dash signals the end of the options list
     #
-    #---------------------------------------------------------------
+    #-----------------------------------------------
     # Define the option string passed to GetOptions.
-    #---------------------------------------------------------------
+    #-----------------------------------------------
 
     my @option_string   = ();
     my %expansion       = ();
@@ -2462,6 +3085,7 @@ sub generate_options {
     ###########################
     $add_option->( 'backup-and-modify-in-place', 'b',     '!' );
     $add_option->( 'backup-file-extension',      'bext',  '=s' );
+    $add_option->( 'backup-method',              'bm',    '=s' );
     $add_option->( 'character-encoding',         'enc',   '=s' );
     $add_option->( 'force-read-binary',          'f',     '!' );
     $add_option->( 'format',                     'fmt',   '=s' );
@@ -2532,11 +3156,15 @@ sub generate_options {
     ########################################
     $category = 3;    # Whitespace control
     ########################################
+    $add_option->( 'add-trailing-commas',                       'atc',   '!' );
     $add_option->( 'add-semicolons',                            'asc',   '!' );
     $add_option->( 'add-whitespace',                            'aws',   '!' );
     $add_option->( 'block-brace-tightness',                     'bbt',   '=i' );
     $add_option->( 'brace-tightness',                           'bt',    '=i' );
     $add_option->( 'delete-old-whitespace',                     'dws',   '!' );
+    $add_option->( 'delete-repeated-commas',                    'drc',   '!' );
+    $add_option->( 'delete-trailing-commas',                    'dtc',   '!' );
+    $add_option->( 'delete-weld-interfering-commas',            'dwic',  '!' );
     $add_option->( 'delete-semicolons',                         'dsm',   '!' );
     $add_option->( 'function-paren-vertical-alignment',         'fpva',  '!' );
     $add_option->( 'keyword-paren-inner-tightness',             'kpit',  '=i' );
@@ -2559,6 +3187,7 @@ sub generate_options {
     $add_option->( 'trim-pod',                                  'trp',   '!' );
     $add_option->( 'want-left-space',                           'wls',   '=s' );
     $add_option->( 'want-right-space',                          'wrs',   '=s' );
+    $add_option->( 'want-trailing-commas',                      'wtc',   '=s' );
     $add_option->( 'space-prototype-paren',                     'spp',   '=i' );
     $add_option->( 'valign-code',                               'vc',    '!' );
     $add_option->( 'valign-block-comments',                     'vbc',   '!' );
@@ -2622,6 +3251,7 @@ sub generate_options {
     $add_option->( 'paren-vertical-tightness-closing',        'pvtc',  '=i' );
     $add_option->( 'weld-nested-containers',                  'wn',    '!' );
     $add_option->( 'weld-nested-exclusion-list',              'wnxl',  '=s' );
+    $add_option->( 'weld-fat-comma',                          'wfc',   '!' );
     $add_option->( 'space-backslash-quote',                   'sbq',   '=i' );
     $add_option->( 'stack-closing-block-brace',               'scbb',  '!' );
     $add_option->( 'stack-closing-hash-brace',                'schb',  '!' );
@@ -2764,9 +3394,9 @@ sub generate_options {
         }
     }
 
-    #---------------------------------------------------------------
+    #---------------------------------------
     # Assign valid ranges to certain options
-    #---------------------------------------------------------------
+    #---------------------------------------
     # In the future, these may be used to make preliminary checks
     # hash keys are long names
     # If key or value is undefined:
@@ -2816,11 +3446,11 @@ sub generate_options {
     # Note: we could actually allow negative ci if someone really wants it:
     # $option_range{'continuation-indentation'} = [ undef, undef ];
 
-    #---------------------------------------------------------------
+    #------------------------------------------------------------------
     # DEFAULTS: Assign default values to the above options here, except
     # for 'outfile' and 'help'.
     # These settings should approximate the perlstyle(1) suggestions.
-    #---------------------------------------------------------------
+    #------------------------------------------------------------------
     my @defaults = qw(
       add-newlines
       add-terminal-newline
@@ -2928,6 +3558,7 @@ sub generate_options {
       timestamp
       trim-qw
       format=tidy
+      backup-method=copy
       backup-file-extension=bak
       code-skipping
       format-skipping
@@ -2940,10 +3571,10 @@ sub generate_options {
 
     push @defaults, "perl-syntax-check-flags=-c -T";
 
-    #---------------------------------------------------------------
+    #-----------------------------------------------------------------------
     # Define abbreviations which will be expanded into the above primitives.
     # These may be defined recursively.
-    #---------------------------------------------------------------
+    #-----------------------------------------------------------------------
     %expansion = (
         %expansion,
         'freeze-newlines'    => [qw(noadd-newlines nodelete-old-newlines)],
@@ -3218,9 +3849,11 @@ sub _process_command_line {
     # breaking old versions of Perl without these routines.
     # Previous configuration is reset at the exit of this routine.
     my $glc;
-    eval { $glc = Getopt::Long::Configure() };
-    unless ($EVAL_ERROR) {
-        eval { Getopt::Long::ConfigDefaults() };
+    if ( eval { $glc = Getopt::Long::Configure(); 1 } ) {
+        my $ok = eval { Getopt::Long::ConfigDefaults(); 1 };
+        if ( !$ok && DEVEL_MODE ) {
+            Fault("Failed call to Getopt::Long::ConfigDefaults: $EVAL_ERROR\n");
+        }
     }
     else { $glc = undef }
 
@@ -3229,9 +3862,9 @@ sub _process_command_line {
         $roption_category, $roption_range
     ) = generate_options();
 
-    #---------------------------------------------------------------
+    #--------------------------------------------------------------
     # set the defaults by passing the above list through GetOptions
-    #---------------------------------------------------------------
+    #--------------------------------------------------------------
     my %Opts = ();
     {
         local @ARGV = ();
@@ -3253,11 +3886,11 @@ sub _process_command_line {
     my $saw_ignore_profile = 0;
     my $saw_dump_profile   = 0;
 
-    #---------------------------------------------------------------
+    #--------------------------------------------------------------
     # Take a first look at the command-line parameters.  Do as many
     # immediate dumps as possible, which can avoid confusion if the
     # perltidyrc file has an error.
-    #---------------------------------------------------------------
+    #--------------------------------------------------------------
     foreach my $i (@ARGV) {
 
         $i =~ s/^--/-/;
@@ -3330,9 +3963,9 @@ sub _process_command_line {
         Exit(1);
     }
 
-    #---------------------------------------------------------------
+    #----------------------------------------
     # read any .perltidyrc configuration file
-    #---------------------------------------------------------------
+    #----------------------------------------
     unless ($saw_ignore_profile) {
 
         # resolve possible conflict between $perltidyrc_stream passed
@@ -3450,9 +4083,9 @@ EOM
         }
     }
 
-    #---------------------------------------------------------------
+    #----------------------------------------
     # now process the command line parameters
-    #---------------------------------------------------------------
+    #----------------------------------------
     expand_command_abbreviations( $rexpansion, \@raw_options, $config_file );
 
     local $SIG{'__WARN__'} = sub { Warn( $_[0] ) };
@@ -3461,7 +4094,12 @@ EOM
     }
 
     # reset Getopt::Long configuration back to its previous value
-    eval { Getopt::Long::Configure($glc) } if defined $glc;
+    if ( defined($glc) ) {
+        my $ok = eval { Getopt::Long::Configure($glc); 1 };
+        if ( !$ok && DEVEL_MODE ) {
+            Fault("Could not reset Getopt::Long configuration: $EVAL_ERROR\n");
+        }
+    }
 
     return ( \%Opts, $config_file, \@raw_options, $roption_string,
         $rexpansion, $roption_category, $roption_range );
@@ -3531,9 +4169,9 @@ sub check_options {
 
     my ( $rOpts, $is_Windows, $Windows_type, $rpending_complaint ) = @_;
 
-    #---------------------------------------------------------------
+    #------------------------------------------------------------
     # check and handle any interactions among the basic options..
-    #---------------------------------------------------------------
+    #------------------------------------------------------------
 
     # Since perltidy only encodes in utf8, problems can occur if we let it
     # decode anything else.  See discussions for issue git #83.
@@ -3576,21 +4214,6 @@ EOM
     # compatibility but is ignored if set.
     $rOpts->{'check-syntax'} = 0;
 
-    # check iteration count and quietly fix if necessary:
-    # - iterations option only applies to code beautification mode
-    # - the convergence check should stop most runs on iteration 2, and
-    #   virtually all on iteration 3.  But we'll allow up to 6.
-    if ( $rOpts->{'format'} ne 'tidy' ) {
-        $rOpts->{'iterations'} = 1;
-    }
-    elsif ( defined( $rOpts->{'iterations'} ) ) {
-        if    ( $rOpts->{'iterations'} <= 0 ) { $rOpts->{'iterations'} = 1 }
-        elsif ( $rOpts->{'iterations'} > 6 )  { $rOpts->{'iterations'} = 6 }
-    }
-    else {
-        $rOpts->{'iterations'} = 1;
-    }
-
     my $check_blank_count = sub {
         my ( $key, $abbrev ) = @_;
         if ( $rOpts->{$key} ) {
@@ -3843,7 +4466,7 @@ sub expand_command_abbreviations {
 
         # update parameter list @ARGV to the new one
         @ARGV = @new_argv;
-        last unless ( $abbrev_count > 0 );
+        last if ( !$abbrev_count );
 
         # make sure we are not in an infinite loop
         if ( $pass_count == $max_passes ) {
@@ -3955,7 +4578,13 @@ sub Win_OS_Type {
 
     # Use the standard API call to determine the version
     my ( $undef, $major, $minor, $build, $id );
-    eval { ( $undef, $major, $minor, $build, $id ) = Win32::GetOSVersion() };
+    my $ok = eval {
+        ( $undef, $major, $minor, $build, $id ) = Win32::GetOSVersion();
+        1;
+    };
+    if ( !$ok && DEVEL_MODE ) {
+        Fault("Could not cal Win32::GetOSVersion(): $EVAL_ERROR\n");
+    }
 
     #
     #    NAME                   ID   MAJOR  MINOR
@@ -3970,13 +4599,13 @@ sub Win_OS_Type {
         1 => {
             0  => "95",
             10 => "98",
-            90 => "Me"
+            90 => "Me",
         },
         2 => {
             0  => "2000",      # or NT 4, see below
             1  => "XP/.Net",
             2  => "Win2003",
-            51 => "NT3.51"
+            51 => "NT3.51",
         }
     }->{$id}->{$minor};
 
@@ -4171,10 +4800,6 @@ sub Win_Config_Locs {
     # Directory, and All Users Directory.  All Users will be empty on a
     # 9x/Me box.  Contributed by: Yves Orton.
 
-    # Original coding:
-    # my $rpending_complaint = shift;
-    # my $os = (@_) ? shift : Win_OS_Type();
-
     my ( $rpending_complaint, $os ) = @_;
     if ( !$os ) { $os = Win_OS_Type(); }
 
@@ -4206,11 +4831,14 @@ sub Win_Config_Locs {
 
 sub dump_config_file {
     my ( $fh, $config_file, $rconfig_file_chatter ) = @_;
-    print STDOUT "$$rconfig_file_chatter";
+    print STDOUT "${$rconfig_file_chatter}";
     if ($fh) {
         print STDOUT "# Dump of file: '$config_file'\n";
         while ( my $line = $fh->getline() ) { print STDOUT $line }
-        eval { $fh->close() };
+        my $ok = eval { $fh->close(); 1 };
+        if ( !$ok && DEVEL_MODE ) {
+            Fault("Could not close file handle(): $EVAL_ERROR\n");
+        }
     }
     else {
         print STDOUT "# ...no config file found\n";
@@ -4316,7 +4944,10 @@ EOM
         $death_message =
 "Didn't see a '}' to match the '{' at line $opening_brace_line in config file '$config_file'\n";
     }
-    eval { $fh->close() };
+    my $ok = eval { $fh->close(); 1 };
+    if ( !$ok && DEVEL_MODE ) {
+        Fault("Could not close file handle(): $EVAL_ERROR\n");
+    }
     return ( \@config_list, $death_message );
 } ## end sub read_config_file
 
@@ -4466,7 +5097,7 @@ sub dump_long_names {
     my @names = @_;
     print STDOUT <<EOM;
 # Command line long names (passed to GetOptions)
-#---------------------------------------------------------------
+#--------------------------------------------------
 # here is a summary of the Getopt codes:
 # <none> does not take an argument
 # =s takes a mandatory string
@@ -4477,7 +5108,7 @@ sub dump_long_names {
 #  i.e., -foo and -nofoo are allowed
 # a double dash signals the end of the options list
 #
-#---------------------------------------------------------------
+#--------------------------------------------------
 EOM
 
     foreach my $name ( sort @names ) { print STDOUT "$name\n" }
@@ -4752,20 +5383,4 @@ EOF
     return;
 } ## end sub usage
 
-sub process_this_file {
-
-    my ( $tokenizer, $formatter ) = @_;
-
-    while ( my $line = $tokenizer->get_line() ) {
-        $formatter->write_line($line);
-    }
-    my $severe_error = $tokenizer->report_tokenization_errors();
-
-    # user-defined formatters are possible, and may not have a
-    # sub 'finish_formatting', so we have to check
-    $formatter->finish_formatting($severe_error)
-      if $formatter->can('finish_formatting');
-
-    return;
-} ## end sub process_this_file
 1;
index bbb6bfe466d676346d5da9ffcfb667c4622c55cd..ae5381d14ed1731842a379a6965d4d009ce4fec5 100644 (file)
@@ -475,7 +475,7 @@ The module 'Perl::Tidy' comes with a binary 'perltidy' which is installed when t
 
 =head1 VERSION
 
-This man page documents Perl::Tidy version 20220613
+This man page documents Perl::Tidy version 20221112
 
 =head1 LICENSE
 
index 5227325d7329d4bf6452d09f1b6eb33cbdc6f64e..caac624ea39697be2b319d1c68b67477dbad7d9c 100644 (file)
@@ -8,7 +8,7 @@ package Perl::Tidy::Debugger;
 use strict;
 use warnings;
 use English qw( -no_match_vars );
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 use constant EMPTY_STRING => q{};
 use constant SPACE        => q{ };
@@ -67,7 +67,6 @@ sub write_debug_entry {
     my $rtoken_type = $line_of_tokens->{_rtoken_type};
     my $rtokens     = $line_of_tokens->{_rtokens};
     my $rlevels     = $line_of_tokens->{_rlevels};
-    my $rblock_type = $line_of_tokens->{_rblock_type};
 
     my $input_line_number = $line_of_tokens->{_line_number};
     my $line_type         = $line_of_tokens->{_line_type};
@@ -76,7 +75,6 @@ sub write_debug_entry {
 
     my $token_str              = "$input_line_number: ";
     my $reconstructed_original = "$input_line_number: ";
-    my $block_str              = "$input_line_number: ";
 
     my $pattern   = EMPTY_STRING;
     my @next_char = ( '"', '"' );
@@ -94,7 +92,6 @@ sub write_debug_entry {
             $pattern .= $rtoken_type->[$j];
         }
         $reconstructed_original .= $rtokens->[$j];
-        $block_str              .= "($rblock_type->[$j])";
         $num = length( $rtokens->[$j] );
         my $type_str = $rtoken_type->[$j];
 
index daa63da670b7d2e5deec035f12d3784b7bb86928..4716271d9779e163361c9ebfd1626fcf560ae92e 100644 (file)
@@ -7,7 +7,7 @@
 package Perl::Tidy::DevNull;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 sub new   { my $self = shift; return bless {}, $self }
 sub print { return }
 sub close { return }
index af81a0cfc3be11ee5ef8c243f8ff691306da712f..a111ce8f1723e33837abf7bc7475cbfe0d790d92 100644 (file)
@@ -21,7 +21,7 @@ package Perl::Tidy::Diagnostics;
 use strict;
 use warnings;
 use English qw( -no_match_vars );
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 use constant EMPTY_STRING => q{};
 
index f16a41126c341c6ed0138ee18a8efa418f41955b..67b7983ac8bb337ef61d5089d142420342dd14d4 100644 (file)
@@ -7,7 +7,7 @@
 package Perl::Tidy::FileWriter;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 use constant DEVEL_MODE   => 0;
 use constant EMPTY_STRING => q{};
@@ -182,11 +182,6 @@ sub get_convergence_check {
     return $self->[_K_arrival_order_matches_] && !@{$rlist};
 }
 
-sub get_K_sequence_error_msg {
-    my ($self) = @_;
-    return $self->[_K_sequence_error_msg_];
-}
-
 sub get_output_line_number {
     return $_[0]->[_output_line_number_];
 }
@@ -305,9 +300,11 @@ $str
 This is probably due to a recent programming change and needs to be fixed.
 EOM
 
+                # Always die during development, this needs to be fixed
                 if (DEVEL_MODE) { Fault($msg) }
 
-                $self->warning($msg);
+                # Otherwise warn if string is not empty (added for b1378)
+                $self->warning($msg) if ( length($str) );
 
                 # Only issue this warning once
                 $self->[_K_sequence_error_msg_] = $msg;
index e55bf05c9aaa10435a19a10acd7eec2dca94f2d3..357b03b1257e0568ba4c72212f2f566dfc5a7672 100644 (file)
@@ -1,4 +1,4 @@
-#####################################################################
+####################################################################
 #
 # The Perl::Tidy::Formatter package adds indentation, whitespace, and
 # line breaks to the token stream
@@ -51,8 +51,9 @@ use constant SPACE        => q{ };
 { #<<< A non-indenting brace to contain all lexical variables
 
 use Carp;
-use English qw( -no_match_vars );
-our $VERSION = '20220613';
+use English    qw( -no_match_vars );
+use List::Util qw( min max );          # min, max are in Perl 5.8
+our $VERSION = '20221112';
 
 # The Tokenizer will be loaded with the Formatter
 ##use Perl::Tidy::Tokenizer;    # for is_keyword()
@@ -125,6 +126,31 @@ EOM
     return;
 } ## end sub Fault
 
+sub Fault_Warn {
+    my ($msg) = @_;
+
+    # This is the same as Fault except that it calls Warn instead of Die
+    # and returns.
+    my ( $package0, $filename0, $line0, $subroutine0 ) = caller(0);
+    my ( $package1, $filename1, $line1, $subroutine1 ) = caller(1);
+    my ( $package2, $filename2, $line2, $subroutine2 ) = caller(2);
+    my $input_stream_name = get_input_stream_name();
+
+    Warn(<<EOM);
+==============================================================================
+While operating on input stream with name: '$input_stream_name'
+A fault was detected at line $line0 of sub '$subroutine1'
+in file '$filename1'
+which was called from line $line1 of sub '$subroutine2'
+Message: '$msg'
+This is probably an error introduced by a recent programming change.
+Perl::Tidy::Formatter.pm reports VERSION='$VERSION'.
+==============================================================================
+EOM
+
+    return;
+} ## end sub Fault_Warn
+
 sub Exit {
     my ($msg) = @_;
     Perl::Tidy::Exit($msg);
@@ -144,6 +170,7 @@ my (
     $rOpts,
     $rOpts_add_newlines,
     $rOpts_add_whitespace,
+    $rOpts_add_trailing_commas,
     $rOpts_blank_lines_after_opening_block,
     $rOpts_block_brace_tightness,
     $rOpts_block_brace_vertical_tightness,
@@ -163,6 +190,8 @@ my (
     $rOpts_delete_closing_side_comments,
     $rOpts_delete_old_whitespace,
     $rOpts_delete_side_comments,
+    $rOpts_delete_trailing_commas,
+    $rOpts_delete_weld_interfering_commas,
     $rOpts_extended_continuation_indentation,
     $rOpts_format_skipping,
     $rOpts_freeze_whitespace,
@@ -188,6 +217,7 @@ my (
     $rOpts_outdent_static_block_comments,
     $rOpts_recombine,
     $rOpts_short_concatenation_item_length,
+    $rOpts_space_prototype_paren,
     $rOpts_stack_closing_block_brace,
     $rOpts_static_block_comments,
     $rOpts_sub_alias_list,
@@ -203,6 +233,7 @@ my (
 
     # Static hashes initialized in a BEGIN block
     %is_assignment,
+    %is_non_list_type,
     %is_if_unless_and_or_last_next_redo_return,
     %is_if_elsif_else_unless_while_until_for_foreach,
     %is_if_unless_while_until_for_foreach,
@@ -291,9 +322,13 @@ my (
     %stack_closing_token,
 
     %weld_nested_exclusion_rules,
+    %weld_fat_comma_rules,
     %line_up_parentheses_control_hash,
     $line_up_parentheses_control_is_lxpl,
 
+    %trailing_comma_rules,
+    $controlled_comma_style,
+
     # regex patterns for text identification.
     # Most are initialized in a sub make_**_pattern during configuration.
     # Most can be configured by user parameters.
@@ -322,6 +357,7 @@ my (
     @maximum_text_length_at_level,
     $stress_level_alpha,
     $stress_level_beta,
+    $high_stress_level,
 
     # Total number of sequence items in a weld, for quick checks
     $total_weld_count,
@@ -387,7 +423,6 @@ BEGIN {
     my $i = 0;
     use constant {
         _rlines_                    => $i++,
-        _rlines_new_                => $i++,
         _rLL_                       => $i++,
         _Klimit_                    => $i++,
         _rdepth_of_opening_seqno_   => $i++,
@@ -402,15 +437,16 @@ BEGIN {
         _K_opening_ternary_         => $i++,
         _K_closing_ternary_         => $i++,
         _K_first_seq_item_          => $i++,
-        _rK_phantom_semicolons_     => $i++,
         _rtype_count_by_seqno_      => $i++,
         _ris_function_call_paren_   => $i++,
         _rlec_count_by_seqno_       => $i++,
         _ris_broken_container_      => $i++,
         _ris_permanently_broken_    => $i++,
+        _rblank_and_comment_count_  => $i++,
         _rhas_list_                 => $i++,
         _rhas_broken_list_          => $i++,
         _rhas_broken_list_with_lec_ => $i++,
+        _rfirst_comma_line_index_   => $i++,
         _rhas_code_block_           => $i++,
         _rhas_broken_code_block_    => $i++,
         _rhas_ternary_              => $i++,
@@ -423,6 +459,7 @@ BEGIN {
         _rparent_of_seqno_          => $i++,
         _rchildren_of_seqno_        => $i++,
         _ris_list_by_seqno_         => $i++,
+        _ris_cuddled_closing_brace_ => $i++,
         _rbreak_container_          => $i++,
         _rshort_nested_             => $i++,
         _length_function_           => $i++,
@@ -493,9 +530,13 @@ BEGIN {
         _ris_essential_old_breakpoint_     => $i++,
         _roverride_cab3_                   => $i++,
         _ris_assigned_structure_           => $i++,
+        _ris_short_broken_eval_block_      => $i++,
+        _ris_bare_trailing_comma_by_seqno_ => $i++,
 
-        _rseqno_non_indenting_brace_by_ix_    => $i++,
-        _rreduce_vertical_tightness_by_seqno_ => $i++,
+        _rseqno_non_indenting_brace_by_ix_ => $i++,
+        _rmax_vertical_tightness_          => $i++,
+
+        _no_vertical_tightness_flags_ => $i++,
 
         _LAST_SELF_INDEX_ => $i - 1,
     };
@@ -519,6 +560,7 @@ BEGIN {
         _rix_seqno_controlling_ci_   => $i++,
         _batch_CODE_type_            => $i++,
         _ri_starting_one_line_block_ => $i++,
+        _runmatched_opening_indexes_ => $i++,
     };
 }
 
@@ -568,6 +610,10 @@ BEGIN {
     );
     @is_assignment{@q} = (1) x scalar(@q);
 
+    # a hash needed by break_lists for efficiency:
+    push @q, qw{ ; < > ~ f };
+    @is_non_list_type{@q} = (1) x scalar(@q);
+
     @q = qw(is if unless and or err last next redo return);
     @is_if_unless_and_or_last_next_redo_return{@q} = (1) x scalar(@q);
 
@@ -744,7 +790,7 @@ sub new {
     initialize_undo_ci();
     initialize_process_line_of_CODE();
     initialize_grind_batch_of_CODE();
-    initialize_final_indentation_adjustment();
+    initialize_get_final_indentation();
     initialize_postponed_breakpoint();
     initialize_batch_variables();
     initialize_write_line();
@@ -754,7 +800,7 @@ sub new {
         file_writer_object => $file_writer_object,
         logger_object      => $logger_object,
         diagnostics_object => $diagnostics_object,
-        length_function    => $length_function
+        length_function    => $length_function,
     );
 
     write_logfile_entry("\nStarting tokenization pass...\n");
@@ -777,8 +823,7 @@ sub new {
     my $self = [];
 
     # Basic data structures...
-    $self->[_rlines_]     = [];    # = ref to array of lines of the file
-    $self->[_rlines_new_] = [];    # = ref to array of output lines
+    $self->[_rlines_] = [];    # = ref to array of lines of the file
 
     # 'rLL' = reference to the continuous liner array of all tokens in a file.
     # 'LL' stands for 'Linked List'. Using a linked list was a disaster, but
@@ -795,9 +840,6 @@ sub new {
     $self->[_K_closing_ternary_]   = {};
     $self->[_K_first_seq_item_]    = undef; # K of first token with a sequence #
 
-    # Array of phantom semicolons, in case we ever need to undo them
-    $self->[_rK_phantom_semicolons_] = undef;
-
     # 'rSS' is the 'Signed Sequence' list, a continuous list of all sequence
     # numbers with + or - indicating opening or closing. This list represents
     # the entire container tree and is invariant under reformatting.  It can be
@@ -820,9 +862,11 @@ sub new {
     $self->[_rlec_count_by_seqno_]       = {};
     $self->[_ris_broken_container_]      = {};
     $self->[_ris_permanently_broken_]    = {};
+    $self->[_rblank_and_comment_count_]  = {};
     $self->[_rhas_list_]                 = {};
     $self->[_rhas_broken_list_]          = {};
     $self->[_rhas_broken_list_with_lec_] = {};
+    $self->[_rfirst_comma_line_index_]   = {};
     $self->[_rhas_code_block_]           = {};
     $self->[_rhas_broken_code_block_]    = {};
     $self->[_rhas_ternary_]              = {};
@@ -835,6 +879,7 @@ sub new {
     $self->[_rparent_of_seqno_]          = {};
     $self->[_rchildren_of_seqno_]        = {};
     $self->[_ris_list_by_seqno_]         = {};
+    $self->[_ris_cuddled_closing_brace_] = {};
 
     $self->[_rbreak_container_] = {};                 # prevent one-line blocks
     $self->[_rshort_nested_]    = {};                 # blocks not forced open
@@ -909,9 +954,13 @@ sub new {
     $self->[_ris_essential_old_breakpoint_]     = {};
     $self->[_roverride_cab3_]                   = {};
     $self->[_ris_assigned_structure_]           = {};
+    $self->[_ris_short_broken_eval_block_]      = {};
+    $self->[_ris_bare_trailing_comma_by_seqno_] = {};
+
+    $self->[_rseqno_non_indenting_brace_by_ix_] = {};
+    $self->[_rmax_vertical_tightness_]          = {};
 
-    $self->[_rseqno_non_indenting_brace_by_ix_]    = {};
-    $self->[_rreduce_vertical_tightness_by_seqno_] = {};
+    $self->[_no_vertical_tightness_flags_] = 0;
 
     # This flag will be updated later by a call to get_save_logfile()
     $self->[_save_logfile_] = defined($logger_object);
@@ -1171,11 +1220,6 @@ sub get_convergence_check {
     return $self->[_converged_];
 }
 
-sub get_added_semicolon_count {
-    my $self = shift;
-    return $self->[_added_semicolon_count_];
-}
-
 sub get_output_line_number {
     my ($self) = @_;
     my $vao = $self->[_vertical_aligner_object_];
@@ -1206,20 +1250,6 @@ sub consecutive_nonblank_lines {
       $vao->get_cached_line_count();
 }
 
-sub max {
-    my (@vals) = @_;
-    my $max = shift @vals;
-    for (@vals) { $max = $_ > $max ? $_ : $max }
-    return $max;
-}
-
-sub min {
-    my (@vals) = @_;
-    my $min = shift @vals;
-    for (@vals) { $min = $_ < $min ? $_ : $min }
-    return $min;
-}
-
 sub split_words {
 
     # given a string containing words separated by whitespace,
@@ -1471,6 +1501,7 @@ EOM
         my @toks = @_;
         foreach my $tok (@toks) {
             if ( $tok eq '?' ) { $tok = ':' }    # patch to coordinate ?/:
+            if ( $tok eq ',' ) { $controlled_comma_style = 1 }
             my $lbs = $left_bond_strength{$tok};
             my $rbs = $right_bond_strength{$tok};
             if ( defined($lbs) && defined($rbs) && $lbs < $rbs ) {
@@ -1484,6 +1515,7 @@ EOM
     my $break_before = sub {
         my @toks = @_;
         foreach my $tok (@toks) {
+            if ( $tok eq ',' ) { $controlled_comma_style = 1 }
             my $lbs = $left_bond_strength{$tok};
             my $rbs = $right_bond_strength{$tok};
             if ( defined($lbs) && defined($rbs) && $rbs < $lbs ) {
@@ -1567,6 +1599,25 @@ EOM
         ##Warn("Increased -ci=n to n=2 for stability with -lp and -vmll\n");
     }
 
+    #-----------------------------------------------------------
+    # The combination -lp -vmll -atc -dtc -wtc=b can be unstable
+    #-----------------------------------------------------------
+    # This fixes b1386 b1387 b1388
+    if (   $rOpts->{'variable-maximum-line-length'}
+        && $rOpts->{'line-up-parentheses'}
+        && $rOpts->{'add-trailing-commas'}
+        && $rOpts->{'delete-trailing-commas'}
+        && $rOpts->{'want-trailing-commas'}
+        && $rOpts->{'want-trailing-commas'} =~ /b/ )
+    {
+        $rOpts->{'delete-trailing-commas'} = 0;
+## warning causes trouble with test cases and this combo is so rare that
+## it is unlikely to not occur in practice.
+##        Warn(
+##"The combination -vmll -lp -atc -dtc -wtc=b can be unstable; turning off -dtc\n"
+##        );
+    }
+
     %container_indentation_options = ();
     foreach my $pair (
         [ 'break-before-hash-brace-and-indent',     '{' ],
@@ -1581,10 +1632,13 @@ EOM
 
             # (1) -lp is not compatible with opt=2, silently set to opt=0
             # (2) opt=0 and 2 give same result if -i=-ci; but opt=0 is faster
+            # (3) set opt=0 if -i < -ci (can be unstable, case b1355)
             if ( $opt == 2 ) {
-                if (   $rOpts->{'line-up-parentheses'}
-                    || $rOpts->{'indent-columns'} ==
-                    $rOpts->{'continuation-indentation'} )
+                if (
+                    $rOpts->{'line-up-parentheses'}
+                    || ( $rOpts->{'indent-columns'} <=
+                        $rOpts->{'continuation-indentation'} )
+                  )
                 {
                     $opt = 0;
                 }
@@ -1689,6 +1743,11 @@ EOM
         '(' => ')',
         '[' => ']',
         '?' => ':',
+
+        '}' => '{',
+        ')' => '(',
+        ']' => '[',
+        ':' => '?',
     );
 
     if ( $rOpts->{'ignore-old-breakpoints'} ) {
@@ -1740,12 +1799,16 @@ EOM
     initialize_keep_old_breakpoints( $rOpts->{'keep-old-breakpoints-after'},
         'kba', \%keep_break_after_type );
 
+    $controlled_comma_style ||= $keep_break_before_type{','};
+    $controlled_comma_style ||= $keep_break_after_type{','};
+
     #------------------------------------------------------------
     # Make global vars for frequently used options for efficiency
     #------------------------------------------------------------
 
-    $rOpts_add_newlines   = $rOpts->{'add-newlines'};
-    $rOpts_add_whitespace = $rOpts->{'add-whitespace'};
+    $rOpts_add_newlines        = $rOpts->{'add-newlines'};
+    $rOpts_add_trailing_commas = $rOpts->{'add-trailing-commas'};
+    $rOpts_add_whitespace      = $rOpts->{'add-whitespace'};
     $rOpts_blank_lines_after_opening_block =
       $rOpts->{'blank-lines-after-opening-block'};
     $rOpts_block_brace_tightness = $rOpts->{'block-brace-tightness'};
@@ -1777,9 +1840,12 @@ EOM
     $rOpts_delete_old_whitespace = $rOpts->{'delete-old-whitespace'};
     $rOpts_extended_continuation_indentation =
       $rOpts->{'extended-continuation-indentation'};
-    $rOpts_delete_side_comments = $rOpts->{'delete-side-comments'};
-    $rOpts_format_skipping      = $rOpts->{'format-skipping'};
-    $rOpts_freeze_whitespace    = $rOpts->{'freeze-whitespace'};
+    $rOpts_delete_side_comments   = $rOpts->{'delete-side-comments'};
+    $rOpts_delete_trailing_commas = $rOpts->{'delete-trailing-commas'};
+    $rOpts_delete_weld_interfering_commas =
+      $rOpts->{'delete-weld-interfering-commas'};
+    $rOpts_format_skipping   = $rOpts->{'format-skipping'};
+    $rOpts_freeze_whitespace = $rOpts->{'freeze-whitespace'};
     $rOpts_function_paren_vertical_alignment =
       $rOpts->{'function-paren-vertical-alignment'};
     $rOpts_fuzzy_line_length      = $rOpts->{'fuzzy-line-length'};
@@ -1810,6 +1876,7 @@ EOM
     $rOpts_recombine = $rOpts->{'recombine'};
     $rOpts_short_concatenation_item_length =
       $rOpts->{'short-concatenation-item-length'};
+    $rOpts_space_prototype_paren     = $rOpts->{'space-prototype-paren'};
     $rOpts_stack_closing_block_brace = $rOpts->{'stack-closing-block-brace'};
     $rOpts_static_block_comments     = $rOpts->{'static-block-comments'};
     $rOpts_sub_alias_list            = $rOpts->{'sub-alias-list'};
@@ -1967,7 +2034,15 @@ EOM
         $stress_level_beta = $level;
     }
 
+    # This is a combined level which works well for turning off formatting
+    # features in most cases:
+    $high_stress_level = min( $stress_level_alpha, $stress_level_beta + 2 );
+
+    %trailing_comma_rules = ();
+    initialize_trailing_comma_rules();
+
     initialize_weld_nested_exclusion_rules();
+    initialize_weld_fat_comma_rules();
 
     %line_up_parentheses_control_hash    = ();
     $line_up_parentheses_control_is_lxpl = 1;
@@ -2181,6 +2256,27 @@ EOM
     return;
 } ## end sub initialize_weld_nested_exclusion_rules
 
+sub initialize_weld_fat_comma_rules {
+
+    # Initialize a hash controlling which opening token types can be
+    # welded around a fat comma
+    %weld_fat_comma_rules = ();
+
+    # The -wfc flag turns on welding of '=>' after an opening paren
+    if ( $rOpts->{'weld-fat-comma'} ) { $weld_fat_comma_rules{'('} = 1 }
+
+    # This could be generalized in the future by introducing a parameter
+    # -weld-fat-comma-after=str (-wfca=str), where str contains any of:
+    #    * { [ (
+    # to indicate which opening parens may weld to a subsequent '=>'
+
+    # The flag -wfc would then be equivalent to -wfca='('
+
+    # This has not been done because it is not yet clear how useful
+    # this generalization would be.
+    return;
+} ## end sub initialize_weld_fat_comma_rules
+
 sub initialize_line_up_parentheses_control_hash {
     my ( $str, $opt_name ) = @_;
     return unless ($str);
@@ -2379,6 +2475,136 @@ EOM
 
 } ## end sub initialize_keep_old_breakpoints
 
+sub initialize_trailing_comma_rules {
+
+    # Setup control hash for trailing commas
+
+    # -wtc=s defines desired trailing comma policy:
+    #
+    #  =" "  stable
+    #        [ both -atc  and -dtc ignored ]
+    #  =0 : none
+    #        [requires -dtc; -atc ignored]
+    #  =1 or * : all
+    #        [requires -atc; -dtc ignored]
+    #  =m : multiline lists require trailing comma
+    #        if -atc set => will add missing multiline trailing commas
+    #        if -dtc set => will delete trailing single line commas
+    #  =b or 'bare' (multiline) lists require trailing comma
+    #        if -atc set => will add missing bare trailing commas
+    #        if -dtc set => will delete non-bare trailing commas
+    #  =h or 'hash': single column stable bare lists require trailing comma
+    #        if -atc set will add these
+    #        if -dtc set will delete other trailing commas
+
+    # This routine must be called after the alpha and beta stress levels
+    # have been defined.
+
+    my $rvalid_flags = [qw(0 1 * m b h i)];
+
+    my $option = $rOpts->{'want-trailing-commas'};
+
+    if ($option) {
+        $option =~ s/^\s+//;
+        $option =~ s/\s+$//;
+    }
+    if ( defined($option) && length($option) ) {
+        my $error_message;
+        my %rule_hash;
+        my @q = @{$rvalid_flags};
+        my %is_valid_flag;
+        @is_valid_flag{@q} = (1) x scalar(@q);
+
+        # handle single character control, such as -wtc='b'
+        if ( length($option) == 1 ) {
+            foreach (qw< ) ] } >) {
+                $rule_hash{$_} = [ $option, EMPTY_STRING ];
+            }
+        }
+
+        # handle multi-character control(s), such as -wtc='[m' or -wtc='k(m'
+        else {
+            my @parts = split /\s+/, $option;
+            foreach my $part (@parts) {
+                if ( length($part) >= 2 && length($part) <= 3 ) {
+                    my $val   = substr( $part, -1, 1 );
+                    my $key_o = substr( $part, -2, 1 );
+                    if ( $is_opening_token{$key_o} ) {
+                        my $paren_flag = EMPTY_STRING;
+                        if ( length($part) == 3 ) {
+                            $paren_flag = substr( $part, 0, 1 );
+                        }
+                        my $key = $matching_token{$key_o};
+                        $rule_hash{$key} = [ $val, $paren_flag ];
+                    }
+                    else {
+                        $error_message .= "Unrecognized term: '$part'\n";
+                    }
+                }
+                else {
+                    $error_message .= "Unrecognized term: '$part'\n";
+                }
+            }
+        }
+
+        # check for valid control characters
+        if ( !$error_message ) {
+            foreach my $key ( keys %rule_hash ) {
+                my $item = $rule_hash{$key};
+                my ( $val, $paren_flag ) = @{$item};
+                if ( $val && !$is_valid_flag{$val} ) {
+                    my $valid_str = join( SPACE, @{$rvalid_flags} );
+                    $error_message .=
+                      "Unexpected value '$val'; must be one of: $valid_str\n";
+                    last;
+                }
+                if ($paren_flag) {
+                    if ( $paren_flag !~ /^[kKfFwW]$/ ) {
+                        $error_message .=
+"Unexpected paren flag '$paren_flag'; must be one of: k K f F w W\n";
+                        last;
+                    }
+                    if ( $key ne ')' ) {
+                        $error_message .=
+"paren flag '$paren_flag' is only allowed before a '('\n";
+                        last;
+                    }
+                }
+            }
+        }
+
+        if ($error_message) {
+            Warn(<<EOM);
+Error parsing --want-trailing-commas='$option':
+$error_message
+EOM
+        }
+
+        # Set the control hash if no errors
+        else {
+            %trailing_comma_rules = %rule_hash;
+        }
+    }
+
+    # Both adding and deleting commas can lead to instability in extreme cases
+    if ( $rOpts_add_trailing_commas && $rOpts_delete_trailing_commas ) {
+
+        # If the possible instability is significant, then we can turn off
+        # -dtc as a defensive measure to prevent it.
+
+        # We must turn off -dtc for very small values of --whitespace-cycle
+        # to avoid instability.  A minimum value of -wc=3 fixes b1393, but a
+        # value of 4 is used here for safety.  This parameter is seldom used,
+        # and much larger than this when used, so the cutoff value is not
+        # critical.
+        if ( $rOpts_whitespace_cycle && $rOpts_whitespace_cycle <= 4 ) {
+            $rOpts_delete_trailing_commas = 0;
+        }
+    }
+
+    return;
+}
+
 sub initialize_whitespace_hashes {
 
     # This is called once before formatting begins to initialize these global
@@ -2509,6 +2735,8 @@ sub initialize_whitespace_hashes {
 
 } ## end sub initialize_whitespace_hashes
 
+{ #<<< begin closure set_whitespace_flags
+
 my %is_special_ws_type;
 my %is_wCUG;
 my %is_wi;
@@ -2517,7 +2745,7 @@ BEGIN {
 
     # The following hash is used to skip over needless if tests.
     # Be sure to update it when adding new checks in its block.
-    my @q = qw(k w C m - Q);
+    my @q = qw(k w C m - Q);
     push @q, '#';
     @is_special_ws_type{@q} = (1) x scalar(@q);
 
@@ -2531,6 +2759,26 @@ BEGIN {
 
 use constant DEBUG_WHITE => 0;
 
+# closure variables
+my (
+
+    $rLL,
+    $jmax,
+
+    $j_tight_closing_paren,
+    $last_token,
+    $token,
+    $type,
+    $ws,
+
+);
+
+# Hashes to set spaces around container tokens according to their
+# sequence numbers.  These are set as keywords are examined.
+# They are controlled by the -kpit and -kpitl flags.
+my %opening_container_inside_ws;
+my %closing_container_inside_ws;
+
 sub set_whitespace_flags {
 
     # This routine is called once per file to set whitespace flags for that
@@ -2547,9 +2795,19 @@ sub set_whitespace_flags {
 
     my $self = shift;
 
-    my $rLL                  = $self->[_rLL_];
+    # initialize closure variables
+    $rLL  = $self->[_rLL_];
+    $jmax = @{$rLL} - 1;
+
+    $j_tight_closing_paren = -1;
+    $token                 = SPACE;
+    $type                  = 'b';
+    $last_token            = EMPTY_STRING;
+
+    %opening_container_inside_ws = ();
+    %closing_container_inside_ws = ();
+
     my $rblock_type_of_seqno = $self->[_rblock_type_of_seqno_];
-    my $jmax                 = @{$rLL} - 1;
 
     my $rOpts_space_keyword_paren   = $rOpts->{'space-keyword-paren'};
     my $rOpts_space_backslash_quote = $rOpts->{'space-backslash-quote'};
@@ -2562,111 +2820,19 @@ sub set_whitespace_flags {
 
     my %is_for_foreach = ( 'for' => 1, 'foreach' => 1 );
 
-    my ( $rtokh, $token, $type );
+    my $rtokh;
     my $rtokh_last      = $rLL->[0];
     my $rtokh_last_last = $rtokh_last;
 
-    my $last_type  = EMPTY_STRING;
-    my $last_token = EMPTY_STRING;
-
-    my $j_tight_closing_paren = -1;
+    my $last_type = EMPTY_STRING;
 
     $rtokh = [ @{ $rLL->[0] } ];
-    $token = SPACE;
-    $type  = 'b';
 
     $rtokh->[_TOKEN_]         = $token;
     $rtokh->[_TYPE_]          = $type;
     $rtokh->[_TYPE_SEQUENCE_] = EMPTY_STRING;
     $rtokh->[_LINE_INDEX_]    = 0;
 
-    # This is some logic moved to a sub to avoid deep nesting of if stmts
-    my $ws_in_container = sub {
-
-        my ($j) = @_;
-        my $ws = WS_YES;
-        if ( $j + 1 > $jmax ) { return (WS_NO) }
-
-        # Patch to count '-foo' as single token so that
-        # each of  $a{-foo} and $a{foo} and $a{'foo'} do
-        # not get spaces with default formatting.
-        my $j_here = $j;
-        ++$j_here
-          if ( $token eq '-'
-            && $last_token eq '{'
-            && $rLL->[ $j + 1 ]->[_TYPE_] eq 'w' );
-
-        # Patch to count a sign separated from a number as a single token, as
-        # in the following line. Otherwise, it takes two steps to converge:
-        #    deg2rad(-  0.5)
-        if (   ( $type eq 'm' || $type eq 'p' )
-            && $j < $jmax + 1
-            && $rLL->[ $j + 1 ]->[_TYPE_] eq 'b'
-            && $rLL->[ $j + 2 ]->[_TYPE_] eq 'n'
-            && $rLL->[ $j + 2 ]->[_TOKEN_] =~ /^\d/ )
-        {
-            $j_here = $j + 2;
-        }
-
-        # $j_next is where a closing token should be if
-        # the container has a single token
-        if ( $j_here + 1 > $jmax ) { return (WS_NO) }
-        my $j_next =
-          ( $rLL->[ $j_here + 1 ]->[_TYPE_] eq 'b' )
-          ? $j_here + 2
-          : $j_here + 1;
-
-        if ( $j_next > $jmax ) { return WS_NO }
-        my $tok_next  = $rLL->[$j_next]->[_TOKEN_];
-        my $type_next = $rLL->[$j_next]->[_TYPE_];
-
-        # for tightness = 1, if there is just one token
-        # within the matching pair, we will keep it tight
-        if (
-            $tok_next eq $matching_token{$last_token}
-
-            # but watch out for this: [ [ ]    (misc.t)
-            && $last_token ne $token
-
-            # double diamond is usually spaced
-            && $token ne '<<>>'
-
-          )
-        {
-
-            # remember where to put the space for the closing paren
-            $j_tight_closing_paren = $j_next;
-            return (WS_NO);
-        }
-        return (WS_YES);
-    };
-
-    # Local hashes to set spaces around container tokens according to their
-    # sequence numbers.  These are set as keywords are examined.
-    # They are controlled by the -kpit and -kpitl flags.
-    my %opening_container_inside_ws;
-    my %closing_container_inside_ws;
-    my $set_container_ws_by_keyword = sub {
-
-        return unless (%keyword_paren_inner_tightness);
-
-        my ( $word, $sequence_number ) = @_;
-
-        # We just saw a keyword (or other function name) followed by an opening
-        # paren. Now check to see if the following paren should have special
-        # treatment for its inside space.  If so we set a hash value using the
-        # sequence number as key.
-        if ( $word && $sequence_number ) {
-            my $tightness = $keyword_paren_inner_tightness{$word};
-            if ( defined($tightness) && $tightness != 1 ) {
-                my $ws_flag = $tightness == 0 ? WS_YES : WS_NO;
-                $opening_container_inside_ws{$sequence_number} = $ws_flag;
-                $closing_container_inside_ws{$sequence_number} = $ws_flag;
-            }
-        }
-        return;
-    };
-
     my ( $ws_1, $ws_2, $ws_3, $ws_4 );
 
     # main loop over all tokens to define the whitespace flags
@@ -2677,17 +2843,19 @@ sub set_whitespace_flags {
             next;
         }
 
-        $rtokh_last_last = $rtokh_last;
-
-        $rtokh_last = $rtokh;
         $last_token = $token;
         $last_type  = $type;
 
+        if ( $type ne '#' ) {
+            $rtokh_last_last = $rtokh_last;
+            $rtokh_last      = $rtokh;
+        }
+
         $rtokh = $rLL->[$j];
         $token = $rtokh->[_TOKEN_];
         $type  = $rtokh->[_TYPE_];
 
-        my $ws;
+        $ws = undef;
 
         #---------------------------------------------------------------
         # Whitespace Rules Section 1:
@@ -2754,7 +2922,7 @@ sub set_whitespace_flags {
                     $ws = WS_NO;
                 }
                 else {
-                    $ws = $ws_in_container->($j);
+                    $ws = ws_in_container($j);
                 }
             }
 
@@ -2775,17 +2943,10 @@ sub set_whitespace_flags {
         #---------------------------------------------------------------
         # The hash '%is_special_ws_type' significantly speeds up this routine,
         # but be sure to update it if a new check is added.
-        # Currently has types: qw(k w C m - Q #)
+        # Currently has types: qw(k w C m - Q #)
         if ( $is_special_ws_type{$type} ) {
-            if ( $type eq 'i' ) {
-
-                # never a space before ->
-                if ( substr( $token, 0, 2 ) eq '->' ) {
-                    $ws = WS_NO;
-                }
-            }
 
-            elsif ( $type eq 'k' ) {
+            if ( $type eq 'k' ) {
 
                 # Keywords 'for', 'foreach' are special cases for -kpit since
                 # the opening paren does not always immediately follow the
@@ -2809,7 +2970,7 @@ sub set_whitespace_flags {
                         last if ( $rLL->[$jp]->[_LEVEL_] != $level );    # b1236
                         next unless ( $rLL->[$jp]->[_TOKEN_] eq '(' );
                         my $seqno_p = $rLL->[$jp]->[_TYPE_SEQUENCE_];
-                        $set_container_ws_by_keyword->( $token, $seqno_p );
+                        set_container_ws_by_keyword( $token, $seqno_p );
                         last;
                     }
                 }
@@ -2818,11 +2979,6 @@ sub set_whitespace_flags {
             # retain any space between '-' and bare word
             elsif ( $type eq 'w' || $type eq 'C' ) {
                 $ws = WS_OPTIONAL if $last_type eq '-';
-
-                # never a space before ->
-                if ( substr( $token, 0, 2 ) eq '->' ) {
-                    $ws = WS_NO;
-                }
             }
 
             # retain any space between '-' and bare word; for example
@@ -2925,7 +3081,7 @@ sub set_whitespace_flags {
                         || $space_after_keyword{$last_token} );
 
                     # Set inside space flag if requested
-                    $set_container_ws_by_keyword->( $last_token, $seqno );
+                    set_container_ws_by_keyword( $last_token, $seqno );
                 }
 
                 # Space between function and '('
@@ -2943,39 +3099,40 @@ sub set_whitespace_flags {
                 # NOTE: this would be the place to allow spaces between
                 # repeated parens, like () () (), as in case c017, but I
                 # decided that would not be a good idea.
+
+                # Updated to allow detached '->' from tokenizer (issue c140)
                 elsif (
-                    ##$last_type =~ /^[wCUG]$/
+
+                    #        /^[wCUG]$/
                     $is_wCUG{$last_type}
+
                     || (
-                        ##$last_type =~ /^[wi]$/
+
+                        #      /^[wi]$/
                         $is_wi{$last_type}
 
                         && (
+
+                            # with prefix '->' or '&'
                             $last_token =~ /^([\&]|->)/
 
-                            # or -> or & split from bareword by newline (b1337)
-                            || (
-                                $last_token =~ /^\w/
-                                && (
-                                    $rtokh_last_last->[_TYPE_] eq '->'
-                                    || (   $rtokh_last_last->[_TYPE_] eq 't'
-                                        && $rtokh_last_last->[_TOKEN_] =~
-                                        /^\&\s*$/ )
-                                )
-                            )
+                            # or preceding token '->' (see b1337; c140)
+                            || $rtokh_last_last->[_TYPE_] eq '->'
+
+                            # or preceding sub call operator token '&'
+                            || (   $rtokh_last_last->[_TYPE_] eq 't'
+                                && $rtokh_last_last->[_TOKEN_] =~ /^\&\s*$/ )
                         )
                     )
                   )
                 {
                     $ws = $rOpts_space_function_paren ? WS_YES : WS_NO;
-                    $set_container_ws_by_keyword->( $last_token, $seqno );
+                    set_container_ws_by_keyword( $last_token, $seqno );
                     $ris_function_call_paren->{$seqno} = 1;
                 }
 
                 # space between something like $i and ( in 'snippets/space2.in'
                 # for $i ( 0 .. 20 ) {
-                # FIXME: eventually, type 'i' could be split into multiple
-                # token types so this can be a hardwired rule.
                 elsif ( $last_type eq 'i' && $last_token =~ /^[\$\%\@]/ ) {
                     $ws = WS_YES;
                 }
@@ -2994,9 +3151,10 @@ sub set_whitespace_flags {
                 $ws = WS_OPTIONAL;
             }
 
-            # keep space between 'sub' and '{' for anonymous sub definition
+            # keep space between 'sub' and '{' for anonymous sub definition,
+            # be sure type = 'k' (added for c140)
             if ( $type eq '{' ) {
-                if ( $last_token eq 'sub' ) {
+                if ( $last_token eq 'sub' && $last_type eq 'k' ) {
                     $ws = WS_YES;
                 }
 
@@ -3087,19 +3245,20 @@ sub set_whitespace_flags {
 
         $rwhitespace_flags->[$j] = $ws;
 
-        if (DEBUG_WHITE) {
-            my $str = substr( $last_token, 0, 15 );
-            $str .= SPACE x ( 16 - length($str) );
-            if ( !defined($ws_1) ) { $ws_1 = "*" }
-            if ( !defined($ws_2) ) { $ws_2 = "*" }
-            if ( !defined($ws_3) ) { $ws_3 = "*" }
-            if ( !defined($ws_4) ) { $ws_4 = "*" }
-            print STDOUT
+        next if ( !DEBUG_WHITE );
+
+        my $str = substr( $last_token, 0, 15 );
+        $str .= SPACE x ( 16 - length($str) );
+        if ( !defined($ws_1) ) { $ws_1 = "*" }
+        if ( !defined($ws_2) ) { $ws_2 = "*" }
+        if ( !defined($ws_3) ) { $ws_3 = "*" }
+        if ( !defined($ws_4) ) { $ws_4 = "*" }
+        print STDOUT
 "NEW WHITE:  i=$j $str $last_type $type $ws_1 : $ws_2 : $ws_3 : $ws_4 : $ws \n";
 
-            # reset for next pass
-            $ws_1 = $ws_2 = $ws_3 = $ws_4 = undef;
-        }
+        # reset for next pass
+        $ws_1 = $ws_2 = $ws_3 = $ws_4 = undef;
+
     } ## end main loop
 
     if ( $rOpts->{'tight-secret-operators'} ) {
@@ -3110,6 +3269,87 @@ sub set_whitespace_flags {
 
 } ## end sub set_whitespace_flags
 
+sub set_container_ws_by_keyword {
+
+    my ( $word, $sequence_number ) = @_;
+    return unless (%keyword_paren_inner_tightness);
+
+    # We just saw a keyword (or other function name) followed by an opening
+    # paren. Now check to see if the following paren should have special
+    # treatment for its inside space.  If so we set a hash value using the
+    # sequence number as key.
+    if ( $word && $sequence_number ) {
+        my $tightness = $keyword_paren_inner_tightness{$word};
+        if ( defined($tightness) && $tightness != 1 ) {
+            my $ws_flag = $tightness == 0 ? WS_YES : WS_NO;
+            $opening_container_inside_ws{$sequence_number} = $ws_flag;
+            $closing_container_inside_ws{$sequence_number} = $ws_flag;
+        }
+    }
+    return;
+} ## end sub set_container_ws_by_keyword
+
+sub ws_in_container {
+
+    my ($j) = @_;
+    if ( $j + 1 > $jmax ) { return (WS_NO) }
+
+    # Patch to count '-foo' as single token so that
+    # each of  $a{-foo} and $a{foo} and $a{'foo'} do
+    # not get spaces with default formatting.
+    my $j_here = $j;
+    ++$j_here
+      if ( $token eq '-'
+        && $last_token eq '{'
+        && $rLL->[ $j + 1 ]->[_TYPE_] eq 'w' );
+
+    # Patch to count a sign separated from a number as a single token, as
+    # in the following line. Otherwise, it takes two steps to converge:
+    #    deg2rad(-  0.5)
+    if (   ( $type eq 'm' || $type eq 'p' )
+        && $j < $jmax + 1
+        && $rLL->[ $j + 1 ]->[_TYPE_] eq 'b'
+        && $rLL->[ $j + 2 ]->[_TYPE_] eq 'n'
+        && $rLL->[ $j + 2 ]->[_TOKEN_] =~ /^\d/ )
+    {
+        $j_here = $j + 2;
+    }
+
+    # $j_next is where a closing token should be if
+    # the container has a single token
+    if ( $j_here + 1 > $jmax ) { return (WS_NO) }
+    my $j_next =
+      ( $rLL->[ $j_here + 1 ]->[_TYPE_] eq 'b' )
+      ? $j_here + 2
+      : $j_here + 1;
+
+    if ( $j_next > $jmax ) { return WS_NO }
+    my $tok_next  = $rLL->[$j_next]->[_TOKEN_];
+    my $type_next = $rLL->[$j_next]->[_TYPE_];
+
+    # for tightness = 1, if there is just one token
+    # within the matching pair, we will keep it tight
+    if (
+        $tok_next eq $matching_token{$last_token}
+
+        # but watch out for this: [ [ ]    (misc.t)
+        && $last_token ne $token
+
+        # double diamond is usually spaced
+        && $token ne '<<>>'
+
+      )
+    {
+
+        # remember where to put the space for the closing paren
+        $j_tight_closing_paren = $j_next;
+        return (WS_NO);
+    }
+    return (WS_YES);
+} ## end sub ws_in_container
+
+} ## end closure set_whitespace_flags
+
 sub dump_want_left_space {
     my $fh = shift;
     local $LIST_SEPARATOR = "\n";
@@ -3878,6 +4118,9 @@ EOM
         #    $a->$b($c);
         $binary_bond_strength{'i'}{'->'} = 1.45 * STRONG;
 
+        # Added for c140 to make 'w ->' and 'i ->' behave the same
+        $binary_bond_strength{'w'}{'->'} = 1.45 * STRONG;
+
     # Note that the following alternative strength would make the break at the
     # '->' rather than opening the '('.  Both have advantages and disadvantages.
     # $binary_bond_strength{'i'}{'->'} = 0.5*STRONG + 0.5 * NOMINAL; #
@@ -4010,6 +4253,11 @@ EOM
 
         my ($self) = @_;
 
+        #-----------------------------------------------------------------
+        # Define a 'bond strength' for each token pair in an output batch.
+        # See comments above for definition of bond strength.
+        #-----------------------------------------------------------------
+
         my $rbond_strength_to_go = [];
 
         my $rLL               = $self->[_rLL_];
@@ -4323,7 +4571,8 @@ EOM
             elsif ( $type eq 'w' ) {
                 $bond_str = NO_BREAK
                   if ( !$old_breakpoint_to_go[$i]
-                    && substr( $next_nonblank_token, 0, 1 ) eq '/' );
+                    && substr( $next_nonblank_token, 0, 1 ) eq '/'
+                    && $next_nonblank_type ne '//' );
             }
 
             $bond_str_2 = $bond_str if (DEBUG_BOND);
@@ -4533,8 +4782,8 @@ sub bad_pattern {
     # but it should be safe because the pattern has been constructed
     # by this program.
     my ($pattern) = @_;
-    eval "'##'=~/$pattern/";
-    return $EVAL_ERROR;
+    my $ok = eval "'##'=~/$pattern/";
+    return !defined($ok) || $EVAL_ERROR;
 }
 
 {    ## begin closure prepare_cuddled_block_types
@@ -5225,6 +5474,26 @@ EOM
         return;
     } ## end sub check_sequence_numbers
 
+    sub store_block_type {
+        my ( $self, $block_type, $seqno ) = @_;
+
+        return if ( !$block_type );
+
+        $self->[_rblock_type_of_seqno_]->{$seqno} = $block_type;
+
+        if ( substr( $block_type, 0, 3 ) eq 'sub'
+            || $rOpts_sub_alias_list )
+        {
+            if ( $block_type =~ /$ASUB_PATTERN/ ) {
+                $self->[_ris_asub_block_]->{$seqno} = 1;
+            }
+            elsif ( $block_type =~ /$SUB_PATTERN/ ) {
+                $self->[_ris_sub_block_]->{$seqno} = 1;
+            }
+        }
+        return;
+    }
+
     sub write_line {
 
         # This routine receives lines one-by-one from the tokenizer and stores
@@ -5233,19 +5502,8 @@ EOM
         # to do the actual formatting.
 
         my ( $self, $line_of_tokens_old ) = @_;
-        my $rLL        = $self->[_rLL_];
-        my $Klimit     = $self->[_Klimit_];
-        my $rlines_new = $self->[_rlines_];
-
-        my $K_opening_container     = $self->[_K_opening_container_];
-        my $K_closing_container     = $self->[_K_closing_container_];
-        my $rdepth_of_opening_seqno = $self->[_rdepth_of_opening_seqno_];
-        my $rblock_type_of_seqno    = $self->[_rblock_type_of_seqno_];
-        my $rSS                     = $self->[_rSS_];
-        my $Iss_opening             = $self->[_Iss_opening_];
-        my $Iss_closing             = $self->[_Iss_closing_];
 
-        my $Kfirst;
+        my $rLL            = $self->[_rLL_];
         my $line_of_tokens = {};
         foreach (
             qw(
@@ -5265,193 +5523,55 @@ EOM
             $line_of_tokens->{$_} = $line_of_tokens_old->{$_};
         }
 
-        # Data needed by Logger
-        $line_of_tokens->{_level_0}          = 0;
-        $line_of_tokens->{_ci_level_0}       = 0;
-        $line_of_tokens->{_nesting_blocks_0} = EMPTY_STRING;
-        $line_of_tokens->{_nesting_tokens_0} = EMPTY_STRING;
-
-        # Needed to avoid trimming quotes
-        $line_of_tokens->{_ended_in_blank_token} = undef;
-
-        my $line_type   = $line_of_tokens_old->{_line_type};
-        my $line_number = $line_of_tokens_old->{_line_number};
-        my $CODE_type   = EMPTY_STRING;
+        my $line_type = $line_of_tokens_old->{_line_type};
         my $tee_output;
 
+        my $Klimit = $self->[_Klimit_];
+        my $Kfirst;
+
         # Handle line of non-code
         if ( $line_type ne 'CODE' ) {
             $tee_output ||= $rOpts_tee_pod
               && substr( $line_type, 0, 3 ) eq 'POD';
+
+            $line_of_tokens->{_level_0}              = 0;
+            $line_of_tokens->{_ci_level_0}           = 0;
+            $line_of_tokens->{_nesting_blocks_0}     = EMPTY_STRING;
+            $line_of_tokens->{_nesting_tokens_0}     = EMPTY_STRING;
+            $line_of_tokens->{_ended_in_blank_token} = undef;
+
         }
 
         # Handle line of code
         else {
 
-            my $rtokens        = $line_of_tokens_old->{_rtokens};
-            my $rtoken_type    = $line_of_tokens_old->{_rtoken_type};
-            my $rblock_type    = $line_of_tokens_old->{_rblock_type};
-            my $rtype_sequence = $line_of_tokens_old->{_rtype_sequence};
-            my $rlevels        = $line_of_tokens_old->{_rlevels};
-            my $rci_levels     = $line_of_tokens_old->{_rci_levels};
+            my $rtokens = $line_of_tokens_old->{_rtokens};
+            my $jmax    = @{$rtokens} - 1;
 
-            my $jmax = @{$rtokens} - 1;
             if ( $jmax >= 0 ) {
-                $Kfirst = defined($Klimit) ? $Klimit + 1 : 0;
-
-                DEVEL_MODE
-                  && check_sequence_numbers( $rtokens, $rtoken_type,
-                    $rtype_sequence, $line_number );
-
-                # Find the starting nesting depth ...
-                # It must be the value of variable 'level' of the first token
-                # because the nesting depth is used as a token tag in the
-                # vertical aligner and is compared to actual levels.
-                # So vertical alignment problems will occur with any other
-                # starting value.
-                if ( !defined($nesting_depth) ) {
-                    $nesting_depth = $rlevels->[0];
-                    $nesting_depth = 0 if ( $nesting_depth < 0 );
-                    $rdepth_of_opening_seqno->[SEQ_ROOT] = $nesting_depth - 1;
-                }
-
-                foreach my $j ( 0 .. $jmax ) {
-
-                    # Do not clip the 'level' variable yet. We will do this
-                    # later, in sub 'store_token_to_go'. The reason is that in
-                    # files with level errors, the logic in 'weld_cuddled_else'
-                    # uses a stack logic that will give bad welds if we clip
-                    # levels here.
-                    ## if ( $rlevels->[$j] < 0 ) { $rlevels->[$j] = 0 }
-
-                    # Handle tokens with sequence numbers ...
-                    my $seqno = $rtype_sequence->[$j];
-                    if ($seqno) {
-                        my $token = $rtokens->[$j];
-                        my $sign  = 1;
-                        if ( $is_opening_token{$token} ) {
-                            $K_opening_container->{$seqno} = @{$rLL};
-                            $rdepth_of_opening_seqno->[$seqno] = $nesting_depth;
-                            $nesting_depth++;
-
-                            # Save a sequenced block type at its opening token.
-                            # Note that unsequenced block types can occur in
-                            # unbalanced code with errors but are ignored here.
-                            if ( $rblock_type->[$j] ) {
-                                my $block_type = $rblock_type->[$j];
-                                $rblock_type_of_seqno->{$seqno} = $block_type;
-                                if ( substr( $block_type, 0, 3 ) eq 'sub'
-                                    || $rOpts_sub_alias_list )
-                                {
-                                    if ( $block_type =~ /$ASUB_PATTERN/ ) {
-                                        $self->[_ris_asub_block_]->{$seqno} = 1;
-                                    }
-                                    elsif ( $block_type =~ /$SUB_PATTERN/ ) {
-                                        $self->[_ris_sub_block_]->{$seqno} = 1;
-                                    }
-                                }
-                            }
-                        }
-                        elsif ( $is_closing_token{$token} ) {
-
-                            # The opening depth should always be defined, and
-                            # it should equal $nesting_depth-1.  To protect
-                            # against unforseen error conditions, however, we
-                            # will check this and fix things if necessary.  For
-                            # a test case see issue c055.
-                            my $opening_depth =
-                              $rdepth_of_opening_seqno->[$seqno];
-                            if ( !defined($opening_depth) ) {
-                                $opening_depth = $nesting_depth - 1;
-                                $opening_depth = 0 if ( $opening_depth < 0 );
-                                $rdepth_of_opening_seqno->[$seqno] =
-                                  $opening_depth;
-
-                                # This is not fatal but should not happen.  The
-                                # tokenizer generates sequence numbers
-                                # incrementally upon encountering each new
-                                # opening token, so every positive sequence
-                                # number should correspond to an opening token.
-                                if (DEVEL_MODE) {
-                                    Fault(<<EOM);
-No opening token seen for closing token = '$token' at seq=$seqno at depth=$opening_depth
-EOM
-                                }
-                            }
-                            $K_closing_container->{$seqno} = @{$rLL};
-                            $nesting_depth                 = $opening_depth;
-                            $sign                          = -1;
-                        }
-                        elsif ( $token eq '?' ) {
-                        }
-                        elsif ( $token eq ':' ) {
-                            $sign = -1;
-                        }
-
-                        # The only sequenced types output by the tokenizer are
-                        # the opening & closing containers and the ternary
-                        # types. So we would only get here if the tokenizer has
-                        # been changed to mark some other tokens with sequence
-                        # numbers, or if an error has been introduced in a
-                        # hash such as %is_opening_container
-                        else {
-                            if (DEVEL_MODE) {
-                                Fault(<<EOM);
-Unexpected sequenced token '$token' of type '$rtoken_type->[$j]', sequence=$seqno arrived from tokenizer.
-Expecting only opening or closing container tokens or ternary tokens with sequence numbers.
-EOM
-                            }
-                        }
 
-                        if ( $sign > 0 ) {
-                            $Iss_opening->[$seqno] = @{$rSS};
-
-                            # For efficiency, we find the maximum level of
-                            # opening tokens of any type.  The actual maximum
-                            # level will be that of their contents which is 1
-                            # greater.  That will be fixed in sub
-                            # 'finish_formatting'.
-                            my $level = $rlevels->[$j];
-                            if ( $level > $self->[_maximum_level_] ) {
-                                $self->[_maximum_level_]         = $level;
-                                $self->[_maximum_level_at_line_] = $line_number;
-                            }
-                        }
-                        else { $Iss_closing->[$seqno] = @{$rSS} }
-                        push @{$rSS}, $sign * $seqno;
-
-                    }
-                    else {
-                        $seqno = EMPTY_STRING unless ( defined($seqno) );
-                    }
+                $Kfirst = defined($Klimit) ? $Klimit + 1 : 0;
 
-                    my @tokary;
-                    @tokary[
-                      _TOKEN_, _TYPE_,     _TYPE_SEQUENCE_,
-                      _LEVEL_, _CI_LEVEL_, _LINE_INDEX_,
-                      ]
-                      = (
-                        $rtokens->[$j],    $rtoken_type->[$j],
-                        $seqno,            $rlevels->[$j],
-                        $rci_levels->[$j], $line_number - 1,
-                      );
-                    push @{$rLL}, \@tokary;
-                } ## end foreach my $j ( 0 .. $jmax )
+                #----------------------------
+                # get the tokens on this line
+                #----------------------------
+                $self->write_line_inner_loop( $line_of_tokens_old,
+                    $line_of_tokens );
 
+                # update Klimit for added tokens
                 $Klimit = @{$rLL} - 1;
 
-                # Need to remember if we can trim the input line
-                $line_of_tokens->{_ended_in_blank_token} =
-                  $rtoken_type->[$jmax] eq 'b';
+            } ## end if ( $jmax >= 0 )
+            else {
 
-                $line_of_tokens->{_level_0}    = $rlevels->[0];
-                $line_of_tokens->{_ci_level_0} = $rci_levels->[0];
-                $line_of_tokens->{_nesting_blocks_0} =
-                  $line_of_tokens_old->{_nesting_blocks_0};
-                $line_of_tokens->{_nesting_tokens_0} =
-                  $line_of_tokens_old->{_nesting_tokens_0};
+                # blank line
+                $line_of_tokens->{_level_0}              = 0;
+                $line_of_tokens->{_ci_level_0}           = 0;
+                $line_of_tokens->{_nesting_blocks_0}     = EMPTY_STRING;
+                $line_of_tokens->{_nesting_tokens_0}     = EMPTY_STRING;
+                $line_of_tokens->{_ended_in_blank_token} = undef;
 
-            } ## end if ( $jmax >= 0 )
+            }
 
             $tee_output ||=
                  $rOpts_tee_block_comments
@@ -5467,50 +5587,223 @@ EOM
         } ## end if ( $line_type eq 'CODE')
 
         # Finish storing line variables
+        $line_of_tokens->{_rK_range} = [ $Kfirst, $Klimit ];
+        $self->[_Klimit_] = $Klimit;
+        my $rlines = $self->[_rlines_];
+        push @{$rlines}, $line_of_tokens;
+
         if ($tee_output) {
             my $fh_tee    = $self->[_fh_tee_];
             my $line_text = $line_of_tokens_old->{_line_text};
             $fh_tee->print($line_text) if ($fh_tee);
         }
 
-        $line_of_tokens->{_rK_range}  = [ $Kfirst, $Klimit ];
-        $line_of_tokens->{_code_type} = $CODE_type;
-        $self->[_Klimit_]             = $Klimit;
-
-        push @{$rlines_new}, $line_of_tokens;
         return;
     } ## end sub write_line
-} ## end closure write_line
 
-#############################################
-# CODE SECTION 5: Pre-process the entire file
-#############################################
+    sub write_line_inner_loop {
+        my ( $self, $line_of_tokens_old, $line_of_tokens ) = @_;
 
-sub finish_formatting {
+        #---------------------------------------------------------------------
+        # Copy the tokens on one line received from the tokenizer to their new
+        # storage locations.
+        #---------------------------------------------------------------------
 
-    my ( $self, $severe_error ) = @_;
+        # Input parameters:
+        #  $line_of_tokens_old = line received from tokenizer
+        #  $line_of_tokens     = line of tokens being formed for formatter
 
-    # The file has been tokenized and is ready to be formatted.
-    # All of the relevant data is stored in $self, ready to go.
+        my $rtokens = $line_of_tokens_old->{_rtokens};
+        my $jmax    = @{$rtokens} - 1;
+        if ( $jmax < 0 ) {
 
-    # Check the maximum level. If it is extremely large we will give up and
-    # output the file verbatim.  Note that the actual maximum level is 1
-    # greater than the saved value, so we fix that here.
-    $self->[_maximum_level_] += 1;
-    my $maximum_level       = $self->[_maximum_level_];
-    my $maximum_table_index = $#maximum_line_length_at_level;
-    if ( !$severe_error && $maximum_level >= $maximum_table_index ) {
-        $severe_error ||= 1;
-        Warn(<<EOM);
-The maximum indentation level, $maximum_level, exceeds the builtin limit of $maximum_table_index.
-Something may be wrong; formatting will be skipped.
-EOM
-    }
+            # safety check; shouldn't happen
+            DEVEL_MODE && Fault("unexpected jmax=$jmax\n");
+            return;
+        }
+
+        my $line_number    = $line_of_tokens_old->{_line_number};
+        my $rtoken_type    = $line_of_tokens_old->{_rtoken_type};
+        my $rblock_type    = $line_of_tokens_old->{_rblock_type};
+        my $rtype_sequence = $line_of_tokens_old->{_rtype_sequence};
+        my $rlevels        = $line_of_tokens_old->{_rlevels};
+        my $rci_levels     = $line_of_tokens_old->{_rci_levels};
+
+        my $rLL                     = $self->[_rLL_];
+        my $rSS                     = $self->[_rSS_];
+        my $rdepth_of_opening_seqno = $self->[_rdepth_of_opening_seqno_];
+
+        DEVEL_MODE
+          && check_sequence_numbers( $rtokens, $rtoken_type,
+            $rtype_sequence, $line_number );
+
+        # Find the starting nesting depth ...
+        # It must be the value of variable 'level' of the first token
+        # because the nesting depth is used as a token tag in the
+        # vertical aligner and is compared to actual levels.
+        # So vertical alignment problems will occur with any other
+        # starting value.
+        if ( !defined($nesting_depth) ) {
+            $nesting_depth                       = $rlevels->[0];
+            $nesting_depth                       = 0 if ( $nesting_depth < 0 );
+            $rdepth_of_opening_seqno->[SEQ_ROOT] = $nesting_depth - 1;
+        }
+
+        foreach my $j ( 0 .. $jmax ) {
+
+            # Do not clip the 'level' variable yet. We will do this
+            # later, in sub 'store_token_to_go'. The reason is that in
+            # files with level errors, the logic in 'weld_cuddled_else'
+            # uses a stack logic that will give bad welds if we clip
+            # levels here.
+            ## if ( $rlevels->[$j] < 0 ) { $rlevels->[$j] = 0 }
+
+            # Handle tokens with sequence numbers ...
+            my $seqno = $rtype_sequence->[$j];
+            if ($seqno) {
+                my $token = $rtokens->[$j];
+                my $sign  = 1;
+                if ( $is_opening_token{$token} ) {
+                    $self->[_K_opening_container_]->{$seqno} = @{$rLL};
+                    $rdepth_of_opening_seqno->[$seqno] = $nesting_depth;
+                    $nesting_depth++;
+
+                    # Save a sequenced block type at its opening token.
+                    # Note that unsequenced block types can occur in
+                    # unbalanced code with errors but are ignored here.
+                    $self->store_block_type( $rblock_type->[$j], $seqno )
+                      if ( $rblock_type->[$j] );
+                }
+                elsif ( $is_closing_token{$token} ) {
+
+                    # The opening depth should always be defined, and
+                    # it should equal $nesting_depth-1.  To protect
+                    # against unforseen error conditions, however, we
+                    # will check this and fix things if necessary.  For
+                    # a test case see issue c055.
+                    my $opening_depth = $rdepth_of_opening_seqno->[$seqno];
+                    if ( !defined($opening_depth) ) {
+                        $opening_depth = $nesting_depth - 1;
+                        $opening_depth = 0 if ( $opening_depth < 0 );
+                        $rdepth_of_opening_seqno->[$seqno] = $opening_depth;
+
+                        # This is not fatal but should not happen.  The
+                        # tokenizer generates sequence numbers
+                        # incrementally upon encountering each new
+                        # opening token, so every positive sequence
+                        # number should correspond to an opening token.
+                        DEVEL_MODE && Fault(<<EOM);
+No opening token seen for closing token = '$token' at seq=$seqno at depth=$opening_depth
+EOM
+                    }
+                    $self->[_K_closing_container_]->{$seqno} = @{$rLL};
+                    $nesting_depth                           = $opening_depth;
+                    $sign                                    = -1;
+                }
+                elsif ( $token eq '?' ) {
+                }
+                elsif ( $token eq ':' ) {
+                    $sign = -1;
+                }
+
+                # The only sequenced types output by the tokenizer are
+                # the opening & closing containers and the ternary
+                # types. So we would only get here if the tokenizer has
+                # been changed to mark some other tokens with sequence
+                # numbers, or if an error has been introduced in a
+                # hash such as %is_opening_container
+                else {
+                    DEVEL_MODE && Fault(<<EOM);
+Unexpected sequenced token '$token' of type '$rtoken_type->[$j]', sequence=$seqno arrived from tokenizer.
+Expecting only opening or closing container tokens or ternary tokens with sequence numbers.
+EOM
+                }
+
+                if ( $sign > 0 ) {
+                    $self->[_Iss_opening_]->[$seqno] = @{$rSS};
+
+                    # For efficiency, we find the maximum level of
+                    # opening tokens of any type.  The actual maximum
+                    # level will be that of their contents which is 1
+                    # greater.  That will be fixed in sub
+                    # 'finish_formatting'.
+                    my $level = $rlevels->[$j];
+                    if ( $level > $self->[_maximum_level_] ) {
+                        $self->[_maximum_level_]         = $level;
+                        $self->[_maximum_level_at_line_] = $line_number;
+                    }
+                }
+                else { $self->[_Iss_closing_]->[$seqno] = @{$rSS} }
+                push @{$rSS}, $sign * $seqno;
+
+            }
+            else {
+                $seqno = EMPTY_STRING unless ( defined($seqno) );
+            }
+
+            my @tokary;
+            @tokary[
+              _TOKEN_, _TYPE_,     _TYPE_SEQUENCE_,
+              _LEVEL_, _CI_LEVEL_, _LINE_INDEX_,
+              ]
+              = (
+                $rtokens->[$j],    $rtoken_type->[$j], $seqno, $rlevels->[$j],
+                $rci_levels->[$j], $line_number - 1,
+              );
+            push @{$rLL}, \@tokary;
+        } ## end foreach my $j ( 0 .. $jmax )
+
+        # Need to remember if we can trim the input line
+        $line_of_tokens->{_ended_in_blank_token} = $rtoken_type->[$jmax] eq 'b';
+
+        # Values needed by Logger
+        $line_of_tokens->{_level_0}    = $rlevels->[0];
+        $line_of_tokens->{_ci_level_0} = $rci_levels->[0];
+        $line_of_tokens->{_nesting_blocks_0} =
+          $line_of_tokens_old->{_nesting_blocks_0};
+        $line_of_tokens->{_nesting_tokens_0} =
+          $line_of_tokens_old->{_nesting_tokens_0};
+
+        return;
+
+    } ## end sub write_line_inner_loop
+
+} ## end closure write_line
+
+#############################################
+# CODE SECTION 5: Pre-process the entire file
+#############################################
+
+sub finish_formatting {
+
+    my ( $self, $severe_error ) = @_;
+
+    # The file has been tokenized and is ready to be formatted.
+    # All of the relevant data is stored in $self, ready to go.
+
+    # Some of the code in sub break_lists is not robust enough to process code
+    # with arbitrary brace errors. The simplest fix is to just return the file
+    # verbatim if there are brace errors.  This fixes issue c160.
+    $severe_error ||= get_saw_brace_error();
+
+    # Check the maximum level. If it is extremely large we will give up and
+    # output the file verbatim.  Note that the actual maximum level is 1
+    # greater than the saved value, so we fix that here.
+    $self->[_maximum_level_] += 1;
+    my $maximum_level       = $self->[_maximum_level_];
+    my $maximum_table_index = $#maximum_line_length_at_level;
+    if ( !$severe_error && $maximum_level >= $maximum_table_index ) {
+        $severe_error ||= 1;
+        Warn(<<EOM);
+The maximum indentation level, $maximum_level, exceeds the builtin limit of $maximum_table_index.
+Something may be wrong; formatting will be skipped.
+EOM
+    }
 
     # output file verbatim if severe error or no formatting requested
     if ( $severe_error || $rOpts->{notidy} ) {
         $self->dump_verbatim();
-        $self->wrapup();
+        $self->wrapup($severe_error);
         return;
     }
 
@@ -5521,44 +5814,56 @@ EOM
         $self->[_save_logfile_] = $logger_object->get_save_logfile();
     }
 
-    my $rix_side_comments = $self->set_CODE_type();
+    {
+        my $rix_side_comments = $self->set_CODE_type();
 
-    $self->find_non_indenting_braces($rix_side_comments);
+        $self->find_non_indenting_braces($rix_side_comments);
 
-    # Handle any requested side comment deletions. It is easier to get
-    # this done here rather than farther down the pipeline because IO
-    # lines take a different route, and because lines with deleted HSC
-    # become BL lines.  We have already handled any tee requests in sub
-    # getline, so it is safe to delete side comments now.
-    $self->delete_side_comments($rix_side_comments)
-      if ( $rOpts_delete_side_comments
-        || $rOpts_delete_closing_side_comments );
+        # Handle any requested side comment deletions. It is easier to get
+        # this done here rather than farther down the pipeline because IO
+        # lines take a different route, and because lines with deleted HSC
+        # become BL lines.  We have already handled any tee requests in sub
+        # getline, so it is safe to delete side comments now.
+        $self->delete_side_comments($rix_side_comments)
+          if ( $rOpts_delete_side_comments
+            || $rOpts_delete_closing_side_comments );
+    }
 
     # Verify that the line hash does not have any unknown keys.
     $self->check_line_hashes() if (DEVEL_MODE);
 
-    # Make a pass through all tokens, adding or deleting any whitespace as
-    # required.  Also make any other changes, such as adding semicolons.
-    # All token changes must be made here so that the token data structure
-    # remains fixed for the rest of this iteration.
-    $self->respace_tokens();
+    {
+        # Make a pass through all tokens, adding or deleting any whitespace as
+        # required.  Also make any other changes, such as adding semicolons.
+        # All token changes must be made here so that the token data structure
+        # remains fixed for the rest of this iteration.
+        my ( $error, $rqw_lines ) = $self->respace_tokens();
+        if ($error) {
+            $self->dump_verbatim();
+            $self->wrapup();
+            return;
+        }
+
+        $self->find_multiline_qw($rqw_lines);
+    }
+
+    $self->examine_vertical_tightness_flags();
 
     $self->set_excluded_lp_containers();
 
-    $self->find_multiline_qw();
-
     $self->keep_old_line_breaks();
 
     # Implement any welding needed for the -wn or -cb options
     $self->weld_containers();
 
-    $self->collapsed_lengths()
+    # Collect info needed to implement the -xlp style
+    $self->xlp_collapsed_lengths()
       if ( $rOpts_line_up_parentheses && $rOpts_extended_line_up_parentheses );
 
     # Locate small nested blocks which should not be broken
     $self->mark_short_nested_blocks();
 
-    $self->adjust_indentation_levels();
+    $self->special_indentation_adjustments();
 
     # Verify that the main token array looks OK.  If this ever causes a fault
     # then place similar checks before the sub calls above to localize the
@@ -5619,25 +5924,26 @@ sub set_CODE_type {
     my $ix_line = -1;
     foreach my $line_of_tokens ( @{$rlines} ) {
         $ix_line++;
-        my $input_line_no = $line_of_tokens->{_line_number};
-        my $line_type     = $line_of_tokens->{_line_type};
+        my $line_type = $line_of_tokens->{_line_type};
 
         my $Last_line_had_side_comment = $has_side_comment;
         if ($has_side_comment) {
             push @ix_side_comments, $ix_line - 1;
+            $has_side_comment = 0;
         }
-        $has_side_comment = 0;
 
-        next unless ( $line_type eq 'CODE' );
+        my $last_CODE_type = $CODE_type;
+        $CODE_type = EMPTY_STRING;
+
+        if ( $line_type ne 'CODE' ) {
+            next;
+        }
 
         my $Klast_prev = $Klast;
 
         my $rK_range = $line_of_tokens->{_rK_range};
         ( $Kfirst, $Klast ) = @{$rK_range};
 
-        my $last_CODE_type = $CODE_type;
-        $CODE_type = EMPTY_STRING;
-
         my $input_line = $line_of_tokens->{_line_text};
         my $jmax       = defined($Kfirst) ? $Klast - $Kfirst : -1;
 
@@ -5663,11 +5969,12 @@ sub set_CODE_type {
               )
             {
                 $In_format_skipping_section = 0;
+                my $input_line_no = $line_of_tokens->{_line_number};
                 write_logfile_entry(
                     "Line $input_line_no: Exiting format-skipping section\n");
             }
             $CODE_type = 'FS';
-            goto NEXT;
+            next;
         }
 
         # Check for a continued quote..
@@ -5676,12 +5983,12 @@ sub set_CODE_type {
             # A line which is entirely a quote or pattern must go out
             # verbatim.  Note: the \n is contained in $input_line.
             if ( $jmax <= 0 ) {
-                if ( ( $input_line =~ "\t" ) ) {
+                if ( $self->[_save_logfile_] && $input_line =~ /\t/ ) {
                     my $input_line_number = $line_of_tokens->{_line_number};
                     $self->note_embedded_tab($input_line_number);
                 }
                 $CODE_type = 'VB';
-                goto NEXT;
+                next;
             }
         }
 
@@ -5699,10 +6006,11 @@ sub set_CODE_type {
           )
         {
             $In_format_skipping_section = 1;
+            my $input_line_no = $line_of_tokens->{_line_number};
             write_logfile_entry(
                 "Line $input_line_no: Entering format-skipping section\n");
             $CODE_type = 'FS';
-            goto NEXT;
+            next;
         }
 
         # ignore trailing blank tokens (they will get deleted later)
@@ -5713,7 +6021,7 @@ sub set_CODE_type {
         # blank line..
         if ( $jmax < 0 ) {
             $CODE_type = 'BL';
-            goto NEXT;
+            next;
         }
 
         # Handle comments
@@ -5765,7 +6073,7 @@ sub set_CODE_type {
                 if ( $last_CODE_type eq 'HSC' ) {
                     $has_side_comment = 1;
                     $CODE_type        = 'HSC';
-                    goto NEXT;
+                    next;
                 }
 
                 #  starting a new HSC chain?
@@ -5799,14 +6107,14 @@ sub set_CODE_type {
                     if ( !$follows_csc ) {
                         $has_side_comment = 1;
                         $CODE_type        = 'HSC';
-                        goto NEXT;
+                        next;
                     }
                 }
             }
 
             if ($is_static_block_comment) {
                 $CODE_type = $no_leading_space ? 'SBCX' : 'SBC';
-                goto NEXT;
+                next;
             }
             elsif ($Last_line_had_side_comment
                 && !$rOpts_maximum_consecutive_blank_lines
@@ -5817,11 +6125,11 @@ sub set_CODE_type {
                 # cannot be inserted.  There is related code in sub
                 # 'process_line_of_CODE'
                 $CODE_type = 'SBCX';
-                goto NEXT;
+                next;
             }
             else {
                 $CODE_type = 'BC';
-                goto NEXT;
+                next;
             }
         }
 
@@ -5829,12 +6137,12 @@ sub set_CODE_type {
 
         if ($rOpts_indent_only) {
             $CODE_type = 'IO';
-            goto NEXT;
+            next;
         }
 
         if ( !$rOpts_add_newlines ) {
             $CODE_type = 'NIN';
-            goto NEXT;
+            next;
         }
 
         #   Patch needed for MakeMaker.  Do not break a statement
@@ -5868,10 +6176,10 @@ sub set_CODE_type {
 
             # This code type has lower priority than others
             $CODE_type = 'VER';
-            goto NEXT;
+            next;
         }
-
-      NEXT:
+    }
+    continue {
         $line_of_tokens->{_code_type} = $CODE_type;
     }
 
@@ -5900,6 +6208,7 @@ sub find_non_indenting_braces {
         if ( $line_type ne 'CODE' ) {
 
             # shouldn't happen
+            DEVEL_MODE && Fault("unexpected line_type=$line_type\n");
             next;
         }
         my $CODE_type = $line_of_tokens->{_code_type};
@@ -5908,6 +6217,7 @@ sub find_non_indenting_braces {
         unless ( defined($Kfirst) && $rLL->[$Klast]->[_TYPE_] eq '#' ) {
 
             # shouldn't happen
+            DEVEL_MODE && Fault("did not get a comment\n");
             next;
         }
         next unless ( $Klast > $Kfirst );    # maybe HSC
@@ -6096,13 +6406,146 @@ BEGIN {
 
 }
 
+{ #<<< begin clousure respace_tokens
+
+my $rLL_new;    # This will be the new array of tokens
+
+# These are variables in $self
+my $rLL;
+my $length_function;
+my $is_encoded_data;
+
+my $K_closing_ternary;
+my $K_opening_ternary;
+my $rchildren_of_seqno;
+my $rhas_broken_code_block;
+my $rhas_broken_list;
+my $rhas_broken_list_with_lec;
+my $rhas_code_block;
+my $rhas_list;
+my $rhas_ternary;
+my $ris_assigned_structure;
+my $ris_broken_container;
+my $ris_excluded_lp_container;
+my $ris_list_by_seqno;
+my $ris_permanently_broken;
+my $rlec_count_by_seqno;
+my $roverride_cab3;
+my $rparent_of_seqno;
+my $rtype_count_by_seqno;
+my $rblock_type_of_seqno;
+
+my $K_opening_container;
+my $K_closing_container;
+
+my %K_first_here_doc_by_seqno;
+
+my $last_nonblank_code_type;
+my $last_nonblank_code_token;
+my $last_nonblank_block_type;
+my $last_last_nonblank_code_type;
+my $last_last_nonblank_code_token;
+
+my %seqno_stack;
+my %K_old_opening_by_seqno;
+my $depth_next;
+my $depth_next_max;
+
+my $cumulative_length;
+
+# Variables holding the current line info
+my $Ktoken_vars;
+my $Kfirst_old;
+my $Klast_old;
+my $Klast_old_code;
+my $CODE_type;
+
+my $rwhitespace_flags;
+
+sub initialize_respace_tokens_closure {
+
+    my ($self) = @_;
+
+    $rLL_new = [];    # This is the new array
+
+    $rLL             = $self->[_rLL_];
+    $length_function = $self->[_length_function_];
+    $is_encoded_data = $self->[_is_encoded_data_];
+
+    $K_closing_ternary         = $self->[_K_closing_ternary_];
+    $K_opening_ternary         = $self->[_K_opening_ternary_];
+    $rchildren_of_seqno        = $self->[_rchildren_of_seqno_];
+    $rhas_broken_code_block    = $self->[_rhas_broken_code_block_];
+    $rhas_broken_list          = $self->[_rhas_broken_list_];
+    $rhas_broken_list_with_lec = $self->[_rhas_broken_list_with_lec_];
+    $rhas_code_block           = $self->[_rhas_code_block_];
+    $rhas_list                 = $self->[_rhas_list_];
+    $rhas_ternary              = $self->[_rhas_ternary_];
+    $ris_assigned_structure    = $self->[_ris_assigned_structure_];
+    $ris_broken_container      = $self->[_ris_broken_container_];
+    $ris_excluded_lp_container = $self->[_ris_excluded_lp_container_];
+    $ris_list_by_seqno         = $self->[_ris_list_by_seqno_];
+    $ris_permanently_broken    = $self->[_ris_permanently_broken_];
+    $rlec_count_by_seqno       = $self->[_rlec_count_by_seqno_];
+    $roverride_cab3            = $self->[_roverride_cab3_];
+    $rparent_of_seqno          = $self->[_rparent_of_seqno_];
+    $rtype_count_by_seqno      = $self->[_rtype_count_by_seqno_];
+    $rblock_type_of_seqno      = $self->[_rblock_type_of_seqno_];
+
+    # Note that $K_opening_container and $K_closing_container have values
+    # defined in sub get_line() for the previous K indexes.  They were needed
+    # in case option 'indent-only' was set, and we didn't get here. We no longer
+    # need those and will eliminate them now to avoid any possible mixing of
+    # old and new values.
+    $K_opening_container = $self->[_K_opening_container_] = {};
+    $K_closing_container = $self->[_K_closing_container_] = {};
+
+    %K_first_here_doc_by_seqno = ();
+
+    $last_nonblank_code_type       = ';';
+    $last_nonblank_code_token      = ';';
+    $last_nonblank_block_type      = EMPTY_STRING;
+    $last_last_nonblank_code_type  = ';';
+    $last_last_nonblank_code_token = ';';
+
+    %seqno_stack            = ();
+    %K_old_opening_by_seqno = ();    # Note: old K index
+    $depth_next             = 0;
+    $depth_next_max         = 0;
+
+    # we will be setting token lengths as we go
+    $cumulative_length = 0;
+
+    $Ktoken_vars    = undef;          # the old K value of $rtoken_vars
+    $Kfirst_old     = undef;          # min K of old line
+    $Klast_old      = undef;          # max K of old line
+    $Klast_old_code = undef;          # K of last token if side comment
+    $CODE_type      = EMPTY_STRING;
+
+    # Set the whitespace flags, which indicate the token spacing preference.
+    $rwhitespace_flags = $self->set_whitespace_flags();
+
+    return;
+
+} ## end sub initialize_respace_tokens_closure
+
 sub respace_tokens {
 
     my $self = shift;
-    return if $rOpts->{'indent-only'};
 
+    #--------------------------------------------------------------------------
     # This routine is called once per file to do as much formatting as possible
     # before new line breaks are set.
+    #--------------------------------------------------------------------------
+
+    # Return parameters:
+    # Set $severe_error=true if processing must terminate immediately
+    my ( $severe_error, $rqw_lines );
+
+    # We change any spaces in --indent-only mode
+    if ( $rOpts->{'indent-only'} ) {
+        return ( $severe_error, $rqw_lines );
+    }
 
     # This routine makes all necessary and possible changes to the tokenization
     # after the initial tokenization of the file. This is a tedious routine,
@@ -6121,1160 +6564,704 @@ sub respace_tokens {
     # Method: The old tokens are copied one-by-one, with changes, from the old
     # linear storage array $rLL to a new array $rLL_new.
 
-    my $rLL             = $self->[_rLL_];
-    my $Klimit_old      = $self->[_Klimit_];
-    my $rlines          = $self->[_rlines_];
-    my $length_function = $self->[_length_function_];
-    my $is_encoded_data = $self->[_is_encoded_data_];
+    # (re-)initialize closure variables for this problem
+    $self->initialize_respace_tokens_closure();
 
-    my $rLL_new = [];    # This is the new array
-    my $rtoken_vars;
-    my $Ktoken_vars;                   # the old K value of $rtoken_vars
-    my ( $Kfirst_old, $Klast_old );    # Range of old line
-    my $Klast_old_code;                # K of last token if side comment
-    my $Kmax = @{$rLL} - 1;
-
-    my $CODE_type = EMPTY_STRING;
+    #--------------------------------
+    # Main over all lines of the file
+    #--------------------------------
+    my $rlines    = $self->[_rlines_];
     my $line_type = EMPTY_STRING;
+    my $last_K_out;
 
-    # Set the whitespace flags, which indicate the token spacing preference.
-    my $rwhitespace_flags = $self->set_whitespace_flags();
+    foreach my $line_of_tokens ( @{$rlines} ) {
 
-    # we will be setting token lengths as we go
-    my $cumulative_length = 0;
+        my $input_line_number = $line_of_tokens->{_line_number};
+        my $last_line_type    = $line_type;
+        $line_type = $line_of_tokens->{_line_type};
+        next unless ( $line_type eq 'CODE' );
+        my $last_CODE_type = $CODE_type;
+        $CODE_type = $line_of_tokens->{_code_type};
 
-    my %seqno_stack;
-    my %K_old_opening_by_seqno = ();    # Note: old K index
-    my $depth_next             = 0;
-    my $depth_next_max         = 0;
+        if ( $CODE_type eq 'BL' ) {
+            my $seqno = $seqno_stack{ $depth_next - 1 };
+            if ( defined($seqno) ) {
+                $self->[_rblank_and_comment_count_]->{$seqno} += 1;
+                $self->set_permanently_broken($seqno)
+                  if (!$ris_permanently_broken->{$seqno}
+                    && $rOpts_maximum_consecutive_blank_lines );
+            }
+        }
 
-    # Note that $K_opening_container and $K_closing_container have values
-    # defined in sub get_line() for the previous K indexes.  They were needed
-    # in case option 'indent-only' was set, and we didn't get here. We no longer
-    # need those and will eliminate them now to avoid any possible mixing of
-    # old and new values.
-    my $K_opening_container = $self->[_K_opening_container_] = {};
-    my $K_closing_container = $self->[_K_closing_container_] = {};
-
-    my $K_closing_ternary         = $self->[_K_closing_ternary_];
-    my $K_opening_ternary         = $self->[_K_opening_ternary_];
-    my $rK_phantom_semicolons     = $self->[_rK_phantom_semicolons_];
-    my $rchildren_of_seqno        = $self->[_rchildren_of_seqno_];
-    my $rhas_broken_code_block    = $self->[_rhas_broken_code_block_];
-    my $rhas_broken_list          = $self->[_rhas_broken_list_];
-    my $rhas_broken_list_with_lec = $self->[_rhas_broken_list_with_lec_];
-    my $rhas_code_block           = $self->[_rhas_code_block_];
-    my $rhas_list                 = $self->[_rhas_list_];
-    my $rhas_ternary              = $self->[_rhas_ternary_];
-    my $ris_assigned_structure    = $self->[_ris_assigned_structure_];
-    my $ris_broken_container      = $self->[_ris_broken_container_];
-    my $ris_excluded_lp_container = $self->[_ris_excluded_lp_container_];
-    my $ris_list_by_seqno         = $self->[_ris_list_by_seqno_];
-    my $ris_permanently_broken    = $self->[_ris_permanently_broken_];
-    my $rlec_count_by_seqno       = $self->[_rlec_count_by_seqno_];
-    my $roverride_cab3            = $self->[_roverride_cab3_];
-    my $rparent_of_seqno          = $self->[_rparent_of_seqno_];
-    my $rtype_count_by_seqno      = $self->[_rtype_count_by_seqno_];
-    my $rblock_type_of_seqno      = $self->[_rblock_type_of_seqno_];
+        my $rK_range = $line_of_tokens->{_rK_range};
+        my ( $Kfirst, $Klast ) = @{$rK_range};
+        next unless defined($Kfirst);
+        ( $Kfirst_old, $Klast_old ) = ( $Kfirst, $Klast );
+        $Klast_old_code = $Klast_old;
 
-    my $last_nonblank_code_type       = ';';
-    my $last_nonblank_code_token      = ';';
-    my $last_nonblank_block_type      = EMPTY_STRING;
-    my $last_last_nonblank_code_type  = ';';
-    my $last_last_nonblank_code_token = ';';
+        # Be sure an old K value is defined for sub store_token
+        $Ktoken_vars = $Kfirst;
 
-    my %K_first_here_doc_by_seqno;
+        # Check for correct sequence of token indexes...
+        # An error here means that sub write_line() did not correctly
+        # package the tokenized lines as it received them.  If we
+        # get a fault here it has not output a continuous sequence
+        # of K values.  Or a line of CODE may have been mis-marked as
+        # something else.  There is no good way to continue after such an
+        # error.
+        if ( defined($last_K_out) ) {
+            if ( $Kfirst != $last_K_out + 1 ) {
+                Fault_Warn(
+                    "Program Bug: last K out was $last_K_out but Kfirst=$Kfirst"
+                );
+                $severe_error = 1;
+                return ( $severe_error, $rqw_lines );
+            }
+        }
+        else {
 
-    my $set_permanently_broken = sub {
-        my ($seqno) = @_;
-        while ( defined($seqno) ) {
-            $ris_permanently_broken->{$seqno} = 1;
-            $seqno = $rparent_of_seqno->{$seqno};
+            # The first token should always have been given index 0 by sub
+            # write_line()
+            if ( $Kfirst != 0 ) {
+                Fault("Program Bug: first K is $Kfirst but should be 0");
+            }
         }
-        return;
-    };
-    my $store_token = sub {
-        my ($item) = @_;
+        $last_K_out = $Klast;
 
-        # This will be the index of this item in the new array
-        my $KK_new = @{$rLL_new};
+        # Handle special lines of code
+        if ( $CODE_type && $CODE_type ne 'NIN' && $CODE_type ne 'VER' ) {
 
-        #------------------------------------------------------------------
-        # NOTE: called once per token so coding efficiency is critical here
-        #------------------------------------------------------------------
+            # CODE_types are as follows.
+            # 'BL' = Blank Line
+            # 'VB' = Verbatim - line goes out verbatim
+            # 'FS' = Format Skipping - line goes out verbatim, no blanks
+            # 'IO' = Indent Only - only indentation may be changed
+            # 'NIN' = No Internal Newlines - line does not get broken
+            # 'HSC'=Hanging Side Comment - fix this hanging side comment
+            # 'BC'=Block Comment - an ordinary full line comment
+            # 'SBC'=Static Block Comment - a block comment which does not get
+            #      indented
+            # 'SBCX'=Static Block Comment Without Leading Space
+            # 'VER'=VERSION statement
+            # '' or (undefined) - no restructions
 
-        my $type       = $item->[_TYPE_];
-        my $is_blank   = $type eq 'b';
-        my $block_type = EMPTY_STRING;
+            # For a hanging side comment we insert an empty quote before
+            # the comment so that it becomes a normal side comment and
+            # will be aligned by the vertical aligner
+            if ( $CODE_type eq 'HSC' ) {
 
-        # Do not output consecutive blanks. This situation should have been
-        # prevented earlier, but it is worth checking because later routines
-        # make this assumption.
-        if ( $is_blank && $KK_new && $rLL_new->[-1]->[_TYPE_] eq 'b' ) {
-            return;
-        }
+                # Safety Check: This must be a line with one token (a comment)
+                my $rvars_Kfirst = $rLL->[$Kfirst];
+                if ( $Kfirst == $Klast && $rvars_Kfirst->[_TYPE_] eq '#' ) {
 
-        # check for a sequenced item (i.e., container or ?/:)
-        my $type_sequence = $item->[_TYPE_SEQUENCE_];
-        my $token         = $item->[_TOKEN_];
-        if ($type_sequence) {
+                    # Note that even if the flag 'noadd-whitespace' is set, we
+                    # will make an exception here and allow a blank to be
+                    # inserted to push the comment to the right.  We can think
+                    # of this as an adjustment of indentation rather than
+                    # whitespace between tokens. This will also prevent the
+                    # hanging side comment from getting converted to a block
+                    # comment if whitespace gets deleted, as for example with
+                    # the -extrude and -mangle options.
+                    my $rcopy =
+                      copy_token_as_type( $rvars_Kfirst, 'q', EMPTY_STRING );
+                    $self->store_token($rcopy);
+                    $rcopy = copy_token_as_type( $rvars_Kfirst, 'b', SPACE );
+                    $self->store_token($rcopy);
+                    $self->store_token($rvars_Kfirst);
+                    next;
+                }
+                else {
 
-            if ( $is_opening_token{$token} ) {
+                    # This line was mis-marked by sub scan_comment.  Catch in
+                    # DEVEL_MODE, otherwise try to repair and keep going.
+                    Fault(
+                        "Program bug. A hanging side comment has been mismarked"
+                    ) if (DEVEL_MODE);
 
-                $K_opening_container->{$type_sequence} = $KK_new;
-                $block_type = $rblock_type_of_seqno->{$type_sequence};
+                    $CODE_type = EMPTY_STRING;
+                    $line_of_tokens->{_code_type} = $CODE_type;
+                }
+            }
 
-                # Fix for case b1100: Count a line ending in ', [' as having
-                # a line-ending comma.  Otherwise, these commas can be hidden
-                # with something like --opening-square-bracket-right
-                if (   $last_nonblank_code_type eq ','
-                    && $Ktoken_vars == $Klast_old_code
-                    && $Ktoken_vars > $Kfirst_old )
-                {
-                    $rlec_count_by_seqno->{$type_sequence}++;
-                }
-
-                if (   $last_nonblank_code_type eq '='
-                    || $last_nonblank_code_type eq '=>' )
-                {
-                    $ris_assigned_structure->{$type_sequence} =
-                      $last_nonblank_code_type;
-                }
-
-                my $seqno_parent = $seqno_stack{ $depth_next - 1 };
-                $seqno_parent = SEQ_ROOT unless defined($seqno_parent);
-                push @{ $rchildren_of_seqno->{$seqno_parent} }, $type_sequence;
-                $rparent_of_seqno->{$type_sequence}     = $seqno_parent;
-                $seqno_stack{$depth_next}               = $type_sequence;
-                $K_old_opening_by_seqno{$type_sequence} = $Ktoken_vars;
-                $depth_next++;
-
-                if ( $depth_next > $depth_next_max ) {
-                    $depth_next_max = $depth_next;
-                }
-            }
-            elsif ( $is_closing_token{$token} ) {
-
-                $K_closing_container->{$type_sequence} = $KK_new;
-                $block_type = $rblock_type_of_seqno->{$type_sequence};
-
-                # Do not include terminal commas in counts
-                if (   $last_nonblank_code_type eq ','
-                    || $last_nonblank_code_type eq '=>' )
-                {
-                    my $seqno = $seqno_stack{ $depth_next - 1 };
-                    if ($seqno) {
-                        $rtype_count_by_seqno->{$seqno}
-                          ->{$last_nonblank_code_type}--;
-
-                        if (   $Ktoken_vars == $Kfirst_old
-                            && $last_nonblank_code_type eq ','
-                            && $rlec_count_by_seqno->{$seqno} )
-                        {
-                            $rlec_count_by_seqno->{$seqno}--;
-                        }
-                    }
-                }
-
-                # Update the stack...
-                $depth_next--;
+            # Copy tokens unchanged
+            foreach my $KK ( $Kfirst .. $Klast ) {
+                $Ktoken_vars = $KK;
+                $self->store_token( $rLL->[$KK] );
             }
-            else {
-
-                # For ternary, note parent but do not include as child
-                my $seqno_parent = $seqno_stack{ $depth_next - 1 };
-                $seqno_parent = SEQ_ROOT unless defined($seqno_parent);
-                $rparent_of_seqno->{$type_sequence} = $seqno_parent;
+            next;
+        }
 
-                # These are not yet used but could be useful
-                if ( $token eq '?' ) {
-                    $K_opening_ternary->{$type_sequence} = $KK_new;
-                }
-                elsif ( $token eq ':' ) {
-                    $K_closing_ternary->{$type_sequence} = $KK_new;
-                }
-                else {
+        # Handle normal line..
 
-                    # We really shouldn't arrive here, just being cautious:
-                    # The only sequenced types output by the tokenizer are the
-                    # opening & closing containers and the ternary types. Each
-                    # of those was checked above. So we would only get here
-                    # if the tokenizer has been changed to mark some other
-                    # tokens with sequence numbers.
-                    if (DEVEL_MODE) {
-                        Fault(
-"Unexpected token type with sequence number: type='$type', seqno='$type_sequence'"
-                        );
-                    }
-                }
+        # Define index of last token before any side comment for comma counts
+        my $type_end = $rLL->[$Klast_old_code]->[_TYPE_];
+        if ( ( $type_end eq '#' || $type_end eq 'b' )
+            && $Klast_old_code > $Kfirst_old )
+        {
+            $Klast_old_code--;
+            if (   $rLL->[$Klast_old_code]->[_TYPE_] eq 'b'
+                && $Klast_old_code > $Kfirst_old )
+            {
+                $Klast_old_code--;
             }
         }
 
-        # Find the length of this token.  Later it may be adjusted if phantom
-        # or ignoring side comment lengths.
-        my $token_length =
-            $is_encoded_data
-          ? $length_function->($token)
-          : length($token);
-
-        # handle comments
-        my $is_comment = $type eq '#';
-        if ($is_comment) {
-
-            # trim comments if necessary
-            my $ord = ord( substr( $token, -1, 1 ) );
+        # Insert any essential whitespace between lines
+        # if last line was normal CODE.
+        # Patch for rt #125012: use K_previous_code rather than '_nonblank'
+        # because comments may disappear.
+        if ( $last_line_type eq 'CODE' ) {
+            my $type_next  = $rLL->[$Kfirst]->[_TYPE_];
+            my $token_next = $rLL->[$Kfirst]->[_TOKEN_];
             if (
-                $ord > 0
-                && (   $ord < ORD_PRINTABLE_MIN
-                    || $ord > ORD_PRINTABLE_MAX )
-                && $token =~ s/\s+$//
+                is_essential_whitespace(
+                    $last_last_nonblank_code_token,
+                    $last_last_nonblank_code_type,
+                    $last_nonblank_code_token,
+                    $last_nonblank_code_type,
+                    $token_next,
+                    $type_next,
+                )
               )
             {
-                $token_length = $length_function->($token);
-                $item->[_TOKEN_] = $token;
-            }
 
-            # Mark length of side comments as just 1 if sc lengths are ignored
-            if ( $rOpts_ignore_side_comment_lengths
-                && ( !$CODE_type || $CODE_type eq 'HSC' ) )
-            {
-                $token_length = 1;
-            }
-            my $seqno = $seqno_stack{ $depth_next - 1 };
-            if ( defined($seqno)
-                && !$ris_permanently_broken->{$seqno} )
-            {
-                $set_permanently_broken->($seqno);
+                # Copy this first token as blank, but use previous line number
+                my $rcopy = copy_token_as_type( $rLL->[$Kfirst], 'b', SPACE );
+                $rcopy->[_LINE_INDEX_] =
+                  $rLL_new->[-1]->[_LINE_INDEX_];
+
+                # The level and ci_level of newly created spaces should be the
+                # same as the previous token. Otherwise blinking states can
+                # be created if the -lp mode is used. See similar coding in
+                # sub 'store_space_and_token'.  Fixes cases b1109 b1110.
+                $rcopy->[_LEVEL_] =
+                  $rLL_new->[-1]->[_LEVEL_];
+                $rcopy->[_CI_LEVEL_] =
+                  $rLL_new->[-1]->[_CI_LEVEL_];
+
+                $self->store_token($rcopy);
             }
         }
 
-        $item->[_TOKEN_LENGTH_] = $token_length;
-
-        # and update the cumulative length
-        $cumulative_length += $token_length;
+        #-----------------------------------------------
+        # Inner loop to respace tokens on a line of code
+        #-----------------------------------------------
 
-        # Save the length sum to just AFTER this token
-        $item->[_CUMULATIVE_LENGTH_] = $cumulative_length;
+        # The inner loop is in a separate sub for clarity
+        $self->respace_tokens_inner_loop( $Kfirst, $Klast, $input_line_number );
 
-        if ( !$is_blank && !$is_comment ) {
+    }    # End line loop
 
-            # Remember the most recent two non-blank, non-comment tokens.
-            # NOTE: the phantom semicolon code may change the output stack
-            # without updating these values.  Phantom semicolons are considered
-            # the same as blanks for now, but future needs might change that.
-            # See the related note in sub '$add_phantom_semicolon'.
-            $last_last_nonblank_code_type  = $last_nonblank_code_type;
-            $last_last_nonblank_code_token = $last_nonblank_code_token;
+    # finalize data structures
+    $self->respace_post_loop_ops();
 
-            $last_nonblank_code_type  = $type;
-            $last_nonblank_code_token = $token;
-            $last_nonblank_block_type = $block_type;
+    # Reset memory to be the new array
+    $self->[_rLL_] = $rLL_new;
+    my $Klimit;
+    if ( @{$rLL_new} ) { $Klimit = @{$rLL_new} - 1 }
+    $self->[_Klimit_] = $Klimit;
 
-            # count selected types
-            if ( $is_counted_type{$type} ) {
-                my $seqno = $seqno_stack{ $depth_next - 1 };
-                if ( defined($seqno) ) {
-                    $rtype_count_by_seqno->{$seqno}->{$type}++;
+    # During development, verify that the new array still looks okay.
+    DEVEL_MODE && $self->check_token_array();
 
-                    # Count line-ending commas for -bbx
-                    if ( $type eq ',' && $Ktoken_vars == $Klast_old_code ) {
-                        $rlec_count_by_seqno->{$seqno}++;
-                    }
+    # update the token limits of each line
+    ( $severe_error, $rqw_lines ) = $self->resync_lines_and_tokens();
 
-                    # Remember index of first here doc target
-                    if ( $type eq 'h' && !$K_first_here_doc_by_seqno{$seqno} ) {
-                        $K_first_here_doc_by_seqno{$seqno} = $KK_new;
-                    }
-                }
-            }
-        }
+    return ( $severe_error, $rqw_lines );
+} ## end sub respace_tokens
 
-        # For reference, here is how to get the parent sequence number.
-        # This is not used because it is slower than finding it on the fly
-        # in sub parent_seqno_by_K:
+sub respace_tokens_inner_loop {
 
-        # my $seqno_parent =
-        #     $type_sequence && $is_opening_token{$token}
-        #   ? $seqno_stack{ $depth_next - 2 }
-        #   : $seqno_stack{ $depth_next - 1 };
-        # my $KK = @{$rLL_new};
-        # $rseqno_of_parent_by_K->{$KK} = $seqno_parent;
+    my ( $self, $Kfirst, $Klast, $input_line_number ) = @_;
 
-        # and finally, add this item to the new array
-        push @{$rLL_new}, $item;
-        return;
-    };
+    #-----------------------------------------------------------------
+    # Loop to copy all tokens on one line, making any spacing changes,
+    # while also collecting information needed by later subs.
+    #-----------------------------------------------------------------
+    foreach my $KK ( $Kfirst .. $Klast ) {
 
-    my $store_token_and_space = sub {
-        my ( $item, $want_space ) = @_;
+        # TODO: consider eliminating this closure var by passing directly to
+        # store_token following pattern of store_tokens_to_go.
+        $Ktoken_vars = $KK;
 
-        # store a token with preceding space if requested and needed
+        my $rtoken_vars = $rLL->[$KK];
+        my $type        = $rtoken_vars->[_TYPE_];
 
-        # First store the space
-        if (   $want_space
-            && @{$rLL_new}
-            && $rLL_new->[-1]->[_TYPE_] ne 'b'
-            && $rOpts_add_whitespace )
-        {
-            my $rcopy = [ @{$item} ];
-            $rcopy->[_TYPE_]          = 'b';
-            $rcopy->[_TOKEN_]         = SPACE;
-            $rcopy->[_TYPE_SEQUENCE_] = EMPTY_STRING;
+        # Handle a blank space ...
+        if ( $type eq 'b' ) {
 
-            $rcopy->[_LINE_INDEX_] =
-              $rLL_new->[-1]->[_LINE_INDEX_];
+            # Delete it if not wanted by whitespace rules
+            # or we are deleting all whitespace
+            # Note that whitespace flag is a flag indicating whether a
+            # white space BEFORE the token is needed
+            next if ( $KK >= $Klast );    # skip terminal blank
+            my $Knext = $KK + 1;
 
-            # Patch 23-Jan-2021 to fix -lp blinkers:
-            # The level and ci_level of newly created spaces should be the same
-            # as the previous token.  Otherwise the coding for the -lp option
-            # can create a blinking state in some rare cases.
-            $rcopy->[_LEVEL_] =
-              $rLL_new->[-1]->[_LEVEL_];
-            $rcopy->[_CI_LEVEL_] =
-              $rLL_new->[-1]->[_CI_LEVEL_];
+            if ($rOpts_freeze_whitespace) {
+                $self->store_token($rtoken_vars);
+                next;
+            }
 
-            $store_token->($rcopy);
-        }
+            my $ws = $rwhitespace_flags->[$Knext];
+            if (   $ws == -1
+                || $rOpts_delete_old_whitespace )
+            {
 
-        # then the token
-        $store_token->($item);
-        return;
-    };
+                my $token_next = $rLL->[$Knext]->[_TOKEN_];
+                my $type_next  = $rLL->[$Knext]->[_TYPE_];
 
-    my $add_phantom_semicolon = sub {
+                my $do_not_delete = is_essential_whitespace(
+                    $last_last_nonblank_code_token,
+                    $last_last_nonblank_code_type,
+                    $last_nonblank_code_token,
+                    $last_nonblank_code_type,
+                    $token_next,
+                    $type_next,
+                );
 
-        my ($KK) = @_;
+                # Note that repeated blanks will get filtered out here
+                next unless ($do_not_delete);
+            }
 
-        my $Kp = $self->K_previous_nonblank( undef, $rLL_new );
-        return unless ( defined($Kp) );
+            # make it just one character
+            $rtoken_vars->[_TOKEN_] = SPACE;
+            $self->store_token($rtoken_vars);
+            next;
+        }
 
-        # we are only adding semicolons for certain block types
-        my $type_sequence = $rLL->[$KK]->[_TYPE_SEQUENCE_];
-        return unless ($type_sequence);
-        my $block_type = $rblock_type_of_seqno->{$type_sequence};
-        return unless ($block_type);
-        return
-          unless ( $ok_to_add_semicolon_for_block_type{$block_type}
-            || $block_type =~ /^(sub|package)/
-            || $block_type =~ /^\w+\:$/ );
+        my $token = $rtoken_vars->[_TOKEN_];
 
-        my $type_p          = $rLL_new->[$Kp]->[_TYPE_];
-        my $token_p         = $rLL_new->[$Kp]->[_TOKEN_];
-        my $type_sequence_p = $rLL_new->[$Kp]->[_TYPE_SEQUENCE_];
+        # Handle a sequenced token ... i.e. one of ( ) { } [ ] ? :
+        if ( $rtoken_vars->[_TYPE_SEQUENCE_] ) {
 
-        # Do not add a semicolon if...
-        return
-          if (
+            # One of ) ] } ...
+            if ( $is_closing_token{$token} ) {
 
-            # it would follow a comment (and be isolated)
-            $type_p eq '#'
+                my $type_sequence = $rtoken_vars->[_TYPE_SEQUENCE_];
+                my $block_type    = $rblock_type_of_seqno->{$type_sequence};
 
-            # it follows a code block ( because they are not always wanted
-            # there and may add clutter)
-            || $type_sequence_p && $rblock_type_of_seqno->{$type_sequence_p}
+                #---------------------------------------------
+                # check for semicolon addition in a code block
+                #---------------------------------------------
+                if ($block_type) {
 
-            # it would follow a label
-            || $type_p eq 'J'
+                    # if not preceded by a ';' ..
+                    if ( $last_nonblank_code_type ne ';' ) {
 
-            # it would be inside a 'format' statement (and cause syntax error)
-            || (   $type_p eq 'k'
-                && $token_p =~ /format/ )
+                        # tentatively insert a semicolon if appropriate
+                        $self->add_phantom_semicolon($KK)
+                          if $rOpts->{'add-semicolons'};
+                    }
+                }
 
-          );
+                #----------------------------------------------------------
+                # check for addition/deletion of a trailing comma in a list
+                #----------------------------------------------------------
+                else {
 
-        # Do not add a semicolon if it would impede a weld with an immediately
-        # following closing token...like this
-        #   { ( some code ) }
-        #                  ^--No semicolon can go here
+                    # if this is a list ..
+                    my $rtype_count = $rtype_count_by_seqno->{$type_sequence};
+                    if (   $rtype_count
+                        && $rtype_count->{','}
+                        && !$rtype_count->{';'}
+                        && !$rtype_count->{'f'} )
+                    {
 
-        # look at the previous token... note use of the _NEW rLL array here,
-        # but sequence numbers are invariant.
-        my $seqno_inner = $rLL_new->[$Kp]->[_TYPE_SEQUENCE_];
+                        # if NOT preceded by a comma..
+                        if ( $last_nonblank_code_type ne ',' ) {
 
-        # If it is also a CLOSING token we have to look closer...
-        if (
-               $seqno_inner
-            && $is_closing_token{$token_p}
+                            # insert a comma if requested
+                            if (   $rOpts_add_trailing_commas
+                                && %trailing_comma_rules )
+                            {
+                                $self->add_trailing_comma( $KK, $Kfirst,
+                                    $trailing_comma_rules{$token} );
+                            }
+                        }
 
-            # we only need to look if there is just one inner container..
-            && defined( $rchildren_of_seqno->{$type_sequence} )
-            && @{ $rchildren_of_seqno->{$type_sequence} } == 1
-          )
-        {
+                        # if preceded by a comma ..
+                        else {
 
-            # Go back and see if the corresponding two OPENING tokens are also
-            # together.  Note that we are using the OLD K indexing here:
-            my $K_outer_opening = $K_old_opening_by_seqno{$type_sequence};
-            if ( defined($K_outer_opening) ) {
-                my $K_nxt = $self->K_next_nonblank($K_outer_opening);
-                if ( defined($K_nxt) ) {
-                    my $seqno_nxt = $rLL->[$K_nxt]->[_TYPE_SEQUENCE_];
+                            # delete a trailing comma if requested
+                            my $deleted;
+                            if (   $rOpts_delete_trailing_commas
+                                && %trailing_comma_rules )
+                            {
+                                $deleted =
+                                  $self->delete_trailing_comma( $KK, $Kfirst,
+                                    $trailing_comma_rules{$token} );
+                            }
 
-                    # Is the next token after the outer opening the same as
-                    # our inner closing (i.e. same sequence number)?
-                    # If so, do not insert a semicolon here.
-                    return if ( $seqno_nxt && $seqno_nxt == $seqno_inner );
+                            # delete a weld-interfering comma if requested
+                            if (  !$deleted
+                                && $rOpts_delete_weld_interfering_commas
+                                && $is_closing_type{
+                                    $last_last_nonblank_code_type} )
+                            {
+                                $self->delete_weld_interfering_comma($KK);
+                            }
+                        }
+                    }
                 }
             }
         }
 
-        # We will insert an empty semicolon here as a placeholder.  Later, if
-        # it becomes the last token on a line, we will bring it to life.  The
-        # advantage of doing this is that (1) we just have to check line
-        # endings, and (2) the phantom semicolon has zero width and therefore
-        # won't cause needless breaks of one-line blocks.
-        my $Ktop = -1;
-        if (   $rLL_new->[$Ktop]->[_TYPE_] eq 'b'
-            && $want_left_space{';'} == WS_NO )
-        {
+        # Modify certain tokens here for whitespace
+        # The following is not yet done, but could be:
+        #   sub (x x x)
+        #     ( $type =~ /^[wit]$/ )
+        elsif ( $is_wit{$type} ) {
+
+            # change '$  var'  to '$var' etc
+            # change '@    '   to '@'
+            # Examples: <<snippets/space1.in>>
+            my $ord = ord( substr( $token, 1, 1 ) );
+            if (
 
-            # convert the blank into a semicolon..
-            # be careful: we are working on the new stack top
-            # on a token which has been stored.
-            my $rcopy = copy_token_as_type( $rLL_new->[$Ktop], 'b', SPACE );
+                # quick test for possible blank at second char
+                $ord > 0 && ( $ord < ORD_PRINTABLE_MIN
+                    || $ord > ORD_PRINTABLE_MAX )
+              )
+            {
+                my ( $sigil, $word ) = split /\s+/, $token, 2;
 
-            # Convert the existing blank to:
-            #   a phantom semicolon for one_line_block option = 0 or 1
-            #   a real semicolon    for one_line_block option = 2
-            my $tok     = EMPTY_STRING;
-            my $len_tok = 0;
-            if ( $rOpts_one_line_block_semicolons == 2 ) {
-                $tok     = ';';
-                $len_tok = 1;
+                # $sigil =~ /^[\$\&\%\*\@]$/ )
+                if ( $is_sigil{$sigil} ) {
+                    $token = $sigil;
+                    $token .= $word if ( defined($word) );    # fix c104
+                    $rtoken_vars->[_TOKEN_] = $token;
+                }
             }
 
-            $rLL_new->[$Ktop]->[_TOKEN_]        = $tok;
-            $rLL_new->[$Ktop]->[_TOKEN_LENGTH_] = $len_tok;
-            $rLL_new->[$Ktop]->[_TYPE_]         = ';';
+            # Trim certain spaces in identifiers
+            if ( $type eq 'i' ) {
 
-            # NOTE: we are changing the output stack without updating variables
-            # $last_nonblank_code_type, etc. Future needs might require that
-            # those variables be updated here.  For now, it seems ok to skip
-            # this.
+                if (
+                    (
+                        substr( $token, 0, 3 ) eq 'sub'
+                        || $rOpts_sub_alias_list
+                    )
+                    && $token =~ /$SUB_PATTERN/
+                  )
+                {
 
-            # Save list of new K indexes of phantom semicolons.
-            # This will be needed if we want to undo them for iterations in
-            # future coding.
-            push @{$rK_phantom_semicolons}, @{$rLL_new} - 1;
+                    # -spp = 0 : no space before opening prototype paren
+                    # -spp = 1 : stable (follow input spacing)
+                    # -spp = 2 : always space before opening prototype paren
+                    if ( !defined($rOpts_space_prototype_paren)
+                        || $rOpts_space_prototype_paren == 1 )
+                    {
+                        ## default: stable
+                    }
+                    elsif ( $rOpts_space_prototype_paren == 0 ) {
+                        $token =~ s/\s+\(/\(/;
+                    }
+                    elsif ( $rOpts_space_prototype_paren == 2 ) {
+                        $token =~ s/\(/ (/;
+                    }
 
-            # Then store a new blank
-            $store_token->($rcopy);
-        }
-        else {
+                    # one space max, and no tabs
+                    $token =~ s/\s+/ /g;
+                    $rtoken_vars->[_TOKEN_] = $token;
+                }
 
-            # Patch for issue c078: keep line indexes in order.  If the top
-            # token is a space that we are keeping (due to '-wls=';') then
-            # we have to check that old line indexes stay in order.
-            # In very rare
-            # instances in which side comments have been deleted and converted
-            # into blanks, we may have filtered down multiple blanks into just
-            # one. In that case the top blank may have a higher line number
-            # than the previous nonblank token. Although the line indexes of
-            # blanks are not really significant, we need to keep them in order
-            # in order to pass error checks.
-            if ( $rLL_new->[$Ktop]->[_TYPE_] eq 'b' ) {
-                my $old_top_ix = $rLL_new->[$Ktop]->[_LINE_INDEX_];
-                my $new_top_ix = $rLL_new->[$Kp]->[_LINE_INDEX_];
-                if ( $new_top_ix < $old_top_ix ) {
-                    $rLL_new->[$Ktop]->[_LINE_INDEX_] = $new_top_ix;
-                }
-            }
-
-            my $rcopy =
-              copy_token_as_type( $rLL_new->[$Kp], ';', EMPTY_STRING );
-            $store_token->($rcopy);
-            push @{$rK_phantom_semicolons}, @{$rLL_new} - 1;
-        }
-        return;
-    };
+                # clean up spaces in package identifiers, like
+                #   "package        Bob::Dog;"
+                elsif ( substr( $token, 0, 7 ) eq 'package'
+                    && $token =~ /^package\s/ )
+                {
+                    $token =~ s/\s+/ /g;
+                    $rtoken_vars->[_TOKEN_] = $token;
+                }
 
-    my $check_Q = sub {
+                # trim identifiers of trailing blanks which can occur
+                # under some unusual circumstances, such as if the
+                # identifier 'witch' has trailing blanks on input here:
+                #
+                # sub
+                # witch
+                # ()   # prototype may be on new line ...
+                # ...
+                my $ord_ch = ord( substr( $token, -1, 1 ) );
+                if (
 
-        # Check that a quote looks okay
-        # This sub works but needs to by sync'd with the log file output
-        # before it can be used.
-        my ( $KK, $Kfirst, $line_number ) = @_;
-        my $token = $rLL->[$KK]->[_TOKEN_];
-        $self->note_embedded_tab($line_number) if ( $token =~ "\t" );
+                    # quick check for possible ending space
+                    $ord_ch > 0 && ( $ord_ch < ORD_PRINTABLE_MIN
+                        || $ord_ch > ORD_PRINTABLE_MAX )
+                  )
+                {
+                    $token =~ s/\s+$//g;
+                    $rtoken_vars->[_TOKEN_] = $token;
+                }
+            }
+        }
 
-        # The remainder of this routine looks for something like
-        #        '$var = s/xxx/yyy/;'
-        # in case it should have been '$var =~ s/xxx/yyy/;'
+        # handle semicolons
+        elsif ( $type eq ';' ) {
 
-        # Start by looking for a token beginning with one of: s y m / tr
-        return
-          unless ( $is_s_y_m_slash{ substr( $token, 0, 1 ) }
-            || substr( $token, 0, 2 ) eq 'tr' );
+            # Remove unnecessary semicolons, but not after bare
+            # blocks, where it could be unsafe if the brace is
+            # mis-tokenized.
+            if (
+                $rOpts->{'delete-semicolons'}
+                && (
+                    (
+                           $last_nonblank_block_type
+                        && $last_nonblank_code_type eq '}'
+                        && (
+                            $is_block_without_semicolon{
+                                $last_nonblank_block_type}
+                            || $last_nonblank_block_type =~ /$SUB_PATTERN/
+                            || $last_nonblank_block_type =~ /^\w+:$/
+                        )
+                    )
+                    || $last_nonblank_code_type eq ';'
+                )
+              )
+            {
 
-        # ... and preceded by one of: = == !=
-        my $Kp = $self->K_previous_nonblank( undef, $rLL_new );
-        return unless ( defined($Kp) );
-        my $previous_nonblank_type = $rLL_new->[$Kp]->[_TYPE_];
-        return unless ( $is_unexpected_equals{$previous_nonblank_type} );
-        my $previous_nonblank_token = $rLL_new->[$Kp]->[_TOKEN_];
+                # This looks like a deletable semicolon, but even if a
+                # semicolon can be deleted it is not necessarily best to do
+                # so.  We apply these additional rules for deletion:
+                # - Always ok to delete a ';' at the end of a line
+                # - Never delete a ';' before a '#' because it would
+                #   promote it to a block comment.
+                # - If a semicolon is not at the end of line, then only
+                #   delete if it is followed by another semicolon or closing
+                #   token.  This includes the comment rule.  It may take
+                #   two passes to get to a final state, but it is a little
+                #   safer.  For example, keep the first semicolon here:
+                #      eval { sub bubba { ok(0) }; ok(0) } || ok(1);
+                #   It is not required but adds some clarity.
+                my $ok_to_delete = 1;
+                if ( $KK < $Klast ) {
+                    my $Kn = $self->K_next_nonblank($KK);
+                    if ( defined($Kn) && $Kn <= $Klast ) {
+                        my $next_nonblank_token_type = $rLL->[$Kn]->[_TYPE_];
+                        $ok_to_delete = $next_nonblank_token_type eq ';'
+                          || $next_nonblank_token_type eq '}';
+                    }
+                }
 
-        my $previous_nonblank_type_2  = 'b';
-        my $previous_nonblank_token_2 = EMPTY_STRING;
-        my $Kpp = $self->K_previous_nonblank( $Kp, $rLL_new );
-        if ( defined($Kpp) ) {
-            $previous_nonblank_type_2  = $rLL_new->[$Kpp]->[_TYPE_];
-            $previous_nonblank_token_2 = $rLL_new->[$Kpp]->[_TOKEN_];
-        }
+                # do not delete only nonblank token in a file
+                else {
+                    my $Kp = $self->K_previous_code( undef, $rLL_new );
+                    my $Kn = $self->K_next_nonblank($KK);
+                    $ok_to_delete = defined($Kn) || defined($Kp);
+                }
 
-        my $next_nonblank_token = EMPTY_STRING;
-        my $Kn                  = $KK + 1;
-        if ( $Kn <= $Kmax && $rLL->[$Kn]->[_TYPE_] eq 'b' ) { $Kn += 1 }
-        if ( $Kn <= $Kmax ) {
-            $next_nonblank_token = $rLL->[$Kn]->[_TOKEN_];
+                if ($ok_to_delete) {
+                    $self->note_deleted_semicolon($input_line_number);
+                    next;
+                }
+                else {
+                    write_logfile_entry("Extra ';'\n");
+                }
+            }
         }
 
-        my $token_0 = $rLL->[$Kfirst]->[_TOKEN_];
-        my $type_0  = $rLL->[$Kfirst]->[_TYPE_];
+        # Old patch to add space to something like "x10".
+        # Note: This is now done in the Tokenizer, but this code remains
+        # for reference.
+        elsif ( $type eq 'n' ) {
+            if ( substr( $token, 0, 1 ) eq 'x' && $token =~ /^x\d+/ ) {
+                $token =~ s/x/x /;
+                $rtoken_vars->[_TOKEN_] = $token;
+                if (DEVEL_MODE) {
+                    Fault(<<EOM);
+Near line $input_line_number, Unexpected need to split a token '$token' - this should now be done by the Tokenizer
+EOM
+                }
+            }
+        }
 
-        if (
-            ##$token =~ /^(s|tr|y|m|\/)/
-            ##&& $previous_nonblank_token =~ /^(=|==|!=)$/
-            1
+        # check for a qw quote
+        elsif ( $type eq 'q' ) {
 
-            # preceded by simple scalar
-            && $previous_nonblank_type_2 eq 'i'
-            && $previous_nonblank_token_2 =~ /^\$/
+            # trim blanks from right of qw quotes
+            # (To avoid trimming qw quotes use -ntqw; the tokenizer handles
+            # this)
+            $token =~ s/\s*$//;
+            $rtoken_vars->[_TOKEN_] = $token;
+            if ( $self->[_save_logfile_] && $token =~ /\t/ ) {
+                $self->note_embedded_tab($input_line_number);
+            }
+            if ( $rwhitespace_flags->[$KK] == WS_YES ) {
+                $self->store_space_and_token($rtoken_vars);
+            }
+            else {
+                $self->store_token($rtoken_vars);
+            }
+            next;
+        } ## end if ( $type eq 'q' )
 
-            # followed by some kind of termination
-            # (but give complaint if we can not see far enough ahead)
-            && $next_nonblank_token =~ /^[; \)\}]$/
+        # delete repeated commas if requested
+        elsif ( $type eq ',' ) {
+            if (   $last_nonblank_code_type eq ','
+                && $rOpts->{'delete-repeated-commas'} )
+            {
+                # Could note this deletion as a possible future update:
+                ## $self->note_deleted_comma($input_line_number);
+                next;
+            }
 
-            # scalar is not declared
-            ##                      =~ /^(my|our|local)$/
-            && !( $type_0 eq 'k' && $is_my_our_local{$token_0} )
-          )
-        {
-            my $lno   = 1 + $rLL_new->[$Kp]->[_LINE_INDEX_];
-            my $guess = substr( $previous_nonblank_token, 0, 1 ) . '~';
-            complain(
-"Line $lno: Note: be sure you want '$previous_nonblank_token' instead of '$guess' here\n"
-            );
+            # remember input line index of first comma if -wtc is used
+            if (%trailing_comma_rules) {
+                my $seqno = $seqno_stack{ $depth_next - 1 };
+                if ( defined($seqno)
+                    && !defined( $self->[_rfirst_comma_line_index_]->{$seqno} )
+                  )
+                {
+                    $self->[_rfirst_comma_line_index_]->{$seqno} =
+                      $rtoken_vars->[_LINE_INDEX_];
+                }
+            }
         }
-        return;
-    };
-
-    #-------------------------------------------
-    # Main loop to respace all lines of the file
-    #-------------------------------------------
-    my $last_K_out;
 
-    foreach my $line_of_tokens ( @{$rlines} ) {
-
-        my $input_line_number = $line_of_tokens->{_line_number};
-        my $last_line_type    = $line_type;
-        $line_type = $line_of_tokens->{_line_type};
-        next unless ( $line_type eq 'CODE' );
-        my $last_CODE_type = $CODE_type;
-        $CODE_type = $line_of_tokens->{_code_type};
-        my $rK_range = $line_of_tokens->{_rK_range};
-        my ( $Kfirst, $Klast ) = @{$rK_range};
-        next unless defined($Kfirst);
-        ( $Kfirst_old, $Klast_old ) = ( $Kfirst, $Klast );
-        $Klast_old_code = $Klast_old;
+        # change 'LABEL   :'   to 'LABEL:'
+        elsif ( $type eq 'J' ) {
+            $token =~ s/\s+//g;
+            $rtoken_vars->[_TOKEN_] = $token;
+        }
 
-        # Be sure an old K value is defined for sub $store_token
-        $Ktoken_vars = $Kfirst;
+        # check a quote for problems
+        elsif ( $type eq 'Q' ) {
+            $self->check_Q( $KK, $Kfirst, $input_line_number )
+              if ( $self->[_save_logfile_] );
+        }
 
-        # Check for correct sequence of token indexes...
-        # An error here means that sub write_line() did not correctly
-        # package the tokenized lines as it received them.  If we
-        # get a fault here it has not output a continuous sequence
-        # of K values.  Or a line of CODE may have been mis-marked as
-        # something else.  There is no good way to continue after such an
-        # error.
-        # FIXME: Calling Fault will produce zero output; it would be best to
-        # find a way to dump the input file.
-        if ( defined($last_K_out) ) {
-            if ( $Kfirst != $last_K_out + 1 ) {
-                Fault(
-                    "Program Bug: last K out was $last_K_out but Kfirst=$Kfirst"
-                );
-            }
+        # Store this token with possible previous blank
+        if ( $rwhitespace_flags->[$KK] == WS_YES ) {
+            $self->store_space_and_token($rtoken_vars);
         }
         else {
-
-            # The first token should always have been given index 0 by sub
-            # write_line()
-            if ( $Kfirst != 0 ) {
-                Fault("Program Bug: first K is $Kfirst but should be 0");
-            }
+            $self->store_token($rtoken_vars);
         }
-        $last_K_out = $Klast;
 
-        # Handle special lines of code
-        if ( $CODE_type && $CODE_type ne 'NIN' && $CODE_type ne 'VER' ) {
-
-            # CODE_types are as follows.
-            # 'BL' = Blank Line
-            # 'VB' = Verbatim - line goes out verbatim
-            # 'FS' = Format Skipping - line goes out verbatim, no blanks
-            # 'IO' = Indent Only - only indentation may be changed
-            # 'NIN' = No Internal Newlines - line does not get broken
-            # 'HSC'=Hanging Side Comment - fix this hanging side comment
-            # 'BC'=Block Comment - an ordinary full line comment
-            # 'SBC'=Static Block Comment - a block comment which does not get
-            #      indented
-            # 'SBCX'=Static Block Comment Without Leading Space
-            # 'VER'=VERSION statement
-            # '' or (undefined) - no restructions
+    }    # End token loop
+    return;
+} ## end sub respace_tokens_inner_loop
 
-            # For a hanging side comment we insert an empty quote before
-            # the comment so that it becomes a normal side comment and
-            # will be aligned by the vertical aligner
-            if ( $CODE_type eq 'HSC' ) {
+sub respace_post_loop_ops {
 
-                # Safety Check: This must be a line with one token (a comment)
-                my $rvars_Kfirst = $rLL->[$Kfirst];
-                if ( $Kfirst == $Klast && $rvars_Kfirst->[_TYPE_] eq '#' ) {
+    my ($self) = @_;
 
-                    # Note that even if the flag 'noadd-whitespace' is set, we
-                    # will make an exception here and allow a blank to be
-                    # inserted to push the comment to the right.  We can think
-                    # of this as an adjustment of indentation rather than
-                    # whitespace between tokens. This will also prevent the
-                    # hanging side comment from getting converted to a block
-                    # comment if whitespace gets deleted, as for example with
-                    # the -extrude and -mangle options.
-                    my $rcopy =
-                      copy_token_as_type( $rvars_Kfirst, 'q', EMPTY_STRING );
-                    $store_token->($rcopy);
-                    $rcopy = copy_token_as_type( $rvars_Kfirst, 'b', SPACE );
-                    $store_token->($rcopy);
-                    $store_token->($rvars_Kfirst);
-                    next;
-                }
-                else {
+    # Walk backwards through the tokens, making forward links to sequence items.
+    if ( @{$rLL_new} ) {
+        my $KNEXT;
+        foreach my $KK ( reverse( 0 .. @{$rLL_new} - 1 ) ) {
+            $rLL_new->[$KK]->[_KNEXT_SEQ_ITEM_] = $KNEXT;
+            if ( $rLL_new->[$KK]->[_TYPE_SEQUENCE_] ) { $KNEXT = $KK }
+        }
+        $self->[_K_first_seq_item_] = $KNEXT;
+    }
 
-                    # This line was mis-marked by sub scan_comment.  Catch in
-                    # DEVEL_MODE, otherwise try to repair and keep going.
-                    Fault(
-                        "Program bug. A hanging side comment has been mismarked"
-                    ) if (DEVEL_MODE);
+    # Find and remember lists by sequence number
+    my %is_C_style_for;
+    foreach my $seqno ( keys %{$K_opening_container} ) {
+        my $K_opening = $K_opening_container->{$seqno};
+        next unless defined($K_opening);
 
-                    $CODE_type = EMPTY_STRING;
-                    $line_of_tokens->{_code_type} = $CODE_type;
-                }
-            }
+        # code errors may leave undefined closing tokens
+        my $K_closing = $K_closing_container->{$seqno};
+        next unless defined($K_closing);
 
-            if ( $CODE_type eq 'BL' ) {
-                my $seqno = $seqno_stack{ $depth_next - 1 };
-                if (   defined($seqno)
-                    && !$ris_permanently_broken->{$seqno}
-                    && $rOpts_maximum_consecutive_blank_lines )
-                {
-                    $set_permanently_broken->($seqno);
-                }
-            }
+        my $lx_open   = $rLL_new->[$K_opening]->[_LINE_INDEX_];
+        my $lx_close  = $rLL_new->[$K_closing]->[_LINE_INDEX_];
+        my $line_diff = $lx_close - $lx_open;
+        $ris_broken_container->{$seqno} = $line_diff;
 
-            # Copy tokens unchanged
-            foreach my $KK ( $Kfirst .. $Klast ) {
-                $Ktoken_vars = $KK;
-                $store_token->( $rLL->[$KK] );
+        # See if this is a list
+        my $is_list;
+        my $rtype_count = $rtype_count_by_seqno->{$seqno};
+        if ($rtype_count) {
+            my $comma_count     = $rtype_count->{','};
+            my $fat_comma_count = $rtype_count->{'=>'};
+            my $semicolon_count = $rtype_count->{';'};
+            if ( $rtype_count->{'f'} ) {
+                $semicolon_count += $rtype_count->{'f'};
+                $is_C_style_for{$seqno} = 1;
             }
-            next;
-        }
 
-        # Handle normal line..
+            # We will define a list to be a container with one or more commas
+            # and no semicolons. Note that we have included the semicolons
+            # in a 'for' container in the semicolon count to keep c-style for
+            # statements from being formatted as lists.
+            if ( ( $comma_count || $fat_comma_count ) && !$semicolon_count ) {
+                $is_list = 1;
 
-        # Define index of last token before any side comment for comma counts
-        my $type_end = $rLL->[$Klast_old_code]->[_TYPE_];
-        if ( ( $type_end eq '#' || $type_end eq 'b' )
-            && $Klast_old_code > $Kfirst_old )
-        {
-            $Klast_old_code--;
-            if (   $rLL->[$Klast_old_code]->[_TYPE_] eq 'b'
-                && $Klast_old_code > $Kfirst_old )
-            {
-                $Klast_old_code--;
+                # We need to do one more check for a parenthesized list:
+                # At an opening paren following certain tokens, such as 'if',
+                # we do not want to format the contents as a list.
+                if ( $rLL_new->[$K_opening]->[_TOKEN_] eq '(' ) {
+                    my $Kp = $self->K_previous_code( $K_opening, $rLL_new );
+                    if ( defined($Kp) ) {
+                        my $type_p  = $rLL_new->[$Kp]->[_TYPE_];
+                        my $token_p = $rLL_new->[$Kp]->[_TOKEN_];
+                        $is_list =
+                          $type_p eq 'k'
+                          ? !$is_nonlist_keyword{$token_p}
+                          : !$is_nonlist_type{$type_p};
+                    }
+                }
             }
         }
 
-        # Insert any essential whitespace between lines
-        # if last line was normal CODE.
-        # Patch for rt #125012: use K_previous_code rather than '_nonblank'
-        # because comments may disappear.
-        if ( $last_line_type eq 'CODE' ) {
-            my $type_next  = $rLL->[$Kfirst]->[_TYPE_];
-            my $token_next = $rLL->[$Kfirst]->[_TOKEN_];
-            if (
-                is_essential_whitespace(
-                    $last_last_nonblank_code_token,
-                    $last_last_nonblank_code_type,
-                    $last_nonblank_code_token,
-                    $last_nonblank_code_type,
-                    $token_next,
-                    $type_next,
-                )
-              )
-            {
-
-                # Copy this first token as blank, but use previous line number
-                my $rcopy = copy_token_as_type( $rLL->[$Kfirst], 'b', SPACE );
-                $rcopy->[_LINE_INDEX_] =
-                  $rLL_new->[-1]->[_LINE_INDEX_];
+        # Look for a block brace marked as uncertain.  If the tokenizer thinks
+        # its guess is uncertain for the type of a brace following an unknown
+        # bareword then it adds a trailing space as a signal.  We can fix the
+        # type here now that we have had a better look at the contents of the
+        # container. This fixes case b1085. To find the corresponding code in
+        # Tokenizer.pm search for 'b1085' with an editor.
+        my $block_type = $rblock_type_of_seqno->{$seqno};
+        if ( $block_type && substr( $block_type, -1, 1 ) eq SPACE ) {
 
-                # The level and ci_level of newly created spaces should be the
-                # same as the previous token. Otherwise blinking states can
-                # be created if the -lp mode is used. See similar coding in
-                # sub 'store_token_and_space'.  Fixes cases b1109 b1110.
-                $rcopy->[_LEVEL_] =
-                  $rLL_new->[-1]->[_LEVEL_];
-                $rcopy->[_CI_LEVEL_] =
-                  $rLL_new->[-1]->[_CI_LEVEL_];
+            # Always remove the trailing space
+            $block_type =~ s/\s+$//;
 
-                $store_token->($rcopy);
+            # Try to filter out parenless sub calls
+            my $Knn1 = $self->K_next_nonblank( $K_opening, $rLL_new );
+            my $Knn2;
+            if ( defined($Knn1) ) {
+                $Knn2 = $self->K_next_nonblank( $Knn1, $rLL_new );
             }
-        }
-
-        #-------------------------------------------------------
-        # Loop to copy all tokens on this line, with any changes
-        #-------------------------------------------------------
-        my $type_sequence;
-        foreach my $KK ( $Kfirst .. $Klast ) {
-            $Ktoken_vars = $KK;
-            $rtoken_vars = $rLL->[$KK];
-            my $token              = $rtoken_vars->[_TOKEN_];
-            my $type               = $rtoken_vars->[_TYPE_];
-            my $last_type_sequence = $type_sequence;
-            $type_sequence = $rtoken_vars->[_TYPE_SEQUENCE_];
-
-            # Handle a blank space ...
-            if ( $type eq 'b' ) {
-
-                # Delete it if not wanted by whitespace rules
-                # or we are deleting all whitespace
-                # Note that whitespace flag is a flag indicating whether a
-                # white space BEFORE the token is needed
-                next if ( $KK >= $Klast );    # skip terminal blank
-                my $Knext = $KK + 1;
-
-                if ($rOpts_freeze_whitespace) {
-                    $store_token->($rtoken_vars);
-                    next;
-                }
-
-                my $ws = $rwhitespace_flags->[$Knext];
-                if (   $ws == -1
-                    || $rOpts_delete_old_whitespace )
-                {
-
-                    my $token_next = $rLL->[$Knext]->[_TOKEN_];
-                    my $type_next  = $rLL->[$Knext]->[_TYPE_];
-
-                    my $do_not_delete = is_essential_whitespace(
-                        $last_last_nonblank_code_token,
-                        $last_last_nonblank_code_type,
-                        $last_nonblank_code_token,
-                        $last_nonblank_code_type,
-                        $token_next,
-                        $type_next,
-                    );
-
-                    # Note that repeated blanks will get filtered out here
-                    next unless ($do_not_delete);
-                }
+            my $type_nn1 = defined($Knn1) ? $rLL_new->[$Knn1]->[_TYPE_] : 'b';
+            my $type_nn2 = defined($Knn2) ? $rLL_new->[$Knn2]->[_TYPE_] : 'b';
 
-                # make it just one character
-                $rtoken_vars->[_TOKEN_] = SPACE;
-                $store_token->($rtoken_vars);
-                next;
+            #   if ( $type_nn1 =~ /^[wU]$/ && $type_nn2 =~ /^[wiqQGCZ]$/ ) {
+            if ( $wU{$type_nn1} && $wiq{$type_nn2} ) {
+                $is_list = 0;
             }
 
-            # Handle a nonblank token...
+            # Convert to a hash brace if it looks like it holds a list
+            if ($is_list) {
 
-            if ($type_sequence) {
+                $block_type = EMPTY_STRING;
 
-                # Insert a tentative missing semicolon if the next token is
-                # a closing block brace
-                if (
-                       $type eq '}'
-                    && $token eq '}'
+                $rLL_new->[$K_opening]->[_CI_LEVEL_] = 1;
+                $rLL_new->[$K_closing]->[_CI_LEVEL_] = 1;
+            }
 
-                    # not preceded by a ';'
-                    && $last_nonblank_code_type ne ';'
+            $rblock_type_of_seqno->{$seqno} = $block_type;
+        }
 
-                    # and this is not a VERSION stmt (is all one line, we
-                    # are not inserting semicolons on one-line blocks)
-                    && $CODE_type ne 'VER'
+        # Handle a list container
+        if ( $is_list && !$block_type ) {
+            $ris_list_by_seqno->{$seqno} = $seqno;
+            my $seqno_parent = $rparent_of_seqno->{$seqno};
+            my $depth        = 0;
+            while ( defined($seqno_parent) && $seqno_parent ne SEQ_ROOT ) {
+                $depth++;
 
-                    # and we are allowed to add semicolons
-                    && $rOpts->{'add-semicolons'}
-                  )
+                # for $rhas_list we need to save the minimum depth
+                if (  !$rhas_list->{$seqno_parent}
+                    || $rhas_list->{$seqno_parent} > $depth )
                 {
-                    $add_phantom_semicolon->($KK);
+                    $rhas_list->{$seqno_parent} = $depth;
                 }
-            }
-
-            # Modify certain tokens here for whitespace
-            # The following is not yet done, but could be:
-            #   sub (x x x)
-            #     ( $type =~ /^[wit]$/ )
-            elsif ( $is_wit{$type} ) {
 
-                # change '$  var'  to '$var' etc
-                # change '@    '   to '@'
-                # Examples: <<snippets/space1.in>>
-                my $ord = ord( substr( $token, 1, 1 ) );
-                if (
-
-                    # quick test for possible blank at second char
-                    $ord > 0 && ( $ord < ORD_PRINTABLE_MIN
-                        || $ord > ORD_PRINTABLE_MAX )
-                  )
-                {
-                    my ( $sigil, $word ) = split /\s+/, $token, 2;
-
-                    # $sigil =~ /^[\$\&\%\*\@]$/ )
-                    if ( $is_sigil{$sigil} ) {
-                        $token = $sigil;
-                        $token .= $word if ( defined($word) );    # fix c104
-                        $rtoken_vars->[_TOKEN_] = $token;
-                    }
-                }
-
-                # Split identifiers with leading arrows, inserting blanks
-                # if necessary.  It is easier and safer here than in the
-                # tokenizer.  For example '->new' becomes two tokens, '->'
-                # and 'new' with a possible blank between.
-                #
-                # Note: there is a related patch in sub set_whitespace_flags
-                elsif (length($token) > 2
-                    && substr( $token, 0, 2 ) eq '->'
-                    && $token =~ /^\-\>(.*)$/
-                    && $1 )
-                {
-
-                    my $token_save = $1;
-                    my $type_save  = $type;
-
-                    # Change '-> new'  to '->new'
-                    $token_save =~ s/^\s+//g;
-
-                    # store a blank to left of arrow if necessary
-                    my $Kprev = $self->K_previous_nonblank($KK);
-                    if (   defined($Kprev)
-                        && $rLL->[$Kprev]->[_TYPE_] ne 'b'
-                        && $rOpts_add_whitespace
-                        && $want_left_space{'->'} == WS_YES )
-                    {
-                        my $rcopy =
-                          copy_token_as_type( $rtoken_vars, 'b', SPACE );
-                        $store_token->($rcopy);
-                    }
-
-                    # then store the arrow
-                    my $rcopy = copy_token_as_type( $rtoken_vars, '->', '->' );
-                    $store_token->($rcopy);
-
-                    # store a blank after the arrow if requested
-                    # added for issue git #33
-                    if ( $want_right_space{'->'} == WS_YES ) {
-                        my $rcopy_b =
-                          copy_token_as_type( $rtoken_vars, 'b', SPACE );
-                        $store_token->($rcopy_b);
-                    }
-
-                    # then reset the current token to be the remainder,
-                    # and reset the whitespace flag according to the arrow
-                    $token = $rtoken_vars->[_TOKEN_] = $token_save;
-                    $type  = $rtoken_vars->[_TYPE_]  = $type_save;
-                    $store_token->($rtoken_vars);
-                    next;
-                }
-
-                # Trim certain spaces in identifiers
-                if ( $type eq 'i' ) {
-
-                    if (
-                        (
-                            substr( $token, 0, 3 ) eq 'sub'
-                            || $rOpts_sub_alias_list
-                        )
-                        && $token =~ /$SUB_PATTERN/
-                      )
-                    {
-
-                        # -spp = 0 : no space before opening prototype paren
-                        # -spp = 1 : stable (follow input spacing)
-                        # -spp = 2 : always space before opening prototype paren
-                        my $spp = $rOpts->{'space-prototype-paren'};
-                        if ( defined($spp) ) {
-                            if    ( $spp == 0 ) { $token =~ s/\s+\(/\(/; }
-                            elsif ( $spp == 2 ) { $token =~ s/\(/ (/; }
-                        }
-
-                        # one space max, and no tabs
-                        $token =~ s/\s+/ /g;
-                        $rtoken_vars->[_TOKEN_] = $token;
-                    }
-
-                    # clean up spaces in package identifiers, like
-                    #   "package        Bob::Dog;"
-                    elsif ( substr( $token, 0, 7 ) eq 'package'
-                        && $token =~ /^package\s/ )
-                    {
-                        $token =~ s/\s+/ /g;
-                        $rtoken_vars->[_TOKEN_] = $token;
-                    }
-
-                    # trim identifiers of trailing blanks which can occur
-                    # under some unusual circumstances, such as if the
-                    # identifier 'witch' has trailing blanks on input here:
-                    #
-                    # sub
-                    # witch
-                    # ()   # prototype may be on new line ...
-                    # ...
-                    my $ord_ch = ord( substr( $token, -1, 1 ) );
-                    if (
-
-                        # quick check for possible ending space
-                        $ord_ch > 0 && ( $ord_ch < ORD_PRINTABLE_MIN
-                            || $ord_ch > ORD_PRINTABLE_MAX )
-                      )
-                    {
-                        $token =~ s/\s+$//g;
-                        $rtoken_vars->[_TOKEN_] = $token;
-                    }
-                }
-            }
-
-            # handle semicolons
-            elsif ( $type eq ';' ) {
-
-                # Remove unnecessary semicolons, but not after bare
-                # blocks, where it could be unsafe if the brace is
-                # mis-tokenized.
-                if (
-                    $rOpts->{'delete-semicolons'}
-                    && (
-                        (
-                               $last_nonblank_block_type
-                            && $last_nonblank_code_type eq '}'
-                            && (
-                                $is_block_without_semicolon{
-                                    $last_nonblank_block_type}
-                                || $last_nonblank_block_type =~ /$SUB_PATTERN/
-                                || $last_nonblank_block_type =~ /^\w+:$/
-                            )
-                        )
-                        || $last_nonblank_code_type eq ';'
-                    )
-                  )
-                {
-
-                    # This looks like a deletable semicolon, but even if a
-                    # semicolon can be deleted it is not necessarily best to do
-                    # so.  We apply these additional rules for deletion:
-                    # - Always ok to delete a ';' at the end of a line
-                    # - Never delete a ';' before a '#' because it would
-                    #   promote it to a block comment.
-                    # - If a semicolon is not at the end of line, then only
-                    #   delete if it is followed by another semicolon or closing
-                    #   token.  This includes the comment rule.  It may take
-                    #   two passes to get to a final state, but it is a little
-                    #   safer.  For example, keep the first semicolon here:
-                    #      eval { sub bubba { ok(0) }; ok(0) } || ok(1);
-                    #   It is not required but adds some clarity.
-                    my $ok_to_delete = 1;
-                    if ( $KK < $Klast ) {
-                        my $Kn = $self->K_next_nonblank($KK);
-                        if ( defined($Kn) && $Kn <= $Klast ) {
-                            my $next_nonblank_token_type =
-                              $rLL->[$Kn]->[_TYPE_];
-                            $ok_to_delete = $next_nonblank_token_type eq ';'
-                              || $next_nonblank_token_type eq '}';
-                        }
-                    }
-
-                    # do not delete only nonblank token in a file
-                    else {
-                        my $Kp = $self->K_previous_code( undef, $rLL_new );
-                        my $Kn = $self->K_next_nonblank($KK);
-                        $ok_to_delete = defined($Kn) || defined($Kp);
-                    }
-
-                    if ($ok_to_delete) {
-                        $self->note_deleted_semicolon($input_line_number);
-                        next;
-                    }
-                    else {
-                        write_logfile_entry("Extra ';'\n");
-                    }
-                }
-            }
-
-            # Old patch to add space to something like "x10".
-            # Note: This is now done in the Tokenizer, but this code remains
-            # for reference.
-            elsif ( $type eq 'n' ) {
-                if ( substr( $token, 0, 1 ) eq 'x' && $token =~ /^x\d+/ ) {
-                    $token =~ s/x/x /;
-                    $rtoken_vars->[_TOKEN_] = $token;
-                    if (DEVEL_MODE) {
-                        Fault(<<EOM);
-Near line $input_line_number, Unexpected need to split a token '$token' - this should now be done by the Tokenizer
-EOM
-                    }
-                }
-            }
-
-            # check for a qw quote
-            elsif ( $type eq 'q' ) {
-
-                # trim blanks from right of qw quotes
-                # (To avoid trimming qw quotes use -ntqw; the tokenizer handles
-                # this)
-                $token =~ s/\s*$//;
-                $rtoken_vars->[_TOKEN_] = $token;
-                $self->note_embedded_tab($input_line_number)
-                  if ( $token =~ "\t" );
-                $store_token_and_space->(
-                    $rtoken_vars, $rwhitespace_flags->[$KK] == WS_YES
-                );
-                next;
-            } ## end if ( $type eq 'q' )
-
-            # change 'LABEL   :'   to 'LABEL:'
-            elsif ( $type eq 'J' ) {
-                $token =~ s/\s+//g;
-                $rtoken_vars->[_TOKEN_] = $token;
-            }
-
-            # check a quote for problems
-            elsif ( $type eq 'Q' ) {
-                $check_Q->( $KK, $Kfirst, $input_line_number );
-            }
-
-            # Store this token with possible previous blank
-            if ( $rwhitespace_flags->[$KK] == WS_YES ) {
-                $store_token_and_space->( $rtoken_vars, 1 );
-            }
-            else {
-                $store_token->($rtoken_vars);
-            }
-
-        }    # End token loop
-    }    # End line loop
-
-    # Walk backwards through the tokens, making forward links to sequence items.
-    if ( @{$rLL_new} ) {
-        my $KNEXT;
-        foreach my $KK ( reverse( 0 .. @{$rLL_new} - 1 ) ) {
-            $rLL_new->[$KK]->[_KNEXT_SEQ_ITEM_] = $KNEXT;
-            if ( $rLL_new->[$KK]->[_TYPE_SEQUENCE_] ) { $KNEXT = $KK }
-        }
-        $self->[_K_first_seq_item_] = $KNEXT;
-    }
-
-    # Find and remember lists by sequence number
-    foreach my $seqno ( keys %{$K_opening_container} ) {
-        my $K_opening = $K_opening_container->{$seqno};
-        next unless defined($K_opening);
-
-        # code errors may leave undefined closing tokens
-        my $K_closing = $K_closing_container->{$seqno};
-        next unless defined($K_closing);
-
-        my $lx_open   = $rLL_new->[$K_opening]->[_LINE_INDEX_];
-        my $lx_close  = $rLL_new->[$K_closing]->[_LINE_INDEX_];
-        my $line_diff = $lx_close - $lx_open;
-        $ris_broken_container->{$seqno} = $line_diff;
-
-        # See if this is a list
-        my $is_list;
-        my $rtype_count = $rtype_count_by_seqno->{$seqno};
-        if ($rtype_count) {
-            my $comma_count     = $rtype_count->{','};
-            my $fat_comma_count = $rtype_count->{'=>'};
-            my $semicolon_count = $rtype_count->{';'} || $rtype_count->{'f'};
-
-            # We will define a list to be a container with one or more commas
-            # and no semicolons. Note that we have included the semicolons
-            # in a 'for' container in the semicolon count to keep c-style for
-            # statements from being formatted as lists.
-            if ( ( $comma_count || $fat_comma_count ) && !$semicolon_count ) {
-                $is_list = 1;
-
-                # We need to do one more check for a parenthesized list:
-                # At an opening paren following certain tokens, such as 'if',
-                # we do not want to format the contents as a list.
-                if ( $rLL_new->[$K_opening]->[_TOKEN_] eq '(' ) {
-                    my $Kp = $self->K_previous_code( $K_opening, $rLL_new );
-                    if ( defined($Kp) ) {
-                        my $type_p = $rLL_new->[$Kp]->[_TYPE_];
-                        if ( $type_p eq 'k' ) {
-                            my $token_p = $rLL_new->[$Kp]->[_TOKEN_];
-                            $is_list = 0 if ( $is_nonlist_keyword{$token_p} );
-                        }
-                        else {
-                            $is_list = 0 if ( $is_nonlist_type{$type_p} );
-                        }
-                    }
-                }
-            }
-        }
-
-        # Look for a block brace marked as uncertain.  If the tokenizer thinks
-        # its guess is uncertain for the type of a brace following an unknown
-        # bareword then it adds a trailing space as a signal.  We can fix the
-        # type here now that we have had a better look at the contents of the
-        # container. This fixes case b1085. To find the corresponding code in
-        # Tokenizer.pm search for 'b1085' with an editor.
-        my $block_type = $rblock_type_of_seqno->{$seqno};
-        if ( $block_type && substr( $block_type, -1, 1 ) eq SPACE ) {
-
-            # Always remove the trailing space
-            $block_type =~ s/\s+$//;
-
-            # Try to filter out parenless sub calls
-            my $Knn1 = $self->K_next_nonblank( $K_opening, $rLL_new );
-            my $Knn2;
-            if ( defined($Knn1) ) {
-                $Knn2 = $self->K_next_nonblank( $Knn1, $rLL_new );
-            }
-            my $type_nn1 = defined($Knn1) ? $rLL_new->[$Knn1]->[_TYPE_] : 'b';
-            my $type_nn2 = defined($Knn2) ? $rLL_new->[$Knn2]->[_TYPE_] : 'b';
-
-            #   if ( $type_nn1 =~ /^[wU]$/ && $type_nn2 =~ /^[wiqQGCZ]$/ ) {
-            if ( $wU{$type_nn1} && $wiq{$type_nn2} ) {
-                $is_list = 0;
-            }
-
-            # Convert to a hash brace if it looks like it holds a list
-            if ($is_list) {
-
-                $block_type = EMPTY_STRING;
-
-                $rLL_new->[$K_opening]->[_CI_LEVEL_] = 1;
-                $rLL_new->[$K_closing]->[_CI_LEVEL_] = 1;
-            }
-
-            $rblock_type_of_seqno->{$seqno} = $block_type;
-        }
-
-        # Handle a list container
-        if ( $is_list && !$block_type ) {
-            $ris_list_by_seqno->{$seqno} = $seqno;
-            my $seqno_parent = $rparent_of_seqno->{$seqno};
-            my $depth        = 0;
-            while ( defined($seqno_parent) && $seqno_parent ne SEQ_ROOT ) {
-                $depth++;
-
-                # for $rhas_list we need to save the minimum depth
-                if (  !$rhas_list->{$seqno_parent}
-                    || $rhas_list->{$seqno_parent} > $depth )
-                {
-                    $rhas_list->{$seqno_parent} = $depth;
-                }
-
-                if ($line_diff) {
-                    $rhas_broken_list->{$seqno_parent} = 1;
+                if ($line_diff) {
+                    $rhas_broken_list->{$seqno_parent} = 1;
 
                     # Patch1: We need to mark broken lists with non-terminal
                     # line-ending commas for the -bbx=2 parameter. This insures
         }
     }
 
-    # Reset memory to be the new array
-    $self->[_rLL_] = $rLL_new;
-    my $Klimit;
-    if ( @{$rLL_new} ) { $Klimit = @{$rLL_new} - 1 }
-    $self->[_Klimit_] = $Klimit;
-
-    # During development, verify that the new array still looks okay.
-    DEVEL_MODE && $self->check_token_array();
+    # Add -ci to C-style for loops (issue c154)
+    # This is much easier to do here than in the tokenizer.
+    foreach my $seqno ( keys %is_C_style_for ) {
+        my $K_opening = $K_opening_container->{$seqno};
+        my $K_closing = $K_closing_container->{$seqno};
+        my $type_last = 'f';
+        for my $KK ( $K_opening + 1 .. $K_closing - 1 ) {
+            $rLL_new->[$KK]->[_CI_LEVEL_] = $type_last eq 'f' ? 0 : 1;
+            my $type = $rLL_new->[$KK]->[_TYPE_];
+            if ( $type ne 'b' && $type ne '#' ) { $type_last = $type }
+        }
+    }
 
-    # reset the token limits of each line
-    $self->resync_lines_and_tokens();
+    return;
+} ## end sub respace_post_loop_ops
 
+sub set_permanently_broken {
+    my ( $self, $seqno ) = @_;
+    while ( defined($seqno) ) {
+        $ris_permanently_broken->{$seqno} = 1;
+        $seqno = $rparent_of_seqno->{$seqno};
+    }
     return;
-} ## end sub respace_tokens
+} ## end sub set_permanently_broken
 
-sub copy_token_as_type {
+sub store_token {
 
-    # This provides a quick way to create a new token by
-    # slightly modifying an existing token.
-    my ( $rold_token, $type, $token ) = @_;
-    if ( $type eq 'b' ) {
-        $token = SPACE unless defined($token);
-    }
-    elsif ( $type eq 'q' ) {
-        $token = EMPTY_STRING unless defined($token);
-    }
-    elsif ( $type eq '->' ) {
-        $token = '->' unless defined($token);
-    }
-    elsif ( $type eq ';' ) {
-        $token = ';' unless defined($token);
-    }
-    else {
+    my ( $self, $item ) = @_;
 
-        # Unexpected type ... this sub will work as long as both $token and
-        # $type are defined, but we should catch any unexpected types during
-        # development.
-        if (DEVEL_MODE) {
-            Fault(<<EOM);
-sub 'copy_token_as_type' received token type '$type' but expects just one of: 'b' 'q' '->' or ';'
-EOM
-        }
-        else {
-            # shouldn't happen
-        }
-    }
+    #------------------------------------------
+    # Store one token during respace operations
+    #------------------------------------------
 
-    my @rnew_token = @{$rold_token};
-    $rnew_token[_TYPE_]          = $type;
-    $rnew_token[_TOKEN_]         = $token;
-    $rnew_token[_TYPE_SEQUENCE_] = EMPTY_STRING;
-    return \@rnew_token;
-} ## end sub copy_token_as_type
+    # Input parameter:
+    #  $item = ref to a token
 
-sub Debug_dump_tokens {
+    # NOTE: this sub is called once per token so coding efficiency is critical.
 
-    # a debug routine, not normally used
-    my ( $self, $msg ) = @_;
-    my $rLL   = $self->[_rLL_];
-    my $nvars = @{$rLL};
-    print STDERR "$msg\n";
-    print STDERR "ntokens=$nvars\n";
-    print STDERR "K\t_TOKEN_\t_TYPE_\n";
-    my $K = 0;
+    # The next multiple assignment statements are significantly faster than
+    # doing them one-by-one.
+    my (
 
-    foreach my $item ( @{$rLL} ) {
-        print STDERR "$K\t$item->[_TOKEN_]\t$item->[_TYPE_]\n";
-        $K++;
-    }
-    return;
-} ## end sub Debug_dump_tokens
+        $type,
+        $token,
+        $type_sequence,
 
-sub K_next_code {
-    my ( $self, $KK, $rLL ) = @_;
+      ) = @{$item}[
 
-    # return the index K of the next nonblank, non-comment token
-    return unless ( defined($KK) && $KK >= 0 );
+      _TYPE_,
+      _TOKEN_,
+      _TYPE_SEQUENCE_,
 
-    # use the standard array unless given otherwise
-    $rLL = $self->[_rLL_] unless ( defined($rLL) );
-    my $Num  = @{$rLL};
-    my $Knnb = $KK + 1;
-    while ( $Knnb < $Num ) {
-        if ( !defined( $rLL->[$Knnb] ) ) {
+      ];
 
-            # We seem to have encountered a gap in our array.
-            # This shouldn't happen because sub write_line() pushed
-            # items into the $rLL array.
-            Fault("Undefined entry for k=$Knnb") if (DEVEL_MODE);
+    # Set the token length.  Later it may be adjusted again if phantom or
+    # ignoring side comment lengths.
+    my $token_length =
+      $is_encoded_data ? $length_function->($token) : length($token);
+
+    # handle blanks
+    if ( $type eq 'b' ) {
+
+        # Do not output consecutive blanks. This situation should have been
+        # prevented earlier, but it is worth checking because later routines
+        # make this assumption.
+        if ( @{$rLL_new} && $rLL_new->[-1]->[_TYPE_] eq 'b' ) {
             return;
         }
-        if (   $rLL->[$Knnb]->[_TYPE_] ne 'b'
-            && $rLL->[$Knnb]->[_TYPE_] ne '#' )
-        {
-            return $Knnb;
-        }
-        $Knnb++;
     }
-    return;
-} ## end sub K_next_code
-
-sub K_next_nonblank {
-    my ( $self, $KK, $rLL ) = @_;
-
-    # return the index K of the next nonblank token, or
-    # return undef if none
-    return unless ( defined($KK) && $KK >= 0 );
 
-    # The third arg allows this routine to be used on any array.  This is
-    # useful in sub respace_tokens when we are copying tokens from an old $rLL
-    # to a new $rLL array.  But usually the third arg will not be given and we
-    # will just use the $rLL array in $self.
-    $rLL = $self->[_rLL_] unless ( defined($rLL) );
-    my $Num  = @{$rLL};
-    my $Knnb = $KK + 1;
-    return unless ( $Knnb < $Num );
-    return $Knnb if ( $rLL->[$Knnb]->[_TYPE_] ne 'b' );
-    return unless ( ++$Knnb < $Num );
-    return $Knnb if ( $rLL->[$Knnb]->[_TYPE_] ne 'b' );
+    # handle comments
+    elsif ( $type eq '#' ) {
 
-    # Backup loop. Very unlikely to get here; it means we have neighboring
-    # blanks in the token stream.
-    $Knnb++;
-    while ( $Knnb < $Num ) {
+        # trim comments if necessary
+        my $ord = ord( substr( $token, -1, 1 ) );
+        if (
+            $ord > 0
+            && (   $ord < ORD_PRINTABLE_MIN
+                || $ord > ORD_PRINTABLE_MAX )
+            && $token =~ s/\s+$//
+          )
+        {
+            $token_length = $length_function->($token);
+            $item->[_TOKEN_] = $token;
+        }
 
-        # Safety check, this fault shouldn't happen:  The $rLL array is the
-        # main array of tokens, so all entries should be used.  It is
-        # initialized in sub write_line, and then re-initialized by sub
-        # $store_token() within sub respace_tokens.  Tokens are pushed on
-        # so there shouldn't be any gaps.
-        if ( !defined( $rLL->[$Knnb] ) ) {
-            Fault("Undefined entry for k=$Knnb") if (DEVEL_MODE);
-            return;
+        # Mark length of side comments as just 1 if sc lengths are ignored
+        if ( $rOpts_ignore_side_comment_lengths
+            && ( !$CODE_type || $CODE_type eq 'HSC' ) )
+        {
+            $token_length = 1;
+        }
+        my $seqno = $seqno_stack{ $depth_next - 1 };
+        if ( defined($seqno) ) {
+            $self->[_rblank_and_comment_count_]->{$seqno} += 1
+              if ( $CODE_type eq 'BC' );
+            $self->set_permanently_broken($seqno)
+              if !$ris_permanently_broken->{$seqno};
         }
-        if ( $rLL->[$Knnb]->[_TYPE_] ne 'b' ) { return $Knnb }
-        $Knnb++;
     }
-    return;
-} ## end sub K_next_nonblank
 
-sub K_previous_code {
+    # handle non-blanks and non-comments
+    else {
 
-    # return the index K of the previous nonblank, non-comment token
-    # Call with $KK=undef to start search at the top of the array
-    my ( $self, $KK, $rLL ) = @_;
+        my $block_type;
 
-    # use the standard array unless given otherwise
-    $rLL = $self->[_rLL_] unless ( defined($rLL) );
-    my $Num = @{$rLL};
-    if    ( !defined($KK) ) { $KK = $Num }
-    elsif ( $KK > $Num ) {
+        # check for a sequenced item (i.e., container or ?/:)
+        if ($type_sequence) {
 
-        # This fault can be caused by a programming error in which a bad $KK is
-        # given.  The caller should make the first call with KK_new=undef to
-        # avoid this error.
-        Fault(
-"Program Bug: K_previous_nonblank_new called with K=$KK which exceeds $Num"
-        ) if (DEVEL_MODE);
-        return;
-    }
-    my $Kpnb = $KK - 1;
-    while ( $Kpnb >= 0 ) {
-        if (   $rLL->[$Kpnb]->[_TYPE_] ne 'b'
-            && $rLL->[$Kpnb]->[_TYPE_] ne '#' )
-        {
-            return $Kpnb;
-        }
-        $Kpnb--;
-    }
-    return;
-} ## end sub K_previous_code
+            # This will be the index of this item in the new array
+            my $KK_new = @{$rLL_new};
 
-sub K_previous_nonblank {
+            if ( $is_opening_token{$token} ) {
 
-    # return index of previous nonblank token before item K;
-    # Call with $KK=undef to start search at the top of the array
-    my ( $self, $KK, $rLL ) = @_;
+                $K_opening_container->{$type_sequence} = $KK_new;
+                $block_type = $rblock_type_of_seqno->{$type_sequence};
 
-    # use the standard array unless given otherwise
-    $rLL = $self->[_rLL_] unless ( defined($rLL) );
-    my $Num = @{$rLL};
-    if    ( !defined($KK) ) { $KK = $Num }
-    elsif ( $KK > $Num ) {
+                # Fix for case b1100: Count a line ending in ', [' as having
+                # a line-ending comma.  Otherwise, these commas can be hidden
+                # with something like --opening-square-bracket-right
+                if (   $last_nonblank_code_type eq ','
+                    && $Ktoken_vars == $Klast_old_code
+                    && $Ktoken_vars > $Kfirst_old )
+                {
+                    $rlec_count_by_seqno->{$type_sequence}++;
+                }
 
-        # This fault can be caused by a programming error in which a bad $KK is
-        # given.  The caller should make the first call with KK_new=undef to
-        # avoid this error.
-        Fault(
-"Program Bug: K_previous_nonblank_new called with K=$KK which exceeds $Num"
-        ) if (DEVEL_MODE);
-        return;
-    }
-    my $Kpnb = $KK - 1;
-    return unless ( $Kpnb >= 0 );
-    return $Kpnb if ( $rLL->[$Kpnb]->[_TYPE_] ne 'b' );
-    return unless ( --$Kpnb >= 0 );
-    return $Kpnb if ( $rLL->[$Kpnb]->[_TYPE_] ne 'b' );
+                if (   $last_nonblank_code_type eq '='
+                    || $last_nonblank_code_type eq '=>' )
+                {
+                    $ris_assigned_structure->{$type_sequence} =
+                      $last_nonblank_code_type;
+                }
 
-    # Backup loop. We should not get here unless some routine
-    # slipped repeated blanks into the token stream.
-    return unless ( --$Kpnb >= 0 );
-    while ( $Kpnb >= 0 ) {
-        if ( $rLL->[$Kpnb]->[_TYPE_] ne 'b' ) { return $Kpnb }
-        $Kpnb--;
-    }
-    return;
-} ## end sub K_previous_nonblank
+                my $seqno_parent = $seqno_stack{ $depth_next - 1 };
+                $seqno_parent = SEQ_ROOT unless defined($seqno_parent);
+                push @{ $rchildren_of_seqno->{$seqno_parent} }, $type_sequence;
+                $rparent_of_seqno->{$type_sequence}     = $seqno_parent;
+                $seqno_stack{$depth_next}               = $type_sequence;
+                $K_old_opening_by_seqno{$type_sequence} = $Ktoken_vars;
+                $depth_next++;
 
-sub parent_seqno_by_K {
+                if ( $depth_next > $depth_next_max ) {
+                    $depth_next_max = $depth_next;
+                }
+            }
+            elsif ( $is_closing_token{$token} ) {
 
-    # Return the sequence number of the parent container of token K, if any.
+                $K_closing_container->{$type_sequence} = $KK_new;
+                $block_type = $rblock_type_of_seqno->{$type_sequence};
 
-    my ( $self, $KK ) = @_;
-    my $rLL = $self->[_rLL_];
+                # Do not include terminal commas in counts
+                if (   $last_nonblank_code_type eq ','
+                    || $last_nonblank_code_type eq '=>' )
+                {
+                    $rtype_count_by_seqno->{$type_sequence}
+                      ->{$last_nonblank_code_type}--;
 
-    # The task is to jump forward to the next container token
-    # and use the sequence number of either it or its parent.
+                    if (   $Ktoken_vars == $Kfirst_old
+                        && $last_nonblank_code_type eq ','
+                        && $rlec_count_by_seqno->{$type_sequence} )
+                    {
+                        $rlec_count_by_seqno->{$type_sequence}--;
+                    }
+                }
 
-    # For example, consider the following with seqno=5 of the '[' and ']'
-    # being called with index K of the first token of each line:
+                # Update the stack...
+                $depth_next--;
+            }
+            else {
 
-    #                                              # result
-    #    push @tests,                              # -
-    #      [                                       # -
-    #        sub { 99 },   'do {&{%s} for 1,2}',   # 5
-    #        '(&{})(&{})', undef,                  # 5
-    #        [ 2, 2, 0 ],  0                       # 5
-    #      ];                                      # -
+                # For ternary, note parent but do not include as child
+                my $seqno_parent = $seqno_stack{ $depth_next - 1 };
+                $seqno_parent = SEQ_ROOT unless defined($seqno_parent);
+                $rparent_of_seqno->{$type_sequence} = $seqno_parent;
 
-    # NOTE: The ending parent will be SEQ_ROOT for a balanced file.  For
-    # unbalanced files, last sequence number will either be undefined or it may
-    # be at a deeper level.  In either case we will just return SEQ_ROOT to
-    # have a defined value and allow formatting to proceed.
-    my $parent_seqno  = SEQ_ROOT;
-    my $type_sequence = $rLL->[$KK]->[_TYPE_SEQUENCE_];
-    if ($type_sequence) {
-        $parent_seqno = $self->[_rparent_of_seqno_]->{$type_sequence};
-    }
-    else {
-        my $Kt = $rLL->[$KK]->[_KNEXT_SEQ_ITEM_];
-        if ( defined($Kt) ) {
-            $type_sequence = $rLL->[$Kt]->[_TYPE_SEQUENCE_];
-            my $type = $rLL->[$Kt]->[_TYPE_];
+                # These are not yet used but could be useful
+                if ( $token eq '?' ) {
+                    $K_opening_ternary->{$type_sequence} = $KK_new;
+                }
+                elsif ( $token eq ':' ) {
+                    $K_closing_ternary->{$type_sequence} = $KK_new;
+                }
+                else {
 
-            # if next container token is closing, it is the parent seqno
-            if ( $is_closing_type{$type} ) {
-                $parent_seqno = $type_sequence;
+                    # We really shouldn't arrive here, just being cautious:
+                    # The only sequenced types output by the tokenizer are the
+                    # opening & closing containers and the ternary types. Each
+                    # of those was checked above. So we would only get here
+                    # if the tokenizer has been changed to mark some other
+                    # tokens with sequence numbers.
+                    if (DEVEL_MODE) {
+                        Fault(
+"Unexpected token type with sequence number: type='$type', seqno='$type_sequence'"
+                        );
+                    }
+                }
             }
+        }
 
-            # otherwise we want its parent container
-            else {
-                $parent_seqno = $self->[_rparent_of_seqno_]->{$type_sequence};
+        # Remember the most recent two non-blank, non-comment tokens.
+        # NOTE: the phantom semicolon code may change the output stack
+        # without updating these values.  Phantom semicolons are considered
+        # the same as blanks for now, but future needs might change that.
+        # See the related note in sub 'add_phantom_semicolon'.
+        $last_last_nonblank_code_type  = $last_nonblank_code_type;
+        $last_last_nonblank_code_token = $last_nonblank_code_token;
+
+        $last_nonblank_code_type  = $type;
+        $last_nonblank_code_token = $token;
+        $last_nonblank_block_type = $block_type;
+
+        # count selected types
+        if ( $is_counted_type{$type} ) {
+            my $seqno = $seqno_stack{ $depth_next - 1 };
+            if ( defined($seqno) ) {
+                $rtype_count_by_seqno->{$seqno}->{$type}++;
+
+                # Count line-ending commas for -bbx
+                if ( $type eq ',' && $Ktoken_vars == $Klast_old_code ) {
+                    $rlec_count_by_seqno->{$seqno}++;
+                }
+
+                # Remember index of first here doc target
+                if ( $type eq 'h' && !$K_first_here_doc_by_seqno{$seqno} ) {
+                    my $KK_new = @{$rLL_new};
+                    $K_first_here_doc_by_seqno{$seqno} = $KK_new;
+                }
             }
         }
     }
-    $parent_seqno = SEQ_ROOT unless ( defined($parent_seqno) );
-    return $parent_seqno;
-} ## end sub parent_seqno_by_K
 
-sub is_in_block_by_i {
-    my ( $self, $i ) = @_;
+    # cumulative length is the length sum including this token
+    $cumulative_length += $token_length;
 
-    # returns true if
-    #     token at i is contained in a BLOCK
-    #     or is at root level
-    #     or there is some kind of error (i.e. unbalanced file)
-    # returns false otherwise
-    return 1 if ( $i < 0 );    # shouldn't happen, bad call
-    my $seqno = $parent_seqno_to_go[$i];
-    return 1 if ( !$seqno || $seqno eq SEQ_ROOT );
-    return 1 if ( $self->[_rblock_type_of_seqno_]->{$seqno} );
-    return;
-} ## end sub is_in_block_by_i
+    $item->[_CUMULATIVE_LENGTH_] = $cumulative_length;
+    $item->[_TOKEN_LENGTH_]      = $token_length;
 
-sub is_in_list_by_i {
-    my ( $self, $i ) = @_;
+    # For reference, here is how to get the parent sequence number.
+    # This is not used because it is slower than finding it on the fly
+    # in sub parent_seqno_by_K:
 
-    # returns true if token at i is contained in a LIST
-    # returns false otherwise
-    my $seqno = $parent_seqno_to_go[$i];
-    return unless ( $seqno && $seqno ne SEQ_ROOT );
-    if ( $self->[_ris_list_by_seqno_]->{$seqno} ) {
-        return 1;
-    }
+    # my $seqno_parent =
+    #     $type_sequence && $is_opening_token{$token}
+    #   ? $seqno_stack{ $depth_next - 2 }
+    #   : $seqno_stack{ $depth_next - 1 };
+    # my $KK = @{$rLL_new};
+    # $rseqno_of_parent_by_K->{$KK} = $seqno_parent;
+
+    # and finally, add this item to the new array
+    push @{$rLL_new}, $item;
     return;
-} ## end sub is_in_list_by_i
+} ## end sub store_token
 
-sub is_list_by_K {
+sub store_space_and_token {
+    my ( $self, $item ) = @_;
 
-    # Return true if token K is in a list
-    my ( $self, $KK ) = @_;
+    # store a token with preceding space if requested and needed
 
-    my $parent_seqno = $self->parent_seqno_by_K($KK);
-    return unless defined($parent_seqno);
-    return $self->[_ris_list_by_seqno_]->{$parent_seqno};
-}
+    # First store the space
+    if (   @{$rLL_new}
+        && $rLL_new->[-1]->[_TYPE_] ne 'b'
+        && $rOpts_add_whitespace )
+    {
+        my $rcopy = [ @{$item} ];
+        $rcopy->[_TYPE_]          = 'b';
+        $rcopy->[_TOKEN_]         = SPACE;
+        $rcopy->[_TYPE_SEQUENCE_] = EMPTY_STRING;
 
-sub is_list_by_seqno {
+        $rcopy->[_LINE_INDEX_] =
+          $rLL_new->[-1]->[_LINE_INDEX_];
 
-    # Return true if the immediate contents of a container appears to be a
-    # list.
-    my ( $self, $seqno ) = @_;
-    return unless defined($seqno);
-    return $self->[_ris_list_by_seqno_]->{$seqno};
-}
+        # Patch 23-Jan-2021 to fix -lp blinkers:
+        # The level and ci_level of newly created spaces should be the same
+        # as the previous token.  Otherwise the coding for the -lp option
+        # can create a blinking state in some rare cases.
+        $rcopy->[_LEVEL_] =
+          $rLL_new->[-1]->[_LEVEL_];
+        $rcopy->[_CI_LEVEL_] =
+          $rLL_new->[-1]->[_CI_LEVEL_];
 
-sub resync_lines_and_tokens {
+        $self->store_token($rcopy);
+    }
 
-    my $self   = shift;
-    my $rLL    = $self->[_rLL_];
-    my $Klimit = $self->[_Klimit_];
-    my $rlines = $self->[_rlines_];
-    my @Krange_code_without_comments;
-    my @Klast_valign_code;
+    # then the token
+    $self->store_token($item);
+    return;
+} ## end sub store_space_and_token
 
-    # Re-construct the arrays of tokens associated with the original input lines
-    # since they have probably changed due to inserting and deleting blanks
-    # and a few other tokens.
+sub add_phantom_semicolon {
 
-    # This is the next token and its line index:
-    my $Knext = 0;
-    my $Kmax  = defined($Klimit) ? $Klimit : -1;
+    my ( $self, $KK ) = @_;
 
-    # Verify that old line indexes are in still order.  If this error occurs,
-    # check locations where sub 'respace_tokens' creates new tokens (like
-    # blank spaces).  It must have set a bad old line index.
-    if ( DEVEL_MODE && defined($Klimit) ) {
-        my $iline = $rLL->[0]->[_LINE_INDEX_];
-        foreach my $KK ( 1 .. $Klimit ) {
-            my $iline_last = $iline;
-            $iline = $rLL->[$KK]->[_LINE_INDEX_];
-            if ( $iline < $iline_last ) {
-                my $KK_m    = $KK - 1;
-                my $token_m = $rLL->[$KK_m]->[_TOKEN_];
-                my $token   = $rLL->[$KK]->[_TOKEN_];
-                my $type_m  = $rLL->[$KK_m]->[_TYPE_];
-                my $type    = $rLL->[$KK]->[_TYPE_];
-                Fault(<<EOM);
-Line indexes out of order at index K=$KK:
-at KK-1 =$KK_m: old line=$iline_last, type='$type_m', token='$token_m'
-at KK   =$KK: old line=$iline, type='$type', token='$token',
-EOM
-            }
-        }
-    }
+    my $Kp = $self->K_previous_nonblank( undef, $rLL_new );
+    return unless ( defined($Kp) );
 
-    my $iline = -1;
-    foreach my $line_of_tokens ( @{$rlines} ) {
-        $iline++;
-        my $line_type = $line_of_tokens->{_line_type};
-        if ( $line_type eq 'CODE' ) {
+    # we are only adding semicolons for certain block types
+    my $type_sequence = $rLL->[$KK]->[_TYPE_SEQUENCE_];
+    return unless ($type_sequence);
+    my $block_type = $rblock_type_of_seqno->{$type_sequence};
+    return unless ($block_type);
+    return
+      unless ( $ok_to_add_semicolon_for_block_type{$block_type}
+        || $block_type =~ /^(sub|package)/
+        || $block_type =~ /^\w+\:$/ );
 
-            # Get the old number of tokens on this line
-            my $rK_range_old = $line_of_tokens->{_rK_range};
-            my ( $Kfirst_old, $Klast_old ) = @{$rK_range_old};
-            my $Kdiff_old = 0;
-            if ( defined($Kfirst_old) ) {
-                $Kdiff_old = $Klast_old - $Kfirst_old;
-            }
+    my $type_p          = $rLL_new->[$Kp]->[_TYPE_];
+    my $token_p         = $rLL_new->[$Kp]->[_TOKEN_];
+    my $type_sequence_p = $rLL_new->[$Kp]->[_TYPE_SEQUENCE_];
 
-            # Find the range of NEW K indexes for the line:
-            # $Kfirst = index of first token on line
-            # $Klast  = index of last token on line
-            my ( $Kfirst, $Klast );
+    # Do not add a semicolon if...
+    return
+      if (
 
-            my $Knext_beg = $Knext;    # this will be $Kfirst if we find tokens
+        # it would follow a comment (and be isolated)
+        $type_p eq '#'
 
-            # Optimization: Although the actual K indexes may be completely
-            # changed after respacing, the number of tokens on any given line
-            # will often be nearly unchanged.  So we will see if we can start
-            # our search by guessing that the new line has the same number
-            # of tokens as the old line.
-            my $Knext_guess = $Knext + $Kdiff_old;
-            if (   $Knext_guess > $Knext
-                && $Knext_guess < $Kmax
-                && $rLL->[$Knext_guess]->[_LINE_INDEX_] <= $iline )
-            {
+        # it follows a code block ( because they are not always wanted
+        # there and may add clutter)
+        || $type_sequence_p && $rblock_type_of_seqno->{$type_sequence_p}
 
-                # the guess is good, so we can start our search here
-                $Knext = $Knext_guess + 1;
-            }
+        # it would follow a label
+        || $type_p eq 'J'
 
-            while ($Knext <= $Kmax
-                && $rLL->[$Knext]->[_LINE_INDEX_] <= $iline )
-            {
-                $Knext++;
-            }
+        # it would be inside a 'format' statement (and cause syntax error)
+        || (   $type_p eq 'k'
+            && $token_p =~ /format/ )
 
-            if ( $Knext > $Knext_beg ) {
+      );
 
-                $Klast = $Knext - 1;
+    # Do not add a semicolon if it would impede a weld with an immediately
+    # following closing token...like this
+    #   { ( some code ) }
+    #                  ^--No semicolon can go here
 
-                # Delete any terminal blank token
-                if ( $rLL->[$Klast]->[_TYPE_] eq 'b' ) { $Klast -= 1 }
+    # look at the previous token... note use of the _NEW rLL array here,
+    # but sequence numbers are invariant.
+    my $seqno_inner = $rLL_new->[$Kp]->[_TYPE_SEQUENCE_];
 
-                if ( $Klast < $Knext_beg ) {
-                    $Klast = undef;
-                }
-                else {
+    # If it is also a CLOSING token we have to look closer...
+    if (
+           $seqno_inner
+        && $is_closing_token{$token_p}
 
-                    $Kfirst = $Knext_beg;
+        # we only need to look if there is just one inner container..
+        && defined( $rchildren_of_seqno->{$type_sequence} )
+        && @{ $rchildren_of_seqno->{$type_sequence} } == 1
+      )
+    {
 
-                    # Save ranges of non-comment code. This will be used by
-                    # sub keep_old_line_breaks.
-                    if ( $rLL->[$Kfirst]->[_TYPE_] ne '#' ) {
-                        push @Krange_code_without_comments, [ $Kfirst, $Klast ];
-                    }
+        # Go back and see if the corresponding two OPENING tokens are also
+        # together.  Note that we are using the OLD K indexing here:
+        my $K_outer_opening = $K_old_opening_by_seqno{$type_sequence};
+        if ( defined($K_outer_opening) ) {
+            my $K_nxt = $self->K_next_nonblank($K_outer_opening);
+            if ( defined($K_nxt) ) {
+                my $seqno_nxt = $rLL->[$K_nxt]->[_TYPE_SEQUENCE_];
 
-                    # Only save ending K indexes of code types which are blank
-                    # or 'VER'.  These will be used for a convergence check.
-                    # See related code in sub 'convey_batch_to_vertical_aligner'
-                    my $CODE_type = $line_of_tokens->{_code_type};
-                    if (  !$CODE_type
-                        || $CODE_type eq 'VER' )
-                    {
-                        push @Klast_valign_code, $Klast;
-                    }
-                }
+                # Is the next token after the outer opening the same as
+                # our inner closing (i.e. same sequence number)?
+                # If so, do not insert a semicolon here.
+                return if ( $seqno_nxt && $seqno_nxt == $seqno_inner );
             }
+        }
+    }
 
-            # It is only safe to trim the actual line text if the input
-            # line had a terminal blank token. Otherwise, we may be
-            # in a quote.
-            if ( $line_of_tokens->{_ended_in_blank_token} ) {
-                $line_of_tokens->{_line_text} =~ s/\s+$//;
-            }
-            $line_of_tokens->{_rK_range} = [ $Kfirst, $Klast ];
+    # We will insert an empty semicolon here as a placeholder.  Later, if
+    # it becomes the last token on a line, we will bring it to life.  The
+    # advantage of doing this is that (1) we just have to check line
+    # endings, and (2) the phantom semicolon has zero width and therefore
+    # won't cause needless breaks of one-line blocks.
+    my $Ktop = -1;
+    if (   $rLL_new->[$Ktop]->[_TYPE_] eq 'b'
+        && $want_left_space{';'} == WS_NO )
+    {
 
-            # Deleting semicolons can create new empty code lines
-            # which should be marked as blank
-            if ( !defined($Kfirst) ) {
-                my $CODE_type = $line_of_tokens->{_code_type};
-                if ( !$CODE_type ) {
-                    $line_of_tokens->{_code_type} = 'BL';
-                }
-            }
+        # convert the blank into a semicolon..
+        # be careful: we are working on the new stack top
+        # on a token which has been stored.
+        my $rcopy = copy_token_as_type( $rLL_new->[$Ktop], 'b', SPACE );
+
+        # Convert the existing blank to:
+        #   a phantom semicolon for one_line_block option = 0 or 1
+        #   a real semicolon    for one_line_block option = 2
+        my $tok     = EMPTY_STRING;
+        my $len_tok = 0;
+        if ( $rOpts_one_line_block_semicolons == 2 ) {
+            $tok     = ';';
+            $len_tok = 1;
         }
-    }
 
-    # There shouldn't be any nodes beyond the last one.  This routine is
-    # relinking lines and tokens after the tokens have been respaced.  A fault
-    # here indicates some kind of bug has been introduced into the above loops.
-    # There is not good way to keep going; we better stop here.
-    # FIXME: This will produce zero output. it would be best to find a way to
-    # dump the input file.
-    if ( $Knext <= $Kmax ) {
+        $rLL_new->[$Ktop]->[_TOKEN_]        = $tok;
+        $rLL_new->[$Ktop]->[_TOKEN_LENGTH_] = $len_tok;
+        $rLL_new->[$Ktop]->[_TYPE_]         = ';';
 
-        Fault("unexpected tokens at end of file when reconstructing lines");
-    }
-    $self->[_rKrange_code_without_comments_] = \@Krange_code_without_comments;
+        $self->[_rtype_count_by_seqno_]->{$type_sequence}->{';'}++;
 
-    # Setup the convergence test in the FileWriter based on line-ending indexes
-    my $file_writer_object = $self->[_file_writer_object_];
-    $file_writer_object->setup_convergence_test( \@Klast_valign_code );
+        # NOTE: we are changing the output stack without updating variables
+        # $last_nonblank_code_type, etc. Future needs might require that
+        # those variables be updated here.  For now, it seems ok to skip
+        # this.
 
-    # Mark essential old breakpoints if combination -iob -lp is used.  These
-    # two options do not work well together, but we can avoid turning -iob off
-    # by ignoring -iob at certain essential line breaks.
-    # Fixes cases b1021 b1023 b1034 b1048 b1049 b1050 b1056 b1058
-    if ( $rOpts_ignore_old_breakpoints && $rOpts_line_up_parentheses ) {
-        my %is_assignment_or_fat_comma = %is_assignment;
-        $is_assignment_or_fat_comma{'=>'} = 1;
-        my $ris_essential_old_breakpoint =
-          $self->[_ris_essential_old_breakpoint_];
-        my ( $Kfirst, $Klast );
-        foreach my $line_of_tokens ( @{$rlines} ) {
-            my $line_type = $line_of_tokens->{_line_type};
-            if ( $line_type ne 'CODE' ) {
-                ( $Kfirst, $Klast ) = ( undef, undef );
-                next;
-            }
-            my ( $Kfirst_prev, $Klast_prev ) = ( $Kfirst, $Klast );
-            ( $Kfirst, $Klast ) = @{ $line_of_tokens->{_rK_range} };
+        # Then store a new blank
+        $self->store_token($rcopy);
+    }
+    else {
 
-            next unless defined($Klast_prev);
-            next unless defined($Kfirst);
-            my $type_last  = $rLL->[$Klast_prev]->[_TOKEN_];
-            my $type_first = $rLL->[$Kfirst]->[_TOKEN_];
-            next
-              unless ( $is_assignment_or_fat_comma{$type_last}
-                || $is_assignment_or_fat_comma{$type_first} );
-            $ris_essential_old_breakpoint->{$Klast_prev} = 1;
+        # Patch for issue c078: keep line indexes in order.  If the top
+        # token is a space that we are keeping (due to '-wls=';') then
+        # we have to check that old line indexes stay in order.
+        # In very rare
+        # instances in which side comments have been deleted and converted
+        # into blanks, we may have filtered down multiple blanks into just
+        # one. In that case the top blank may have a higher line number
+        # than the previous nonblank token. Although the line indexes of
+        # blanks are not really significant, we need to keep them in order
+        # in order to pass error checks.
+        if ( $rLL_new->[$Ktop]->[_TYPE_] eq 'b' ) {
+            my $old_top_ix = $rLL_new->[$Ktop]->[_LINE_INDEX_];
+            my $new_top_ix = $rLL_new->[$Kp]->[_LINE_INDEX_];
+            if ( $new_top_ix < $old_top_ix ) {
+                $rLL_new->[$Ktop]->[_LINE_INDEX_] = $new_top_ix;
+            }
         }
+
+        my $rcopy = copy_token_as_type( $rLL_new->[$Kp], ';', EMPTY_STRING );
+        $self->store_token($rcopy);
     }
     return;
-} ## end sub resync_lines_and_tokens
-
-sub keep_old_line_breaks {
+} ## end sub add_phantom_semicolon
 
-    # Called once per file to find and mark any old line breaks which
-    # should be kept.  We will be translating the input hashes into
-    # token indexes.
+sub add_trailing_comma {
 
-    # A flag is set as follows:
-    # = 1 make a hard break (flush the current batch)
-    #     best for something like leading commas (-kbb=',')
-    # = 2 make a soft break (keep building current batch)
-    #     best for something like leading ->
+    # Implement the --add-trailing-commas flag to the line end before index $KK:
 
-    my ($self) = @_;
+    my ( $self, $KK, $Kfirst, $trailing_comma_rule ) = @_;
 
-    my $rLL = $self->[_rLL_];
-    my $rKrange_code_without_comments =
-      $self->[_rKrange_code_without_comments_];
-    my $rbreak_before_Kfirst = $self->[_rbreak_before_Kfirst_];
-    my $rbreak_after_Klast   = $self->[_rbreak_after_Klast_];
-    my $rwant_container_open = $self->[_rwant_container_open_];
-    my $K_opening_container  = $self->[_K_opening_container_];
-    my $ris_broken_container = $self->[_ris_broken_container_];
-    my $ris_list_by_seqno    = $self->[_ris_list_by_seqno_];
+    # Input parameter:
+    #  $KK = index of closing token in old ($rLL) token list
+    #        which starts a new line and is not preceded by a comma
+    #  $Kfirst = index of first token on the current line of input tokens
+    #  $add_flags = user control flags
 
-    # This code moved here from sub break_lists to fix b1120
-    if ( $rOpts->{'break-at-old-method-breakpoints'} ) {
-        foreach my $item ( @{$rKrange_code_without_comments} ) {
-            my ( $Kfirst, $Klast ) = @{$item};
-            my $type  = $rLL->[$Kfirst]->[_TYPE_];
-            my $token = $rLL->[$Kfirst]->[_TOKEN_];
+    # For example, we might want to add a comma here:
 
-            # leading '->' use a value of 2 which causes a soft
-            # break rather than a hard break
-            if ( $type eq '->' ) {
-                $rbreak_before_Kfirst->{$Kfirst} = 2;
-            }
+    #   bless {
+    #           _name   => $name,
+    #           _price  => $price,
+    #           _rebate => $rebate  <------ location of possible bare comma
+    #          }, $pkg;
+    #          ^-------------------closing token at index $KK on new line
 
-            # leading ')->' use a special flag to insure that both
-            # opening and closing parens get opened
-            # Fix for b1120: only for parens, not braces
-            elsif ( $token eq ')' ) {
-                my $Kn = $self->K_next_nonblank($Kfirst);
-                next
-                  unless ( defined($Kn)
-                    && $Kn <= $Klast
-                    && $rLL->[$Kn]->[_TYPE_] eq '->' );
-                my $seqno = $rLL->[$Kfirst]->[_TYPE_SEQUENCE_];
-                next unless ($seqno);
+    # Do not add a comma if it would follow a comment
+    my $Kp = $self->K_previous_nonblank( undef, $rLL_new );
+    return unless ( defined($Kp) );
+    my $type_p = $rLL_new->[$Kp]->[_TYPE_];
+    return if ( $type_p eq '#' );
 
-                # Note: in previous versions there was a fix here to avoid
-                # instability between conflicting -bom and -pvt or -pvtc flags.
-                # The fix skipped -bom for a small line difference.  But this
-                # was troublesome, and instead the fix has been moved to
-                # sub set_vertical_tightness_flags where priority is given to
-                # the -bom flag over -pvt and -pvtc flags.  Both opening and
-                # closing paren flags are involved because even though -bom only
-                # requests breaking before the closing paren, automated logic
-                # opens the opening paren when the closing paren opens.
-                # Relevant cases are b977, b1215, b1270, b1303
+    # see if the user wants a trailing comma here
+    my $match =
+      $self->match_trailing_comma_rule( $KK, $Kfirst, $Kp,
+        $trailing_comma_rule, 1 );
 
-                $rwant_container_open->{$seqno} = 1;
-            }
-        }
+    # if so, add a comma
+    if ($match) {
+        my $Knew = $self->store_new_token( ',', ',', $Kp );
     }
 
-    return unless ( %keep_break_before_type || %keep_break_after_type );
+    return;
 
-    my $check_for_break = sub {
-        my ( $KK, $rkeep_break_hash, $rbreak_hash ) = @_;
-        my $seqno = $rLL->[$KK]->[_TYPE_SEQUENCE_];
+} ## end sub add_trailing_comma
 
-        # non-container tokens use the type as the key
-        if ( !$seqno ) {
-            my $type = $rLL->[$KK]->[_TYPE_];
-            if ( $rkeep_break_hash->{$type} ) {
-                $rbreak_hash->{$KK} = 1;
-            }
-        }
+sub delete_trailing_comma {
 
-        # container tokens use the token as the key
-        else {
-            my $token = $rLL->[$KK]->[_TOKEN_];
-            my $flag  = $rkeep_break_hash->{$token};
-            if ($flag) {
+    my ( $self, $KK, $Kfirst, $trailing_comma_rule ) = @_;
 
-                my $match = $flag eq '1' || $flag eq '*';
+    # Apply the --delete-trailing-commas flag to the comma before index $KK
 
-                # check for special matching codes
-                if ( !$match ) {
-                    if ( $token eq '(' || $token eq ')' ) {
-                        $match = $self->match_paren_flag( $KK, $flag );
-                    }
-                    elsif ( $token eq '{' || $token eq '}' ) {
+    # Input parameter:
+    #  $KK = index of a closing token in OLD ($rLL) token list
+    #        which is preceded by a comma on the same line.
+    #  $Kfirst = index of first token on the current line of input tokens
+    #  $delete_option = user control flag
 
-                        # These tentative codes 'b' and 'B' for brace types are
-                        # placeholders for possible future brace types. They
-                        # are not documented and may be changed.
-                        my $block_type =
-                          $self->[_rblock_type_of_seqno_]->{$seqno};
-                        if    ( $flag eq 'b' ) { $match = $block_type }
-                        elsif ( $flag eq 'B' ) { $match = !$block_type }
-                        else {
-                            # unknown code - no match
-                        }
-                    }
-                }
-                $rbreak_hash->{$KK} = 1 if ($match);
-            }
-        }
-    };
-
-    foreach my $item ( @{$rKrange_code_without_comments} ) {
-        my ( $Kfirst, $Klast ) = @{$item};
-        $check_for_break->(
-            $Kfirst, \%keep_break_before_type, $rbreak_before_Kfirst
-        );
-        $check_for_break->(
-            $Klast, \%keep_break_after_type, $rbreak_after_Klast
-        );
-    }
-    return;
-} ## end sub keep_old_line_breaks
+    # Returns true if the comma was deleted
 
-sub weld_containers {
+    # For example, we might want to delete this comma:
+    #    my @asset = ("FASMX", "FASGX", "FASIX",);
+    #    |                                     |^--------token at index $KK
+    #    |                                     ^------comma of interest
+    #    ^-------------token at $Kfirst
 
-    # Called once per file to do any welding operations requested by --weld*
-    # flags.
-    my ($self) = @_;
+    # Verify that the previous token is a comma.  Note that we are working in
+    # the new token list $rLL_new.
+    my $Kp = $self->K_previous_nonblank( undef, $rLL_new );
+    return unless ( defined($Kp) );
+    if ( $rLL_new->[$Kp]->[_TYPE_] ne ',' ) {
 
-    # This count is used to eliminate needless calls for weld checks elsewhere
-    $total_weld_count = 0;
+        # there must be a '#' between the ',' and closing token; give up.
+        return;
+    }
 
-    return if ( $rOpts->{'indent-only'} );
-    return unless ($rOpts_add_newlines);
+    # Do not delete commas when formatting under stress to avoid instability.
+    # This fixes b1389, b1390, b1391, b1392.  The $high_stress_level has
+    # been found to work well for trailing commas.
+    if ( $rLL_new->[$Kp]->[_LEVEL_] >= $high_stress_level ) {
+        return;
+    }
 
-    # Important: sub 'weld_cuddled_blocks' must be called before
-    # sub 'weld_nested_containers'. This is because the cuddled option needs to
-    # use the original _LEVEL_ values of containers, but the weld nested
-    # containers changes _LEVEL_ of welded containers.
+    # See if the user wants this trailing comma
+    my $match =
+      $self->match_trailing_comma_rule( $KK, $Kfirst, $Kp,
+        $trailing_comma_rule, 0 );
 
-    # Here is a good test case to be sure that both cuddling and welding
-    # are working and not interfering with each other: <<snippets/ce_wn1.in>>
+    # Patch: the --noadd-whitespace flag can cause instability in complex
+    # structures. In this case do not delete the comma. Fixes b1409.
+    if ( !$match && !$rOpts_add_whitespace ) {
+        my $Kn = $self->K_next_nonblank($KK);
+        if ( defined($Kn) ) {
+            my $type_n = $rLL->[$Kn]->[_TYPE_];
+            if ( $type_n ne ';' && $type_n ne '#' ) { return }
+        }
+    }
 
-    #   perltidy -wn -ce
+    # If no match, delete it
+    if ( !$match ) {
 
-   # if ($BOLD_MATH) { (
-   #     $labels, $comment,
-   #     join( '', '<B>', &make_math( $mode, '', '', $_ ), '</B>' )
-   # ) } else { (
-   #     &process_math_in_latex( $mode, $math_style, $slevel, "\\mbox{$text}" ),
-   #     $after
-   # ) }
+        return $self->unstore_last_nonblank_token(',');
+    }
+    return;
 
-    $self->weld_cuddled_blocks() if ( %{$rcuddled_block_types} );
+} ## end sub delete_trailing_comma
 
-    if ( $rOpts->{'weld-nested-containers'} ) {
+sub delete_weld_interfering_comma {
 
-        $self->weld_nested_containers();
+    my ( $self, $KK ) = @_;
 
-        $self->weld_nested_quotes();
-    }
+    # Apply the flag '--delete-weld-interfering-commas' to the comma
+    # before index $KK
 
-    #-------------------------------------------------------------
-    # All welding is done. Finish setting up weld data structures.
-    #-------------------------------------------------------------
+    # Input parameter:
+    #  $KK = index of a closing token in OLD ($rLL) token list
+    #        which is preceded by a comma on the same line.
 
-    my $rLL                  = $self->[_rLL_];
-    my $rK_weld_left         = $self->[_rK_weld_left_];
-    my $rK_weld_right        = $self->[_rK_weld_right_];
-    my $rweld_len_right_at_K = $self->[_rweld_len_right_at_K_];
+    # Returns true if the comma was deleted
 
-    my @K_multi_weld;
-    my @keys = keys %{$rK_weld_right};
-    $total_weld_count = @keys;
+    # For example, we might want to delete this comma:
 
-    # First pass to process binary welds.
-    # This loop is processed in unsorted order for efficiency.
-    foreach my $Kstart (@keys) {
-        my $Kend = $rK_weld_right->{$Kstart};
+    # my $tmpl = { foo => {no_override => 1, default => 42}, };
+    #                                                     || ^------$KK
+    #                                                     |^---$Kp
+    #                                              $Kpp---^
+    #
+    # Note that:
+    #  index $KK is in the old $rLL array, but
+    #  indexes $Kp and $Kpp are in the new $rLL_new array.
 
-        # An error here would be due to an incorrect initialization introduced
-        # in one of the above weld routines, like sub weld_nested.
-        if ( $Kend <= $Kstart ) {
-            Fault("Bad weld link: Kend=$Kend <= Kstart=$Kstart\n")
-              if (DEVEL_MODE);
-            next;
-        }
+    my $type_sequence = $rLL->[$KK]->[_TYPE_SEQUENCE_];
+    return unless ($type_sequence);
 
-        # Set weld values for all tokens this welded pair
-        foreach ( $Kstart + 1 .. $Kend ) {
-            $rK_weld_left->{$_} = $Kstart;
-        }
-        foreach my $Kx ( $Kstart .. $Kend - 1 ) {
-            $rK_weld_right->{$Kx} = $Kend;
-            $rweld_len_right_at_K->{$Kx} =
-              $rLL->[$Kend]->[_CUMULATIVE_LENGTH_] -
-              $rLL->[$Kx]->[_CUMULATIVE_LENGTH_];
-        }
+    # Find the previous token and verify that it is a comma.
+    my $Kp = $self->K_previous_nonblank( undef, $rLL_new );
+    return unless ( defined($Kp) );
+    if ( $rLL_new->[$Kp]->[_TYPE_] ne ',' ) {
 
-        # Remember the leftmost index of welds which continue to the right
-        if ( defined( $rK_weld_right->{$Kend} )
-            && !defined( $rK_weld_left->{$Kstart} ) )
-        {
-            push @K_multi_weld, $Kstart;
-        }
+        # it is not a comma, so give up ( it is probably a '#' )
+        return;
     }
 
-    # Second pass to process chains of welds (these are rare).
-    # This has to be processed in sorted order.
-    if (@K_multi_weld) {
-        my $Kend = -1;
-        foreach my $Kstart ( sort { $a <=> $b } @K_multi_weld ) {
+    # This must be the only comma in this list
+    my $rtype_count = $self->[_rtype_count_by_seqno_]->{$type_sequence};
+    return
+      unless ( defined($rtype_count)
+        && $rtype_count->{','}
+        && $rtype_count->{','} == 1 );
 
-            # Skip any interior K which was originally missing a left link
-            next if ( $Kstart <= $Kend );
+    # Back up to the previous closing token
+    my $Kpp = $self->K_previous_nonblank( $Kp, $rLL_new );
+    return unless ( defined($Kpp) );
+    my $seqno_pp = $rLL_new->[$Kpp]->[_TYPE_SEQUENCE_];
+    my $type_pp  = $rLL_new->[$Kpp]->[_TYPE_];
 
-            # Find the end of this chain
-            $Kend = $rK_weld_right->{$Kstart};
-            my $Knext = $rK_weld_right->{$Kend};
-            while ( defined($Knext) ) {
-                $Kend  = $Knext;
-                $Knext = $rK_weld_right->{$Kend};
-            }
+    # The containers must be nesting (i.e., sequence numbers must differ by 1 )
+    if ( $seqno_pp && $is_closing_type{$type_pp} ) {
+        if ( $seqno_pp == $type_sequence + 1 ) {
 
-            # Set weld values this chain
-            foreach ( $Kstart + 1 .. $Kend ) {
-                $rK_weld_left->{$_} = $Kstart;
-            }
-            foreach my $Kx ( $Kstart .. $Kend - 1 ) {
-                $rK_weld_right->{$Kx} = $Kend;
-                $rweld_len_right_at_K->{$Kx} =
-                  $rLL->[$Kend]->[_CUMULATIVE_LENGTH_] -
-                  $rLL->[$Kx]->[_CUMULATIVE_LENGTH_];
-            }
+            # remove the ',' from the top of the new token list
+            return $self->unstore_last_nonblank_token(',');
         }
     }
-
     return;
-} ## end sub weld_containers
 
-sub cumulative_length_before_K {
-    my ( $self, $KK ) = @_;
-    my $rLL = $self->[_rLL_];
-    return ( $KK <= 0 ) ? 0 : $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_];
-}
+} ## end sub delete_trailing_comma
 
-sub weld_cuddled_blocks {
-    my ($self) = @_;
+sub unstore_last_nonblank_token {
 
-    # Called once per file to handle cuddled formatting
+    my ( $self, $type ) = @_;
 
-    my $rK_weld_left         = $self->[_rK_weld_left_];
-    my $rK_weld_right        = $self->[_rK_weld_right_];
-    my $rblock_type_of_seqno = $self->[_rblock_type_of_seqno_];
+    # remove the most recent nonblank token from the new token list
+    # Input parameter:
+    #   $type = type to be removed (for safety check)
 
-    # This routine implements the -cb flag by finding the appropriate
-    # closing and opening block braces and welding them together.
-    return unless ( %{$rcuddled_block_types} );
+    # Returns true if success
+    #         false if error
 
-    my $rLL = $self->[_rLL_];
-    return unless ( defined($rLL) && @{$rLL} );
-    my $rbreak_container = $self->[_rbreak_container_];
+    # This was written and is used for removing commas, but might
+    # be useful for other tokens. If it is ever used for other tokens
+    # then the issue of what to do about the other variables, such
+    # as token counts and the '$last...' vars needs to be considered.
 
-    my $K_opening_container = $self->[_K_opening_container_];
-    my $K_closing_container = $self->[_K_closing_container_];
+    # Safety check, shouldn't happen
+    if ( @{$rLL_new} < 3 ) {
+        DEVEL_MODE && Fault("not enough tokens on stack to remove '$type'\n");
+        return;
+    }
 
-    my $length_to_opening_seqno = sub {
-        my ($seqno) = @_;
-        my $KK      = $K_opening_container->{$seqno};
-        my $lentot  = $KK <= 0 ? 0 : $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_];
-        return $lentot;
-    };
-    my $length_to_closing_seqno = sub {
-        my ($seqno) = @_;
-        my $KK      = $K_closing_container->{$seqno};
-        my $lentot  = $KK <= 0 ? 0 : $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_];
-        return $lentot;
-    };
+    my ( $rcomma, $rblank );
 
-    my $is_broken_block = sub {
+    # case 1: pop comma from top of stack
+    if ( $rLL_new->[-1]->[_TYPE_] eq $type ) {
+        $rcomma = pop @{$rLL_new};
+    }
 
-        # a block is broken if the input line numbers of the braces differ
-        # we can only cuddle between broken blocks
-        my ($seqno) = @_;
-        my $K_opening = $K_opening_container->{$seqno};
-        return unless ( defined($K_opening) );
-        my $K_closing = $K_closing_container->{$seqno};
-        return unless ( defined($K_closing) );
-        return $rbreak_container->{$seqno}
-          || $rLL->[$K_closing]->[_LINE_INDEX_] !=
-          $rLL->[$K_opening]->[_LINE_INDEX_];
-    };
+    # case 2: pop blank and then comma from top of stack
+    elsif ($rLL_new->[-1]->[_TYPE_] eq 'b'
+        && $rLL_new->[-2]->[_TYPE_] eq $type )
+    {
+        $rblank = pop @{$rLL_new};
+        $rcomma = pop @{$rLL_new};
+    }
 
-    # A stack to remember open chains at all levels: This is a hash rather than
-    # an array for safety because negative levels can occur in files with
-    # errors.  This allows us to keep processing with negative levels.
-    # $in_chain{$level} = [$chain_type, $type_sequence];
-    my %in_chain;
-    my $CBO = $rOpts->{'cuddled-break-option'};
+    # case 3: error, shouldn't happen unless bad call
+    else {
+        DEVEL_MODE && Fault("Could not find token type '$type' to remove\n");
+        return;
+    }
 
-    # loop over structure items to find cuddled pairs
-    my $level = 0;
-    my $KNEXT = $self->[_K_first_seq_item_];
-    while ( defined($KNEXT) ) {
-        my $KK = $KNEXT;
-        $KNEXT = $rLL->[$KNEXT]->[_KNEXT_SEQ_ITEM_];
-        my $rtoken_vars   = $rLL->[$KK];
-        my $type_sequence = $rtoken_vars->[_TYPE_SEQUENCE_];
-        if ( !$type_sequence ) {
-            next if ( $KK == 0 );    # first token in file may not be container
+    # A note on updating vars set by sub store_token for this comma: If we
+    # reduce the comma count by 1 then we also have to change the variable
+    # $last_nonblank_code_type to be $last_last_nonblank_code_type because
+    # otherwise sub store_token is going to ALSO reduce the comma count.
+    # Alternatively, we can leave the count alone and the
+    # $last_nonblank_code_type alone. Then sub store_token will produce
+    # the correct result. This is simpler and is done here.
 
-            # A fault here implies that an error was made in the little loop at
-            # the bottom of sub 'respace_tokens' which set the values of
-            # _KNEXT_SEQ_ITEM_.  Or an error has been introduced in the
-            # loop control lines above.
-            Fault("sequence = $type_sequence not defined at K=$KK")
-              if (DEVEL_MODE);
-            next;
+    # Now add a blank space after the comma if appropriate.
+    # Some unusual spacing controls might need another iteration to
+    # reach a final state.
+    if ( $rLL_new->[-1]->[_TYPE_] ne 'b' ) {
+        if ( defined($rblank) ) {
+            $rblank->[_CUMULATIVE_LENGTH_] -= 1;    # fix for deleted comma
+            push @{$rLL_new}, $rblank;
         }
+    }
+    return 1;
+}
 
-        # NOTE: we must use the original levels here. They can get changed
-        # by sub 'weld_nested_containers', so this routine must be called
-        # before sub 'weld_nested_containers'.
-        my $last_level = $level;
-        $level = $rtoken_vars->[_LEVEL_];
+sub match_trailing_comma_rule {
+
+    my ( $self, $KK, $Kfirst, $Kp, $trailing_comma_rule, $if_add ) = @_;
+
+    # Decide if a trailing comma rule is matched.
+
+    # Input parameter:
+    #  $KK = index of closing token in old ($rLL) token list which follows
+    #    the location of a possible trailing comma. See diagram below.
+    #  $Kfirst = (old) index of first token on the current line of input tokens
+    #  $Kp = index of previous nonblank token in new ($rLL_new) array
+    #  $trailing_comma_rule = packed user control flags
+    #  $if_add = true if adding comma, false if deleteing comma
+
+    # Returns:
+    #   false if no match
+    #   true  if match
+
+    # For example, we might be checking for addition of a comma here:
+
+    #   bless {
+    #           _name   => $name,
+    #           _price  => $price,
+    #           _rebate => $rebate  <------ location of possible trailing comma
+    #          }, $pkg;
+    #          ^-------------------closing token at index $KK
+
+    return unless ($trailing_comma_rule);
+    my ( $trailing_comma_style, $paren_flag ) = @{$trailing_comma_rule};
+
+    # List of $trailing_comma_style values:
+    #   undef  stable: do not change
+    #   '0' : no list should have a trailing comma
+    #   '1' or '*' : every list should have a trailing comma
+    #   'm' a multi-line list should have a trailing commas
+    #   'b' trailing commas should be 'bare' (comma followed by newline)
+    #   'h' lists of key=>value pairs with a bare trailing comma
+    #   'i' same as s=h but also include any list with no more than about one
+    #       comma per line
+    #   ' ' or -wtc not defined : leave trailing commas unchanged [DEFAULT].
+
+    # Note: an interesting generalization would be to let an upper case
+    # letter denote the negation of styles 'm', 'b', 'h', 'i'. This might
+    # be useful for undoing operations. It would be implemented as a wrapper
+    # around this routine.
+
+    #-----------------------------------------
+    #  No style defined : do not add or delete
+    #-----------------------------------------
+    if ( !defined($trailing_comma_style) ) { return !$if_add }
+
+    #----------------------------------------
+    # Set some flags describing this location
+    #----------------------------------------
+    my $type_sequence = $rLL->[$KK]->[_TYPE_SEQUENCE_];
+    return unless ($type_sequence);
+    my $closing_token = $rLL->[$KK]->[_TOKEN_];
+    my $rtype_count   = $self->[_rtype_count_by_seqno_]->{$type_sequence};
+    return unless ( defined($rtype_count) && $rtype_count->{','} );
+    my $is_permanently_broken =
+      $self->[_ris_permanently_broken_]->{$type_sequence};
+
+    # Note that _ris_broken_container_ also stores the line diff
+    # but it is not available at this early stage.
+    my $K_opening = $self->[_K_opening_container_]->{$type_sequence};
+    return if ( !defined($K_opening) );
+
+    # multiline definition 1: opening and closing tokens on different lines
+    my $iline_o                  = $rLL_new->[$K_opening]->[_LINE_INDEX_];
+    my $iline_c                  = $rLL->[$KK]->[_LINE_INDEX_];
+    my $line_diff_containers     = $iline_c - $iline_o;
+    my $has_multiline_containers = $line_diff_containers > 0;
+
+    # multiline definition 2: first and last commas on different lines
+    my $iline_first = $self->[_rfirst_comma_line_index_]->{$type_sequence};
+    my $iline_last  = $rLL_new->[$Kp]->[_LINE_INDEX_];
+    my $has_multiline_commas;
+    my $line_diff_commas = 0;
+    if ( !defined($iline_first) ) {
+
+        # shouldn't happen if caller checked comma count
+        my $type_kp = $rLL_new->[$Kp]->[_TYPE_];
+        Fault(
+"at line $iline_last but line of first comma not defined, at Kp=$Kp, type=$type_kp\n"
+        ) if (DEVEL_MODE);
+    }
+    else {
+        $line_diff_commas     = $iline_last - $iline_first;
+        $has_multiline_commas = $line_diff_commas > 0;
+    }
 
-        if    ( $level < $last_level ) { $in_chain{$last_level} = undef }
-        elsif ( $level > $last_level ) { $in_chain{$level}      = undef }
+    # To avoid instability in edge cases, when adding commas we uses the
+    # multiline_commas definition, but when deleting we use multiline
+    # containers.  This fixes b1384, b1396, b1397, b1398, b1400.
+    my $is_multiline =
+      $if_add ? $has_multiline_commas : $has_multiline_containers;
 
-        # We are only looking at code blocks
-        my $token = $rtoken_vars->[_TOKEN_];
-        my $type  = $rtoken_vars->[_TYPE_];
-        next unless ( $type eq $token );
+    my $is_bare_multiline_comma = $is_multiline && $KK == $Kfirst;
 
-        if ( $token eq '{' ) {
+    my $match;
 
-            my $block_type = $rblock_type_of_seqno->{$type_sequence};
-            if ( !$block_type ) {
+    #----------------------------
+    # 0 : does not match any list
+    #----------------------------
+    if ( $trailing_comma_style eq '0' ) {
+        $match = 0;
+    }
 
-                # patch for unrecognized block types which may not be labeled
-                my $Kp = $self->K_previous_nonblank($KK);
-                while ( $Kp && $rLL->[$Kp]->[_TYPE_] eq '#' ) {
-                    $Kp = $self->K_previous_nonblank($Kp);
-                }
-                next unless $Kp;
-                $block_type = $rLL->[$Kp]->[_TOKEN_];
-            }
-            if ( $in_chain{$level} ) {
+    #------------------------------
+    # '*' or '1' : matches any list
+    #------------------------------
+    elsif ( $trailing_comma_style eq '*' || $trailing_comma_style eq '1' ) {
+        $match = 1;
+    }
 
-                # we are in a chain and are at an opening block brace.
-                # See if we are welding this opening brace with the previous
-                # block brace.  Get their identification numbers:
-                my $closing_seqno = $in_chain{$level}->[1];
-                my $opening_seqno = $type_sequence;
+    #-----------------------------
+    # 'm' matches a Multiline list
+    #-----------------------------
+    elsif ( $trailing_comma_style eq 'm' ) {
+        $match = $is_multiline;
+    }
 
-                # The preceding block must be on multiple lines so that its
-                # closing brace will start a new line.
-                if ( !$is_broken_block->($closing_seqno) ) {
-                    next unless ( $CBO == 2 );
-                    $rbreak_container->{$closing_seqno} = 1;
-                }
+    #----------------------------------
+    # 'b' matches a Bare trailing comma
+    #----------------------------------
+    elsif ( $trailing_comma_style eq 'b' ) {
+        $match = $is_bare_multiline_comma;
+    }
 
-                # we will let the trailing block be either broken or intact
-                ## && $is_broken_block->($opening_seqno);
+    #--------------------------------------------------------------------------
+    # 'h' matches a bare hash list with about 1 comma and 1 fat comma per line.
+    # 'i' matches a bare stable list with about 1 comma per line.
+    #--------------------------------------------------------------------------
+    elsif ( $trailing_comma_style eq 'h' || $trailing_comma_style eq 'i' ) {
 
-                # We can weld the closing brace to its following word ..
-                my $Ko = $K_closing_container->{$closing_seqno};
-                my $Kon;
-                if ( defined($Ko) ) {
-                    $Kon = $self->K_next_nonblank($Ko);
-                }
+        # We can treat these together because they are similar.
+        # The set of 'i' matches includes the set of 'h' matches.
 
-                # ..unless it is a comment
-                if ( defined($Kon) && $rLL->[$Kon]->[_TYPE_] ne '#' ) {
+        # the trailing comma must be bare for both 'h' and 'i'
+        return if ( !$is_bare_multiline_comma );
 
-                    # OK to weld these two tokens...
-                    $rK_weld_right->{$Ko} = $Kon;
-                    $rK_weld_left->{$Kon} = $Ko;
+        # there must be no more than one comma per line for both 'h' and 'i'
+        my $new_comma_count = $rtype_count->{','};
+        $new_comma_count += 1 if ($if_add);
+        return                if ( $new_comma_count > $line_diff_commas + 1 );
 
-                    # Set flag that we want to break the next container
-                    # so that the cuddled line is balanced.
-                    $rbreak_container->{$opening_seqno} = 1
-                      if ($CBO);
-                }
-
-            }
-            else {
+        # a list of key=>value pairs with at least 2 fat commas is a match
+        # for both 'h' and 'i'
+        my $fat_comma_count = $rtype_count->{'=>'};
+        if ( $fat_comma_count && $fat_comma_count >= 2 ) {
 
-                # We are not in a chain. Start a new chain if we see the
-                # starting block type.
-                if ( $rcuddled_block_types->{$block_type} ) {
-                    $in_chain{$level} = [ $block_type, $type_sequence ];
-                }
-                else {
-                    $block_type = '*';
-                    $in_chain{$level} = [ $block_type, $type_sequence ];
-                }
-            }
+            # comma count (including trailer) and fat comma count must differ by
+            # by no more than 1. This allows for some small variations.
+            my $comma_diff = $new_comma_count - $fat_comma_count;
+            $match = ( $comma_diff >= -1 && $comma_diff <= 1 );
         }
-        elsif ( $token eq '}' ) {
-            if ( $in_chain{$level} ) {
-
-                # We are in a chain at a closing brace.  See if this chain
-                # continues..
-                my $Knn = $self->K_next_code($KK);
-                next unless $Knn;
-
-                my $chain_type          = $in_chain{$level}->[0];
-                my $next_nonblank_token = $rLL->[$Knn]->[_TOKEN_];
-                if (
-                    $rcuddled_block_types->{$chain_type}->{$next_nonblank_token}
-                  )
-                {
 
-                    # Note that we do not weld yet because we must wait until
-                    # we we are sure that an opening brace for this follows.
-                    $in_chain{$level}->[1] = $type_sequence;
-                }
-                else { $in_chain{$level} = undef }
-            }
+        # For 'i' only, a list that can be shown to be stable is a match
+        if ( $trailing_comma_style eq 'i' ) {
+            $match ||= (
+                $is_permanently_broken
+                  || ( $rOpts_break_at_old_comma_breakpoints
+                    && !$rOpts_ignore_old_breakpoints )
+            );
         }
     }
-    return;
-} ## end sub weld_cuddled_blocks
 
-sub find_nested_pairs {
-    my $self = shift;
+    #-------------------------------------------------------------------------
+    # Unrecognized parameter. This should have been caught in the input check.
+    #-------------------------------------------------------------------------
+    else {
 
-    # This routine is called once per file to do preliminary work needed for
-    # the --weld-nested option.  This information is also needed for adding
-    # semicolons.
+        DEVEL_MODE && Fault("Unrecognized parameter '$trailing_comma_style'\n");
 
-    my $rLL = $self->[_rLL_];
-    return unless ( defined($rLL) && @{$rLL} );
-    my $Num = @{$rLL};
+        # do not add or delete
+        return !$if_add;
+    }
 
-    my $K_opening_container  = $self->[_K_opening_container_];
-    my $K_closing_container  = $self->[_K_closing_container_];
-    my $rblock_type_of_seqno = $self->[_rblock_type_of_seqno_];
+    # Now do any special paren check
+    if (   $match
+        && $paren_flag
+        && $paren_flag ne '1'
+        && $paren_flag ne '*'
+        && $closing_token eq ')' )
+    {
+        $match &&=
+          $self->match_paren_control_flag( $type_sequence, $paren_flag,
+            $rLL_new );
+    }
 
-    # We define an array of pairs of nested containers
-    my @nested_pairs;
+    # Fix for b1379, b1380, b1381, b1382, b1384 part 1. Mark trailing commas
+    # for use by -vtc logic to avoid instability when -dtc and -atc are both
+    # active.
+    if ($match) {
+        if ( $if_add && $rOpts_delete_trailing_commas
+            || !$if_add && $rOpts_add_trailing_commas )
+        {
+            $self->[_ris_bare_trailing_comma_by_seqno_]->{$type_sequence} = 1;
 
-    # Names of calling routines can either be marked as 'i' or 'w',
-    # and they may invoke a sub call with an '->'. We will consider
-    # any consecutive string of such types as a single unit when making
-    # weld decisions.  We also allow a leading !
-    my $is_name_type = {
-        'i'  => 1,
-        'w'  => 1,
-        'U'  => 1,
-        '->' => 1,
-        '!'  => 1,
-    };
+            # The combination of -atc and -dtc and -cab=3 can be unstable
+            # (b1394). So we deactivate -cab=3 in this case.
+            if ( $rOpts_comma_arrow_breakpoints == 3 ) {
+                $self->[_roverride_cab3_]->{$type_sequence} = 1;
+            }
+        }
+    }
+    return $match;
+}
 
-    # Loop over all closing container tokens
-    foreach my $inner_seqno ( keys %{$K_closing_container} ) {
-        my $K_inner_closing = $K_closing_container->{$inner_seqno};
+sub store_new_token {
 
-        # See if it is immediately followed by another, outer closing token
-        my $K_outer_closing = $K_inner_closing + 1;
-        $K_outer_closing += 1
-          if ( $K_outer_closing < $Num
-            && $rLL->[$K_outer_closing]->[_TYPE_] eq 'b' );
+    my ( $self, $type, $token, $Kp ) = @_;
 
-        next unless ( $K_outer_closing < $Num );
-        my $outer_seqno = $rLL->[$K_outer_closing]->[_TYPE_SEQUENCE_];
-        next unless ($outer_seqno);
-        my $token_outer_closing = $rLL->[$K_outer_closing]->[_TOKEN_];
-        next unless ( $is_closing_token{$token_outer_closing} );
+    # Create and insert a completely new token into the output stream
 
-        # Now we have to check the opening tokens.
-        my $K_outer_opening = $K_opening_container->{$outer_seqno};
-        my $K_inner_opening = $K_opening_container->{$inner_seqno};
-        next unless defined($K_outer_opening) && defined($K_inner_opening);
+    # Input parameters:
+    #  $type  = the token type
+    #  $token = the token text
+    #  $Kp    = index of the previous token in the new list, $rLL_new
 
-        my $inner_blocktype = $rblock_type_of_seqno->{$inner_seqno};
-        my $outer_blocktype = $rblock_type_of_seqno->{$outer_seqno};
+    # Returns:
+    #  $Knew = index in $rLL_new of the new token
 
-        # Verify that the inner opening token is the next container after the
-        # outer opening token.
-        my $K_io_check = $rLL->[$K_outer_opening]->[_KNEXT_SEQ_ITEM_];
-        next unless defined($K_io_check);
-        if ( $K_io_check != $K_inner_opening ) {
+    # This operation is a little tricky because we are creating a new token and
+    # we have to take care to follow the requested whitespace rules.
 
-            # The inner opening container does not immediately follow the outer
-            # opening container, but we may still allow a weld if they are
-            # separated by a sub signature.  For example, we may have something
-            # like this, where $K_io_check may be at the first 'x' instead of
-            # 'io'.  So we need to hop over the signature and see if we arrive
-            # at 'io'.
+    my $Ktop         = @{$rLL_new} - 1;
+    my $top_is_space = $Ktop >= 0 && $rLL_new->[$Ktop]->[_TYPE_] eq 'b';
+    my $Knew;
+    if ( $top_is_space && $want_left_space{$type} == WS_NO ) {
 
-            #            oo               io
-            #             |     x       x |
-            #   $obj->then( sub ( $code ) {
-            #       ...
-            #       return $c->render(text => '', status => $code);
-            #   } );
-            #   | |
-            #  ic oc
+        #----------------------------------------------------
+        # Method 1: Convert the top blank into the new token.
+        #----------------------------------------------------
 
-            next if ( !$inner_blocktype || $inner_blocktype ne 'sub' );
-            next if $rLL->[$K_io_check]->[_TOKEN_] ne '(';
-            my $seqno_signature = $rLL->[$K_io_check]->[_TYPE_SEQUENCE_];
-            next unless defined($seqno_signature);
-            my $K_signature_closing = $K_closing_container->{$seqno_signature};
-            next unless defined($K_signature_closing);
-            my $K_test = $rLL->[$K_signature_closing]->[_KNEXT_SEQ_ITEM_];
-            next
-              unless ( defined($K_test) && $K_test == $K_inner_opening );
+        # Be Careful: we are working on the top of the new stack, on a token
+        # which has been stored.
 
-            # OK, we have arrived at 'io' in the above diagram.  We should put
-            # a limit on the length or complexity of the signature here.  There
-            # is no perfect way to do this, one way is to put a limit on token
-            # count.  For consistency with older versions, we should allow a
-            # signature with a single variable to weld, but not with
-            # multiple variables.  A single variable as in 'sub ($code) {' can
-            # have a $Kdiff of 2 to 4, depending on spacing.
+        my $rcopy = copy_token_as_type( $rLL_new->[$Ktop], 'b', SPACE );
 
-            # But two variables like 'sub ($v1,$v2) {' can have a diff of 4 to
-            # 7, depending on spacing. So to keep formatting consistent with
-            # previous versions, we will also avoid welding if there is a comma
-            # in the signature.
+        $Knew                               = $Ktop;
+        $rLL_new->[$Knew]->[_TOKEN_]        = $token;
+        $rLL_new->[$Knew]->[_TOKEN_LENGTH_] = length($token);
+        $rLL_new->[$Knew]->[_TYPE_]         = $type;
 
-            my $Kdiff = $K_signature_closing - $K_io_check;
-            next if ( $Kdiff > 4 );
+        # NOTE: we are changing the output stack without updating variables
+        # $last_nonblank_code_type, etc. Future needs might require that
+        # those variables be updated here.  For now, we just update the
+        # type counts as necessary.
 
-            my $saw_comma;
-            foreach my $KK ( $K_io_check + 1 .. $K_signature_closing - 1 ) {
-                if ( $rLL->[$KK]->[_TYPE_] eq ',' ) { $saw_comma = 1; last }
+        if ( $is_counted_type{$type} ) {
+            my $seqno = $seqno_stack{ $depth_next - 1 };
+            if ($seqno) {
+                $self->[_rtype_count_by_seqno_]->{$seqno}->{$type}++;
             }
-            next if ($saw_comma);
         }
 
-        # Yes .. this is a possible nesting pair.
-        # They can be separated by a small amount.
-        my $K_diff = $K_inner_opening - $K_outer_opening;
-
-        # Count nonblank characters separating them.
-        if ( $K_diff < 0 ) { next }    # Shouldn't happen
-        my $nonblank_count = 0;
-        my $type;
-        my $is_name;
-
-        # Here is an example of a long identifier chain which counts as a
-        # single nonblank here (this spans about 10 K indexes):
-        #     if ( !Boucherot::SetOfConnections->new->handler->execute(
-        #        ^--K_o_o                                             ^--K_i_o
-        #       @array) )
-        my $Kn_first = $K_outer_opening;
-        my $Kn_last_nonblank;
-        my $saw_comment;
-        foreach my $Kn ( $K_outer_opening + 1 .. $K_inner_opening ) {
-            next if ( $rLL->[$Kn]->[_TYPE_] eq 'b' );
-            if ( !$nonblank_count )        { $Kn_first = $Kn }
-            if ( $Kn eq $K_inner_opening ) { $nonblank_count++; last; }
-            $Kn_last_nonblank = $Kn;
+        # Then store a new blank
+        $self->store_token($rcopy);
+    }
+    else {
 
-            # skip chain of identifier tokens
-            my $last_type    = $type;
-            my $last_is_name = $is_name;
-            $type = $rLL->[$Kn]->[_TYPE_];
-            if ( $type eq '#' ) { $saw_comment = 1; last }
-            $is_name = $is_name_type->{$type};
-            next if ( $is_name && $last_is_name );
+        #----------------------------------------
+        # Method 2: Use the normal storage method
+        #----------------------------------------
 
-            $nonblank_count++;
-            last if ( $nonblank_count > 2 );
+        # Patch for issue c078: keep line indexes in order.  If the top
+        # token is a space that we are keeping (due to '-wls=...) then
+        # we have to check that old line indexes stay in order.
+        # In very rare
+        # instances in which side comments have been deleted and converted
+        # into blanks, we may have filtered down multiple blanks into just
+        # one. In that case the top blank may have a higher line number
+        # than the previous nonblank token. Although the line indexes of
+        # blanks are not really significant, we need to keep them in order
+        # in order to pass error checks.
+        if ($top_is_space) {
+            my $old_top_ix = $rLL_new->[$Ktop]->[_LINE_INDEX_];
+            my $new_top_ix = $rLL_new->[$Kp]->[_LINE_INDEX_];
+            if ( $new_top_ix < $old_top_ix ) {
+                $rLL_new->[$Ktop]->[_LINE_INDEX_] = $new_top_ix;
+            }
         }
 
-        # Do not weld across a comment .. fix for c058.
-        next if ($saw_comment);
-
-        # Patch for b1104: do not weld to a paren preceded by sort/map/grep
-        # because the special line break rules may cause a blinking state
-        if (   defined($Kn_last_nonblank)
-            && $rLL->[$K_inner_opening]->[_TOKEN_] eq '('
-            && $rLL->[$Kn_last_nonblank]->[_TYPE_] eq 'k' )
-        {
-            my $token = $rLL->[$Kn_last_nonblank]->[_TOKEN_];
+        my $rcopy = copy_token_as_type( $rLL_new->[$Kp], $type, $token );
+        $self->store_token($rcopy);
+        $Knew = @{$rLL_new} - 1;
+    }
+    return $Knew;
+} ## end sub store_new_token
 
-            # Turn off welding at sort/map/grep (
-            if ( $is_sort_map_grep{$token} ) { $nonblank_count = 10 }
-        }
+sub check_Q {
 
-        if (
+    # Check that a quote looks okay, and report possible problems
+    # to the logfile.
 
-            # adjacent opening containers, like: do {{
-            $nonblank_count == 1
+    my ( $self, $KK, $Kfirst, $line_number ) = @_;
+    my $token = $rLL->[$KK]->[_TOKEN_];
+    if ( $token =~ /\t/ ) {
+        $self->note_embedded_tab($line_number);
+    }
 
-            # short item following opening paren, like:  fun( yyy (
-            || (   $nonblank_count == 2
-                && $rLL->[$K_outer_opening]->[_TOKEN_] eq '(' )
+    # The remainder of this routine looks for something like
+    #        '$var = s/xxx/yyy/;'
+    # in case it should have been '$var =~ s/xxx/yyy/;'
 
-            # anonymous sub + prototype or sig:  )->then( sub ($code) {
-            # ... but it seems best not to stack two structural blocks, like
-            # this
-            #    sub make_anon_with_my_sub { sub {
-            # because it probably hides the structure a little too much.
-            || (   $inner_blocktype
-                && $inner_blocktype eq 'sub'
-                && $rLL->[$Kn_first]->[_TOKEN_] eq 'sub'
-                && !$outer_blocktype )
-          )
-        {
-            push @nested_pairs,
-              [ $inner_seqno, $outer_seqno, $K_inner_closing ];
-        }
-        next;
-    }
+    # Start by looking for a token beginning with one of: s y m / tr
+    return
+      unless ( $is_s_y_m_slash{ substr( $token, 0, 1 ) }
+        || substr( $token, 0, 2 ) eq 'tr' );
 
-    # The weld routine expects the pairs in order in the form
-    #   [$seqno_inner, $seqno_outer]
-    # And they must be in the same order as the inner closing tokens
-    # (otherwise, welds of three or more adjacent tokens will not work).  The K
-    # value of this inner closing token has temporarily been stored for
-    # sorting.
-    @nested_pairs =
+    # ... and preceded by one of: = == !=
+    my $Kp = $self->K_previous_nonblank( undef, $rLL_new );
+    return unless ( defined($Kp) );
+    my $previous_nonblank_type = $rLL_new->[$Kp]->[_TYPE_];
+    return unless ( $is_unexpected_equals{$previous_nonblank_type} );
+    my $previous_nonblank_token = $rLL_new->[$Kp]->[_TOKEN_];
 
-      # Drop the K index after sorting (it would cause trouble downstream)
-      map { [ $_->[0], $_->[1] ] }
+    my $previous_nonblank_type_2  = 'b';
+    my $previous_nonblank_token_2 = EMPTY_STRING;
+    my $Kpp                       = $self->K_previous_nonblank( $Kp, $rLL_new );
+    if ( defined($Kpp) ) {
+        $previous_nonblank_type_2  = $rLL_new->[$Kpp]->[_TYPE_];
+        $previous_nonblank_token_2 = $rLL_new->[$Kpp]->[_TOKEN_];
+    }
 
-      # Sort on the K values
-      sort { $a->[2] <=> $b->[2] } @nested_pairs;
+    my $next_nonblank_token = EMPTY_STRING;
+    my $Kn                  = $KK + 1;
+    my $Kmax                = @{$rLL} - 1;
+    if ( $Kn <= $Kmax && $rLL->[$Kn]->[_TYPE_] eq 'b' ) { $Kn += 1 }
+    if ( $Kn <= $Kmax ) {
+        $next_nonblank_token = $rLL->[$Kn]->[_TOKEN_];
+    }
 
-    return \@nested_pairs;
-} ## end sub find_nested_pairs
+    my $token_0 = $rLL->[$Kfirst]->[_TOKEN_];
+    my $type_0  = $rLL->[$Kfirst]->[_TYPE_];
 
-sub match_paren_flag {
+    if (
 
-    # Decide if this paren is excluded by user request:
-    #   undef matches no parens
-    #   '*' matches all parens
-    #   'k' matches only if the previous nonblank token is a perl builtin
-    #       keyword (such as 'if', 'while'),
-    #   'K' matches if 'k' does not, meaning if the previous token is not a
-    #       keyword.
-    #   'f' matches if the previous token is a function other than a keyword.
-    #   'F' matches if 'f' does not.
-    #   'w' matches if either 'k' or 'f' match.
-    #   'W' matches if 'w' does not.
-    my ( $self, $KK, $flag ) = @_;
+        # preceded by simple scalar
+        $previous_nonblank_type_2 eq 'i'
+        && $previous_nonblank_token_2 =~ /^\$/
 
-    return 0 unless ( defined($flag) );
-    return 0 if $flag eq '0';
-    return 1 if $flag eq '1';
-    return 1 if $flag eq '*';
-    return 0 unless ( defined($KK) );
+        # followed by some kind of termination
+        # (but give complaint if we can not see far enough ahead)
+        && $next_nonblank_token =~ /^[; \)\}]$/
 
-    my $rLL         = $self->[_rLL_];
-    my $rtoken_vars = $rLL->[$KK];
-    my $seqno       = $rtoken_vars->[_TYPE_SEQUENCE_];
-    return 0 unless ($seqno);
-    my $token     = $rtoken_vars->[_TOKEN_];
-    my $K_opening = $KK;
-    if ( !$is_opening_token{$token} ) {
-        $K_opening = $self->[_K_opening_container_]->{$seqno};
+        # scalar is not declared
+        ##                      =~ /^(my|our|local)$/
+        && !( $type_0 eq 'k' && $is_my_our_local{$token_0} )
+      )
+    {
+        my $lno   = 1 + $rLL_new->[$Kp]->[_LINE_INDEX_];
+        my $guess = substr( $previous_nonblank_token, 0, 1 ) . '~';
+        complain(
+"Line $lno: Note: be sure you want '$previous_nonblank_token' instead of '$guess' here\n"
+        );
     }
-    return unless ( defined($K_opening) );
+    return;
+} ## end sub check_Q
 
-    my ( $is_f, $is_k, $is_w );
-    my $Kp = $self->K_previous_nonblank($K_opening);
-    if ( defined($Kp) ) {
-        my $type_p = $rLL->[$Kp]->[_TYPE_];
+} ## end closure respace_tokens
 
-        # keyword?
-        $is_k = $type_p eq 'k';
+sub copy_token_as_type {
 
-        # function call?
+    # This provides a quick way to create a new token by
+    # slightly modifying an existing token.
+    my ( $rold_token, $type, $token ) = @_;
+    if ( !defined($token) ) {
+        if ( $type eq 'b' ) {
+            $token = SPACE;
+        }
+        elsif ( $type eq 'q' ) {
+            $token = EMPTY_STRING;
+        }
+        elsif ( $type eq '->' ) {
+            $token = '->';
+        }
+        elsif ( $type eq ';' ) {
+            $token = ';';
+        }
+        elsif ( $type eq ',' ) {
+            $token = ',';
+        }
+        else {
+
+            # Unexpected type ... this sub will work as long as both $token and
+            # $type are defined, but we should catch any unexpected types during
+            # development.
+            if (DEVEL_MODE) {
+                Fault(<<EOM);
+sub 'copy_token_as_type' received token type '$type' but expects just one of: 'b' 'q' '->' or ';'
+EOM
+            }
+
+            # Shouldn't get here
+            $token = $type;
+        }
+    }
+
+    my @rnew_token = @{$rold_token};
+    $rnew_token[_TYPE_]          = $type;
+    $rnew_token[_TOKEN_]         = $token;
+    $rnew_token[_TYPE_SEQUENCE_] = EMPTY_STRING;
+    return \@rnew_token;
+} ## end sub copy_token_as_type
+
+sub K_next_code {
+    my ( $self, $KK, $rLL ) = @_;
+
+    # return the index K of the next nonblank, non-comment token
+    return unless ( defined($KK) && $KK >= 0 );
+
+    # use the standard array unless given otherwise
+    $rLL = $self->[_rLL_] unless ( defined($rLL) );
+    my $Num  = @{$rLL};
+    my $Knnb = $KK + 1;
+    while ( $Knnb < $Num ) {
+        if ( !defined( $rLL->[$Knnb] ) ) {
+
+            # We seem to have encountered a gap in our array.
+            # This shouldn't happen because sub write_line() pushed
+            # items into the $rLL array.
+            Fault("Undefined entry for k=$Knnb") if (DEVEL_MODE);
+            return;
+        }
+        if (   $rLL->[$Knnb]->[_TYPE_] ne 'b'
+            && $rLL->[$Knnb]->[_TYPE_] ne '#' )
+        {
+            return $Knnb;
+        }
+        $Knnb++;
+    }
+    return;
+} ## end sub K_next_code
+
+sub K_next_nonblank {
+    my ( $self, $KK, $rLL ) = @_;
+
+    # return the index K of the next nonblank token, or
+    # return undef if none
+    return unless ( defined($KK) && $KK >= 0 );
+
+    # The third arg allows this routine to be used on any array.  This is
+    # useful in sub respace_tokens when we are copying tokens from an old $rLL
+    # to a new $rLL array.  But usually the third arg will not be given and we
+    # will just use the $rLL array in $self.
+    $rLL = $self->[_rLL_] unless ( defined($rLL) );
+    my $Num  = @{$rLL};
+    my $Knnb = $KK + 1;
+    return unless ( $Knnb < $Num );
+    return $Knnb if ( $rLL->[$Knnb]->[_TYPE_] ne 'b' );
+    return unless ( ++$Knnb < $Num );
+    return $Knnb if ( $rLL->[$Knnb]->[_TYPE_] ne 'b' );
+
+    # Backup loop. Very unlikely to get here; it means we have neighboring
+    # blanks in the token stream.
+    $Knnb++;
+    while ( $Knnb < $Num ) {
+
+        # Safety check, this fault shouldn't happen:  The $rLL array is the
+        # main array of tokens, so all entries should be used.  It is
+        # initialized in sub write_line, and then re-initialized by sub
+        # store_token() within sub respace_tokens.  Tokens are pushed on
+        # so there shouldn't be any gaps.
+        if ( !defined( $rLL->[$Knnb] ) ) {
+            Fault("Undefined entry for k=$Knnb") if (DEVEL_MODE);
+            return;
+        }
+        if ( $rLL->[$Knnb]->[_TYPE_] ne 'b' ) { return $Knnb }
+        $Knnb++;
+    }
+    return;
+} ## end sub K_next_nonblank
+
+sub K_previous_code {
+
+    # return the index K of the previous nonblank, non-comment token
+    # Call with $KK=undef to start search at the top of the array
+    my ( $self, $KK, $rLL ) = @_;
+
+    # use the standard array unless given otherwise
+    $rLL = $self->[_rLL_] unless ( defined($rLL) );
+    my $Num = @{$rLL};
+    if    ( !defined($KK) ) { $KK = $Num }
+    elsif ( $KK > $Num ) {
+
+        # This fault can be caused by a programming error in which a bad $KK is
+        # given.  The caller should make the first call with KK_new=undef to
+        # avoid this error.
+        Fault(
+"Program Bug: K_previous_nonblank_new called with K=$KK which exceeds $Num"
+        ) if (DEVEL_MODE);
+        return;
+    }
+    my $Kpnb = $KK - 1;
+    while ( $Kpnb >= 0 ) {
+        if (   $rLL->[$Kpnb]->[_TYPE_] ne 'b'
+            && $rLL->[$Kpnb]->[_TYPE_] ne '#' )
+        {
+            return $Kpnb;
+        }
+        $Kpnb--;
+    }
+    return;
+} ## end sub K_previous_code
+
+sub K_previous_nonblank {
+
+    # return index of previous nonblank token before item K;
+    # Call with $KK=undef to start search at the top of the array
+    my ( $self, $KK, $rLL ) = @_;
+
+    # use the standard array unless given otherwise
+    $rLL = $self->[_rLL_] unless ( defined($rLL) );
+    my $Num = @{$rLL};
+    if    ( !defined($KK) ) { $KK = $Num }
+    elsif ( $KK > $Num ) {
+
+        # This fault can be caused by a programming error in which a bad $KK is
+        # given.  The caller should make the first call with KK_new=undef to
+        # avoid this error.
+        Fault(
+"Program Bug: K_previous_nonblank_new called with K=$KK which exceeds $Num"
+        ) if (DEVEL_MODE);
+        return;
+    }
+    my $Kpnb = $KK - 1;
+    return unless ( $Kpnb >= 0 );
+    return $Kpnb if ( $rLL->[$Kpnb]->[_TYPE_] ne 'b' );
+    return unless ( --$Kpnb >= 0 );
+    return $Kpnb if ( $rLL->[$Kpnb]->[_TYPE_] ne 'b' );
+
+    # Backup loop. We should not get here unless some routine
+    # slipped repeated blanks into the token stream.
+    return unless ( --$Kpnb >= 0 );
+    while ( $Kpnb >= 0 ) {
+        if ( $rLL->[$Kpnb]->[_TYPE_] ne 'b' ) { return $Kpnb }
+        $Kpnb--;
+    }
+    return;
+} ## end sub K_previous_nonblank
+
+sub parent_seqno_by_K {
+
+    # Return the sequence number of the parent container of token K, if any.
+
+    my ( $self, $KK ) = @_;
+    my $rLL = $self->[_rLL_];
+
+    # The task is to jump forward to the next container token
+    # and use the sequence number of either it or its parent.
+
+    # For example, consider the following with seqno=5 of the '[' and ']'
+    # being called with index K of the first token of each line:
+
+    #                                              # result
+    #    push @tests,                              # -
+    #      [                                       # -
+    #        sub { 99 },   'do {&{%s} for 1,2}',   # 5
+    #        '(&{})(&{})', undef,                  # 5
+    #        [ 2, 2, 0 ],  0                       # 5
+    #      ];                                      # -
+
+    # NOTE: The ending parent will be SEQ_ROOT for a balanced file.  For
+    # unbalanced files, last sequence number will either be undefined or it may
+    # be at a deeper level.  In either case we will just return SEQ_ROOT to
+    # have a defined value and allow formatting to proceed.
+    my $parent_seqno  = SEQ_ROOT;
+    my $type_sequence = $rLL->[$KK]->[_TYPE_SEQUENCE_];
+    if ($type_sequence) {
+        $parent_seqno = $self->[_rparent_of_seqno_]->{$type_sequence};
+    }
+    else {
+        my $Kt = $rLL->[$KK]->[_KNEXT_SEQ_ITEM_];
+        if ( defined($Kt) ) {
+            $type_sequence = $rLL->[$Kt]->[_TYPE_SEQUENCE_];
+            my $type = $rLL->[$Kt]->[_TYPE_];
+
+            # if next container token is closing, it is the parent seqno
+            if ( $is_closing_type{$type} ) {
+                $parent_seqno = $type_sequence;
+            }
+
+            # otherwise we want its parent container
+            else {
+                $parent_seqno = $self->[_rparent_of_seqno_]->{$type_sequence};
+            }
+        }
+    }
+    $parent_seqno = SEQ_ROOT unless ( defined($parent_seqno) );
+    return $parent_seqno;
+} ## end sub parent_seqno_by_K
+
+sub is_in_block_by_i {
+    my ( $self, $i ) = @_;
+
+    # returns true if
+    #     token at i is contained in a BLOCK
+    #     or is at root level
+    #     or there is some kind of error (i.e. unbalanced file)
+    # returns false otherwise
+
+    if ( $i < 0 ) {
+        DEVEL_MODE && Fault("Bad call, i='$i'\n");
+        return 1;
+    }
+
+    my $seqno = $parent_seqno_to_go[$i];
+    return 1 if ( !$seqno || $seqno eq SEQ_ROOT );
+    return 1 if ( $self->[_rblock_type_of_seqno_]->{$seqno} );
+    return;
+} ## end sub is_in_block_by_i
+
+sub is_in_list_by_i {
+    my ( $self, $i ) = @_;
+
+    # returns true if token at i is contained in a LIST
+    # returns false otherwise
+    my $seqno = $parent_seqno_to_go[$i];
+    return unless ( $seqno && $seqno ne SEQ_ROOT );
+    if ( $self->[_ris_list_by_seqno_]->{$seqno} ) {
+        return 1;
+    }
+    return;
+} ## end sub is_in_list_by_i
+
+sub is_list_by_K {
+
+    # Return true if token K is in a list
+    my ( $self, $KK ) = @_;
+
+    my $parent_seqno = $self->parent_seqno_by_K($KK);
+    return unless defined($parent_seqno);
+    return $self->[_ris_list_by_seqno_]->{$parent_seqno};
+}
+
+sub is_list_by_seqno {
+
+    # Return true if the immediate contents of a container appears to be a
+    # list.
+    my ( $self, $seqno ) = @_;
+    return unless defined($seqno);
+    return $self->[_ris_list_by_seqno_]->{$seqno};
+}
+
+sub resync_lines_and_tokens {
+
+    my $self = shift;
+
+    # Re-construct the arrays of tokens associated with the original input
+    # lines since they have probably changed due to inserting and deleting
+    # blanks and a few other tokens.
+
+    # Return paremeters:
+    # set severe_error = true if processing needs to terminate
+    my $severe_error;
+    my $rqw_lines = [];
+
+    my $rLL    = $self->[_rLL_];
+    my $Klimit = $self->[_Klimit_];
+    my $rlines = $self->[_rlines_];
+    my @Krange_code_without_comments;
+    my @Klast_valign_code;
+
+    # This is the next token and its line index:
+    my $Knext = 0;
+    my $Kmax  = defined($Klimit) ? $Klimit : -1;
+
+    # Verify that old line indexes are in still order.  If this error occurs,
+    # check locations where sub 'respace_tokens' creates new tokens (like
+    # blank spaces).  It must have set a bad old line index.
+    if ( DEVEL_MODE && defined($Klimit) ) {
+        my $iline = $rLL->[0]->[_LINE_INDEX_];
+        foreach my $KK ( 1 .. $Klimit ) {
+            my $iline_last = $iline;
+            $iline = $rLL->[$KK]->[_LINE_INDEX_];
+            if ( $iline < $iline_last ) {
+                my $KK_m    = $KK - 1;
+                my $token_m = $rLL->[$KK_m]->[_TOKEN_];
+                my $token   = $rLL->[$KK]->[_TOKEN_];
+                my $type_m  = $rLL->[$KK_m]->[_TYPE_];
+                my $type    = $rLL->[$KK]->[_TYPE_];
+                Fault(<<EOM);
+Line indexes out of order at index K=$KK:
+at KK-1 =$KK_m: old line=$iline_last, type='$type_m', token='$token_m'
+at KK   =$KK: old line=$iline, type='$type', token='$token',
+EOM
+            }
+        }
+    }
+
+    my $iline = -1;
+    foreach my $line_of_tokens ( @{$rlines} ) {
+        $iline++;
+        my $line_type = $line_of_tokens->{_line_type};
+        if ( $line_type eq 'CODE' ) {
+
+            # Get the old number of tokens on this line
+            my $rK_range_old = $line_of_tokens->{_rK_range};
+            my ( $Kfirst_old, $Klast_old ) = @{$rK_range_old};
+            my $Kdiff_old = 0;
+            if ( defined($Kfirst_old) ) {
+                $Kdiff_old = $Klast_old - $Kfirst_old;
+            }
+
+            # Find the range of NEW K indexes for the line:
+            # $Kfirst = index of first token on line
+            # $Klast  = index of last token on line
+            my ( $Kfirst, $Klast );
+
+            my $Knext_beg = $Knext;    # this will be $Kfirst if we find tokens
+
+            # Optimization: Although the actual K indexes may be completely
+            # changed after respacing, the number of tokens on any given line
+            # will often be nearly unchanged.  So we will see if we can start
+            # our search by guessing that the new line has the same number
+            # of tokens as the old line.
+            my $Knext_guess = $Knext + $Kdiff_old;
+            if (   $Knext_guess > $Knext
+                && $Knext_guess < $Kmax
+                && $rLL->[$Knext_guess]->[_LINE_INDEX_] <= $iline )
+            {
+
+                # the guess is good, so we can start our search here
+                $Knext = $Knext_guess + 1;
+            }
+
+            while ($Knext <= $Kmax
+                && $rLL->[$Knext]->[_LINE_INDEX_] <= $iline )
+            {
+                $Knext++;
+            }
+
+            if ( $Knext > $Knext_beg ) {
+
+                $Klast = $Knext - 1;
+
+                # Delete any terminal blank token
+                if ( $rLL->[$Klast]->[_TYPE_] eq 'b' ) { $Klast -= 1 }
+
+                if ( $Klast < $Knext_beg ) {
+                    $Klast = undef;
+                }
+                else {
+
+                    $Kfirst = $Knext_beg;
+
+                    # Save ranges of non-comment code. This will be used by
+                    # sub keep_old_line_breaks.
+                    if ( $rLL->[$Kfirst]->[_TYPE_] ne '#' ) {
+                        push @Krange_code_without_comments, [ $Kfirst, $Klast ];
+                    }
+
+                    # Only save ending K indexes of code types which are blank
+                    # or 'VER'.  These will be used for a convergence check.
+                    # See related code in sub 'convey_batch_to_vertical_aligner'
+                    my $CODE_type = $line_of_tokens->{_code_type};
+                    if (  !$CODE_type
+                        || $CODE_type eq 'VER' )
+                    {
+                        push @Klast_valign_code, $Klast;
+                    }
+                }
+            }
+
+            # It is only safe to trim the actual line text if the input
+            # line had a terminal blank token. Otherwise, we may be
+            # in a quote.
+            if ( $line_of_tokens->{_ended_in_blank_token} ) {
+                $line_of_tokens->{_line_text} =~ s/\s+$//;
+            }
+            $line_of_tokens->{_rK_range} = [ $Kfirst, $Klast ];
+
+            # Deleting semicolons can create new empty code lines
+            # which should be marked as blank
+            if ( !defined($Kfirst) ) {
+                my $CODE_type = $line_of_tokens->{_code_type};
+                if ( !$CODE_type ) {
+                    $line_of_tokens->{_code_type} = 'BL';
+                }
+            }
+            else {
+
+                #---------------------------------------------------
+                # save indexes of all lines with a 'q' at either end
+                # for later use by sub find_multiline_qw
+                #---------------------------------------------------
+                if (   $rLL->[$Kfirst]->[_TYPE_] eq 'q'
+                    || $rLL->[$Klast]->[_TYPE_] eq 'q' )
+                {
+                    push @{$rqw_lines}, $iline;
+                }
+            }
+        }
+    }
+
+    # There shouldn't be any nodes beyond the last one.  This routine is
+    # relinking lines and tokens after the tokens have been respaced.  A fault
+    # here indicates some kind of bug has been introduced into the above loops.
+    # There is not good way to keep going; we better stop here.
+    if ( $Knext <= $Kmax ) {
+        Fault_Warn(
+            "unexpected tokens at end of file when reconstructing lines");
+        $severe_error = 1;
+        return ( $severe_error, $rqw_lines );
+    }
+    $self->[_rKrange_code_without_comments_] = \@Krange_code_without_comments;
+
+    # Setup the convergence test in the FileWriter based on line-ending indexes
+    my $file_writer_object = $self->[_file_writer_object_];
+    $file_writer_object->setup_convergence_test( \@Klast_valign_code );
+
+    # Mark essential old breakpoints if combination -iob -lp is used.  These
+    # two options do not work well together, but we can avoid turning -iob off
+    # by ignoring -iob at certain essential line breaks.
+    # Fixes cases b1021 b1023 b1034 b1048 b1049 b1050 b1056 b1058
+    if ( $rOpts_ignore_old_breakpoints && $rOpts_line_up_parentheses ) {
+        my %is_assignment_or_fat_comma = %is_assignment;
+        $is_assignment_or_fat_comma{'=>'} = 1;
+        my $ris_essential_old_breakpoint =
+          $self->[_ris_essential_old_breakpoint_];
+        my ( $Kfirst, $Klast );
+        foreach my $line_of_tokens ( @{$rlines} ) {
+            my $line_type = $line_of_tokens->{_line_type};
+            if ( $line_type ne 'CODE' ) {
+                ( $Kfirst, $Klast ) = ( undef, undef );
+                next;
+            }
+            my ( $Kfirst_prev, $Klast_prev ) = ( $Kfirst, $Klast );
+            ( $Kfirst, $Klast ) = @{ $line_of_tokens->{_rK_range} };
+
+            next unless defined($Klast_prev);
+            next unless defined($Kfirst);
+            my $type_last  = $rLL->[$Klast_prev]->[_TOKEN_];
+            my $type_first = $rLL->[$Kfirst]->[_TOKEN_];
+            next
+              unless ( $is_assignment_or_fat_comma{$type_last}
+                || $is_assignment_or_fat_comma{$type_first} );
+            $ris_essential_old_breakpoint->{$Klast_prev} = 1;
+        }
+    }
+    return ( $severe_error, $rqw_lines );
+} ## end sub resync_lines_and_tokens
+
+sub keep_old_line_breaks {
+
+    # Called once per file to find and mark any old line breaks which
+    # should be kept.  We will be translating the input hashes into
+    # token indexes.
+
+    # A flag is set as follows:
+    # = 1 make a hard break (flush the current batch)
+    #     best for something like leading commas (-kbb=',')
+    # = 2 make a soft break (keep building current batch)
+    #     best for something like leading ->
+
+    my ($self) = @_;
+
+    my $rLL = $self->[_rLL_];
+    my $rKrange_code_without_comments =
+      $self->[_rKrange_code_without_comments_];
+    my $rbreak_before_Kfirst = $self->[_rbreak_before_Kfirst_];
+    my $rbreak_after_Klast   = $self->[_rbreak_after_Klast_];
+    my $rwant_container_open = $self->[_rwant_container_open_];
+    my $K_opening_container  = $self->[_K_opening_container_];
+    my $ris_broken_container = $self->[_ris_broken_container_];
+    my $ris_list_by_seqno    = $self->[_ris_list_by_seqno_];
+
+    # This code moved here from sub break_lists to fix b1120
+    if ( $rOpts->{'break-at-old-method-breakpoints'} ) {
+        foreach my $item ( @{$rKrange_code_without_comments} ) {
+            my ( $Kfirst, $Klast ) = @{$item};
+            my $type  = $rLL->[$Kfirst]->[_TYPE_];
+            my $token = $rLL->[$Kfirst]->[_TOKEN_];
+
+            # leading '->' use a value of 2 which causes a soft
+            # break rather than a hard break
+            if ( $type eq '->' ) {
+                $rbreak_before_Kfirst->{$Kfirst} = 2;
+            }
+
+            # leading ')->' use a special flag to insure that both
+            # opening and closing parens get opened
+            # Fix for b1120: only for parens, not braces
+            elsif ( $token eq ')' ) {
+                my $Kn = $self->K_next_nonblank($Kfirst);
+                next
+                  unless ( defined($Kn)
+                    && $Kn <= $Klast
+                    && $rLL->[$Kn]->[_TYPE_] eq '->' );
+                my $seqno = $rLL->[$Kfirst]->[_TYPE_SEQUENCE_];
+                next unless ($seqno);
+
+                # Note: in previous versions there was a fix here to avoid
+                # instability between conflicting -bom and -pvt or -pvtc flags.
+                # The fix skipped -bom for a small line difference.  But this
+                # was troublesome, and instead the fix has been moved to
+                # sub set_vertical_tightness_flags where priority is given to
+                # the -bom flag over -pvt and -pvtc flags.  Both opening and
+                # closing paren flags are involved because even though -bom only
+                # requests breaking before the closing paren, automated logic
+                # opens the opening paren when the closing paren opens.
+                # Relevant cases are b977, b1215, b1270, b1303
+
+                $rwant_container_open->{$seqno} = 1;
+            }
+        }
+    }
+
+    return unless ( %keep_break_before_type || %keep_break_after_type );
+
+    my $check_for_break = sub {
+        my ( $KK, $rkeep_break_hash, $rbreak_hash ) = @_;
+        my $seqno = $rLL->[$KK]->[_TYPE_SEQUENCE_];
+
+        # non-container tokens use the type as the key
+        if ( !$seqno ) {
+            my $type = $rLL->[$KK]->[_TYPE_];
+            if ( $rkeep_break_hash->{$type} ) {
+                $rbreak_hash->{$KK} = 1;
+            }
+        }
+
+        # container tokens use the token as the key
+        else {
+            my $token = $rLL->[$KK]->[_TOKEN_];
+            my $flag  = $rkeep_break_hash->{$token};
+            if ($flag) {
+
+                my $match = $flag eq '1' || $flag eq '*';
+
+                # check for special matching codes
+                if ( !$match ) {
+                    if ( $token eq '(' || $token eq ')' ) {
+                        $match =
+                          $self->match_paren_control_flag( $seqno, $flag );
+                    }
+                    elsif ( $token eq '{' || $token eq '}' ) {
+
+                        # These tentative codes 'b' and 'B' for brace types are
+                        # placeholders for possible future brace types. They
+                        # are not documented and may be changed.
+                        my $block_type =
+                          $self->[_rblock_type_of_seqno_]->{$seqno};
+                        if    ( $flag eq 'b' ) { $match = $block_type }
+                        elsif ( $flag eq 'B' ) { $match = !$block_type }
+                        else {
+                            # unknown code - no match
+                        }
+                    }
+                }
+                $rbreak_hash->{$KK} = 1 if ($match);
+            }
+        }
+    };
+
+    foreach my $item ( @{$rKrange_code_without_comments} ) {
+        my ( $Kfirst, $Klast ) = @{$item};
+        $check_for_break->(
+            $Kfirst, \%keep_break_before_type, $rbreak_before_Kfirst
+        );
+        $check_for_break->(
+            $Klast, \%keep_break_after_type, $rbreak_after_Klast
+        );
+    }
+    return;
+} ## end sub keep_old_line_breaks
+
+sub weld_containers {
+
+    # Called once per file to do any welding operations requested by --weld*
+    # flags.
+    my ($self) = @_;
+
+    # This count is used to eliminate needless calls for weld checks elsewhere
+    $total_weld_count = 0;
+
+    return if ( $rOpts->{'indent-only'} );
+    return unless ($rOpts_add_newlines);
+
+    # Important: sub 'weld_cuddled_blocks' must be called before
+    # sub 'weld_nested_containers'. This is because the cuddled option needs to
+    # use the original _LEVEL_ values of containers, but the weld nested
+    # containers changes _LEVEL_ of welded containers.
+
+    # Here is a good test case to be sure that both cuddling and welding
+    # are working and not interfering with each other: <<snippets/ce_wn1.in>>
+
+    #   perltidy -wn -ce
+
+   # if ($BOLD_MATH) { (
+   #     $labels, $comment,
+   #     join( '', '<B>', &make_math( $mode, '', '', $_ ), '</B>' )
+   # ) } else { (
+   #     &process_math_in_latex( $mode, $math_style, $slevel, "\\mbox{$text}" ),
+   #     $after
+   # ) }
+
+    $self->weld_cuddled_blocks() if ( %{$rcuddled_block_types} );
+
+    if ( $rOpts->{'weld-nested-containers'} ) {
+
+        $self->weld_nested_containers();
+
+        $self->weld_nested_quotes();
+    }
+
+    #-------------------------------------------------------------
+    # All welding is done. Finish setting up weld data structures.
+    #-------------------------------------------------------------
+
+    my $rLL                  = $self->[_rLL_];
+    my $rK_weld_left         = $self->[_rK_weld_left_];
+    my $rK_weld_right        = $self->[_rK_weld_right_];
+    my $rweld_len_right_at_K = $self->[_rweld_len_right_at_K_];
+
+    my @K_multi_weld;
+    my @keys = keys %{$rK_weld_right};
+    $total_weld_count = @keys;
+
+    # First pass to process binary welds.
+    # This loop is processed in unsorted order for efficiency.
+    foreach my $Kstart (@keys) {
+        my $Kend = $rK_weld_right->{$Kstart};
+
+        # An error here would be due to an incorrect initialization introduced
+        # in one of the above weld routines, like sub weld_nested.
+        if ( $Kend <= $Kstart ) {
+            Fault("Bad weld link: Kend=$Kend <= Kstart=$Kstart\n")
+              if (DEVEL_MODE);
+            next;
+        }
+
+        # Set weld values for all tokens this welded pair
+        foreach ( $Kstart + 1 .. $Kend ) {
+            $rK_weld_left->{$_} = $Kstart;
+        }
+        foreach my $Kx ( $Kstart .. $Kend - 1 ) {
+            $rK_weld_right->{$Kx} = $Kend;
+            $rweld_len_right_at_K->{$Kx} =
+              $rLL->[$Kend]->[_CUMULATIVE_LENGTH_] -
+              $rLL->[$Kx]->[_CUMULATIVE_LENGTH_];
+        }
+
+        # Remember the leftmost index of welds which continue to the right
+        if ( defined( $rK_weld_right->{$Kend} )
+            && !defined( $rK_weld_left->{$Kstart} ) )
+        {
+            push @K_multi_weld, $Kstart;
+        }
+    }
+
+    # Second pass to process chains of welds (these are rare).
+    # This has to be processed in sorted order.
+    if (@K_multi_weld) {
+        my $Kend = -1;
+        foreach my $Kstart ( sort { $a <=> $b } @K_multi_weld ) {
+
+            # Skip any interior K which was originally missing a left link
+            next if ( $Kstart <= $Kend );
+
+            # Find the end of this chain
+            $Kend = $rK_weld_right->{$Kstart};
+            my $Knext = $rK_weld_right->{$Kend};
+            while ( defined($Knext) ) {
+                $Kend  = $Knext;
+                $Knext = $rK_weld_right->{$Kend};
+            }
+
+            # Set weld values this chain
+            foreach ( $Kstart + 1 .. $Kend ) {
+                $rK_weld_left->{$_} = $Kstart;
+            }
+            foreach my $Kx ( $Kstart .. $Kend - 1 ) {
+                $rK_weld_right->{$Kx} = $Kend;
+                $rweld_len_right_at_K->{$Kx} =
+                  $rLL->[$Kend]->[_CUMULATIVE_LENGTH_] -
+                  $rLL->[$Kx]->[_CUMULATIVE_LENGTH_];
+            }
+        }
+    }
+
+    return;
+} ## end sub weld_containers
+
+sub cumulative_length_before_K {
+    my ( $self, $KK ) = @_;
+    my $rLL = $self->[_rLL_];
+    return ( $KK <= 0 ) ? 0 : $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_];
+}
+
+sub weld_cuddled_blocks {
+    my ($self) = @_;
+
+    # Called once per file to handle cuddled formatting
+
+    my $rK_weld_left         = $self->[_rK_weld_left_];
+    my $rK_weld_right        = $self->[_rK_weld_right_];
+    my $rblock_type_of_seqno = $self->[_rblock_type_of_seqno_];
+
+    # This routine implements the -cb flag by finding the appropriate
+    # closing and opening block braces and welding them together.
+    return unless ( %{$rcuddled_block_types} );
+
+    my $rLL = $self->[_rLL_];
+    return unless ( defined($rLL) && @{$rLL} );
+
+    my $rbreak_container          = $self->[_rbreak_container_];
+    my $ris_cuddled_closing_brace = $self->[_ris_cuddled_closing_brace_];
+    my $K_opening_container       = $self->[_K_opening_container_];
+    my $K_closing_container       = $self->[_K_closing_container_];
+
+    my $is_broken_block = sub {
+
+        # a block is broken if the input line numbers of the braces differ
+        # we can only cuddle between broken blocks
+        my ($seqno) = @_;
+        my $K_opening = $K_opening_container->{$seqno};
+        return unless ( defined($K_opening) );
+        my $K_closing = $K_closing_container->{$seqno};
+        return unless ( defined($K_closing) );
+        return $rbreak_container->{$seqno}
+          || $rLL->[$K_closing]->[_LINE_INDEX_] !=
+          $rLL->[$K_opening]->[_LINE_INDEX_];
+    };
+
+    # A stack to remember open chains at all levels: This is a hash rather than
+    # an array for safety because negative levels can occur in files with
+    # errors.  This allows us to keep processing with negative levels.
+    # $in_chain{$level} = [$chain_type, $type_sequence];
+    my %in_chain;
+    my $CBO = $rOpts->{'cuddled-break-option'};
+
+    # loop over structure items to find cuddled pairs
+    my $level = 0;
+    my $KNEXT = $self->[_K_first_seq_item_];
+    while ( defined($KNEXT) ) {
+        my $KK = $KNEXT;
+        $KNEXT = $rLL->[$KNEXT]->[_KNEXT_SEQ_ITEM_];
+        my $rtoken_vars   = $rLL->[$KK];
+        my $type_sequence = $rtoken_vars->[_TYPE_SEQUENCE_];
+        if ( !$type_sequence ) {
+            next if ( $KK == 0 );    # first token in file may not be container
+
+            # A fault here implies that an error was made in the little loop at
+            # the bottom of sub 'respace_tokens' which set the values of
+            # _KNEXT_SEQ_ITEM_.  Or an error has been introduced in the
+            # loop control lines above.
+            Fault("sequence = $type_sequence not defined at K=$KK")
+              if (DEVEL_MODE);
+            next;
+        }
+
+        # NOTE: we must use the original levels here. They can get changed
+        # by sub 'weld_nested_containers', so this routine must be called
+        # before sub 'weld_nested_containers'.
+        my $last_level = $level;
+        $level = $rtoken_vars->[_LEVEL_];
+
+        if    ( $level < $last_level ) { $in_chain{$last_level} = undef }
+        elsif ( $level > $last_level ) { $in_chain{$level}      = undef }
+
+        # We are only looking at code blocks
+        my $token = $rtoken_vars->[_TOKEN_];
+        my $type  = $rtoken_vars->[_TYPE_];
+        next unless ( $type eq $token );
+
+        if ( $token eq '{' ) {
+
+            my $block_type = $rblock_type_of_seqno->{$type_sequence};
+            if ( !$block_type ) {
+
+                # patch for unrecognized block types which may not be labeled
+                my $Kp = $self->K_previous_nonblank($KK);
+                while ( $Kp && $rLL->[$Kp]->[_TYPE_] eq '#' ) {
+                    $Kp = $self->K_previous_nonblank($Kp);
+                }
+                next unless $Kp;
+                $block_type = $rLL->[$Kp]->[_TOKEN_];
+            }
+            if ( $in_chain{$level} ) {
+
+                # we are in a chain and are at an opening block brace.
+                # See if we are welding this opening brace with the previous
+                # block brace.  Get their identification numbers:
+                my $closing_seqno = $in_chain{$level}->[1];
+                my $opening_seqno = $type_sequence;
+
+                # The preceding block must be on multiple lines so that its
+                # closing brace will start a new line.
+                if ( !$is_broken_block->($closing_seqno) ) {
+                    next unless ( $CBO == 2 );
+                    $rbreak_container->{$closing_seqno} = 1;
+                }
+
+                # We can weld the closing brace to its following word ..
+                my $Ko = $K_closing_container->{$closing_seqno};
+                my $Kon;
+                if ( defined($Ko) ) {
+                    $Kon = $self->K_next_nonblank($Ko);
+                }
+
+                # ..unless it is a comment
+                if ( defined($Kon) && $rLL->[$Kon]->[_TYPE_] ne '#' ) {
+
+                    # OK to weld these two tokens...
+                    $rK_weld_right->{$Ko} = $Kon;
+                    $rK_weld_left->{$Kon} = $Ko;
+
+                    # Set flag that we want to break the next container
+                    # so that the cuddled line is balanced.
+                    $rbreak_container->{$opening_seqno} = 1
+                      if ($CBO);
+
+                    # Remember which braces are cuddled.
+                    # The closing brace is used to set adjusted indentations.
+                    # The opening brace is not yet used but might eventually
+                    # be needed in setting adjusted indentation.
+                    $ris_cuddled_closing_brace->{$closing_seqno} = 1;
+
+                }
+
+            }
+            else {
+
+                # We are not in a chain. Start a new chain if we see the
+                # starting block type.
+                if ( $rcuddled_block_types->{$block_type} ) {
+                    $in_chain{$level} = [ $block_type, $type_sequence ];
+                }
+                else {
+                    $block_type = '*';
+                    $in_chain{$level} = [ $block_type, $type_sequence ];
+                }
+            }
+        }
+        elsif ( $token eq '}' ) {
+            if ( $in_chain{$level} ) {
+
+                # We are in a chain at a closing brace.  See if this chain
+                # continues..
+                my $Knn = $self->K_next_code($KK);
+                next unless $Knn;
+
+                my $chain_type          = $in_chain{$level}->[0];
+                my $next_nonblank_token = $rLL->[$Knn]->[_TOKEN_];
+                if (
+                    $rcuddled_block_types->{$chain_type}->{$next_nonblank_token}
+                  )
+                {
+
+                    # Note that we do not weld yet because we must wait until
+                    # we we are sure that an opening brace for this follows.
+                    $in_chain{$level}->[1] = $type_sequence;
+                }
+                else { $in_chain{$level} = undef }
+            }
+        }
+    }
+    return;
+} ## end sub weld_cuddled_blocks
+
+sub find_nested_pairs {
+    my $self = shift;
+
+    # This routine is called once per file to do preliminary work needed for
+    # the --weld-nested option.  This information is also needed for adding
+    # semicolons.
+
+    my $rLL = $self->[_rLL_];
+    return unless ( defined($rLL) && @{$rLL} );
+    my $Num = @{$rLL};
+
+    my $K_opening_container  = $self->[_K_opening_container_];
+    my $K_closing_container  = $self->[_K_closing_container_];
+    my $rblock_type_of_seqno = $self->[_rblock_type_of_seqno_];
+
+    # We define an array of pairs of nested containers
+    my @nested_pairs;
+
+    # Names of calling routines can either be marked as 'i' or 'w',
+    # and they may invoke a sub call with an '->'. We will consider
+    # any consecutive string of such types as a single unit when making
+    # weld decisions.  We also allow a leading !
+    my $is_name_type = {
+        'i'  => 1,
+        'w'  => 1,
+        'U'  => 1,
+        '->' => 1,
+        '!'  => 1,
+    };
+
+    # Loop over all closing container tokens
+    foreach my $inner_seqno ( keys %{$K_closing_container} ) {
+        my $K_inner_closing = $K_closing_container->{$inner_seqno};
+
+        # See if it is immediately followed by another, outer closing token
+        my $K_outer_closing = $K_inner_closing + 1;
+        $K_outer_closing += 1
+          if ( $K_outer_closing < $Num
+            && $rLL->[$K_outer_closing]->[_TYPE_] eq 'b' );
+
+        next unless ( $K_outer_closing < $Num );
+        my $outer_seqno = $rLL->[$K_outer_closing]->[_TYPE_SEQUENCE_];
+        next unless ($outer_seqno);
+        my $token_outer_closing = $rLL->[$K_outer_closing]->[_TOKEN_];
+        next unless ( $is_closing_token{$token_outer_closing} );
+
+        # Simple filter: No commas or semicolons in the outer container
+        my $rtype_count = $self->[_rtype_count_by_seqno_]->{$outer_seqno};
+        if ($rtype_count) {
+            next if ( $rtype_count->{','} || $rtype_count->{';'} );
+        }
+
+        # Now we have to check the opening tokens.
+        my $K_outer_opening = $K_opening_container->{$outer_seqno};
+        my $K_inner_opening = $K_opening_container->{$inner_seqno};
+        next unless defined($K_outer_opening) && defined($K_inner_opening);
+
+        my $inner_blocktype = $rblock_type_of_seqno->{$inner_seqno};
+        my $outer_blocktype = $rblock_type_of_seqno->{$outer_seqno};
+
+        # Verify that the inner opening token is the next container after the
+        # outer opening token.
+        my $K_io_check = $rLL->[$K_outer_opening]->[_KNEXT_SEQ_ITEM_];
+        next unless defined($K_io_check);
+        if ( $K_io_check != $K_inner_opening ) {
+
+            # The inner opening container does not immediately follow the outer
+            # opening container, but we may still allow a weld if they are
+            # separated by a sub signature.  For example, we may have something
+            # like this, where $K_io_check may be at the first 'x' instead of
+            # 'io'.  So we need to hop over the signature and see if we arrive
+            # at 'io'.
+
+            #            oo               io
+            #             |     x       x |
+            #   $obj->then( sub ( $code ) {
+            #       ...
+            #       return $c->render(text => '', status => $code);
+            #   } );
+            #   | |
+            #  ic oc
+
+            next if ( !$inner_blocktype || $inner_blocktype ne 'sub' );
+            next if $rLL->[$K_io_check]->[_TOKEN_] ne '(';
+            my $seqno_signature = $rLL->[$K_io_check]->[_TYPE_SEQUENCE_];
+            next unless defined($seqno_signature);
+            my $K_signature_closing = $K_closing_container->{$seqno_signature};
+            next unless defined($K_signature_closing);
+            my $K_test = $rLL->[$K_signature_closing]->[_KNEXT_SEQ_ITEM_];
+            next
+              unless ( defined($K_test) && $K_test == $K_inner_opening );
+
+            # OK, we have arrived at 'io' in the above diagram.  We should put
+            # a limit on the length or complexity of the signature here.  There
+            # is no perfect way to do this, one way is to put a limit on token
+            # count.  For consistency with older versions, we should allow a
+            # signature with a single variable to weld, but not with
+            # multiple variables.  A single variable as in 'sub ($code) {' can
+            # have a $Kdiff of 2 to 4, depending on spacing.
+
+            # But two variables like 'sub ($v1,$v2) {' can have a diff of 4 to
+            # 7, depending on spacing. So to keep formatting consistent with
+            # previous versions, we will also avoid welding if there is a comma
+            # in the signature.
+
+            my $Kdiff = $K_signature_closing - $K_io_check;
+            next if ( $Kdiff > 4 );
+
+            # backup comma count test; but we cannot get here with Kdiff<=4
+            my $rtc = $self->[_rtype_count_by_seqno_]->{$seqno_signature};
+            next if ( $rtc && $rtc->{','} );
+        }
+
+        # Yes .. this is a possible nesting pair.
+        # They can be separated by a small amount.
+        my $K_diff = $K_inner_opening - $K_outer_opening;
+
+        # Count the number of nonblank characters separating them.
+        # Note: the $nonblank_count includes the inner opening container
+        # but not the outer opening container, so it will be >= 1.
+        if ( $K_diff < 0 ) { next }    # Shouldn't happen
+        my $nonblank_count = 0;
+        my $type;
+        my $is_name;
+
+        # Here is an example of a long identifier chain which counts as a
+        # single nonblank here (this spans about 10 K indexes):
+        #     if ( !Boucherot::SetOfConnections->new->handler->execute(
+        #        ^--K_o_o                                             ^--K_i_o
+        #       @array) )
+        my $Kn_first = $K_outer_opening;
+        my $Kn_last_nonblank;
+        my $saw_comment;
+
+        foreach my $Kn ( $K_outer_opening + 1 .. $K_inner_opening ) {
+            next if ( $rLL->[$Kn]->[_TYPE_] eq 'b' );
+            if ( !$nonblank_count )        { $Kn_first = $Kn }
+            if ( $Kn eq $K_inner_opening ) { $nonblank_count++; last; }
+            $Kn_last_nonblank = $Kn;
+
+            # skip chain of identifier tokens
+            my $last_type    = $type;
+            my $last_is_name = $is_name;
+            $type = $rLL->[$Kn]->[_TYPE_];
+            if ( $type eq '#' ) { $saw_comment = 1; last }
+            $is_name = $is_name_type->{$type};
+            next if ( $is_name && $last_is_name );
+
+            # do not count a possible leading - of bareword hash key
+            next if ( $type eq 'm' && !$last_type );
+
+            $nonblank_count++;
+            last if ( $nonblank_count > 2 );
+        }
+
+        # Do not weld across a comment .. fix for c058.
+        next if ($saw_comment);
+
+        # Patch for b1104: do not weld to a paren preceded by sort/map/grep
+        # because the special line break rules may cause a blinking state
+        if (   defined($Kn_last_nonblank)
+            && $rLL->[$K_inner_opening]->[_TOKEN_] eq '('
+            && $rLL->[$Kn_last_nonblank]->[_TYPE_] eq 'k' )
+        {
+            my $token = $rLL->[$Kn_last_nonblank]->[_TOKEN_];
+
+            # Turn off welding at sort/map/grep (
+            if ( $is_sort_map_grep{$token} ) { $nonblank_count = 10 }
+        }
+
+        my $token_oo = $rLL->[$K_outer_opening]->[_TOKEN_];
+
+        if (
+
+            # 1: adjacent opening containers, like: do {{
+            $nonblank_count == 1
+
+            # 2. anonymous sub + prototype or sig:  )->then( sub ($code) {
+            # ... but it seems best not to stack two structural blocks, like
+            # this
+            #    sub make_anon_with_my_sub { sub {
+            # because it probably hides the structure a little too much.
+            || (   $inner_blocktype
+                && $inner_blocktype eq 'sub'
+                && $rLL->[$Kn_first]->[_TOKEN_] eq 'sub'
+                && !$outer_blocktype )
+
+            # 3. short item following opening paren, like:  fun( yyy (
+            || $nonblank_count == 2 && $token_oo eq '('
+
+            # 4. weld around fat commas, if requested (git #108), such as
+            #     elf->call_method( method_name_foo => {
+            || (   $type eq '=>'
+                && $nonblank_count <= 3
+                && %weld_fat_comma_rules
+                && $weld_fat_comma_rules{$token_oo} )
+          )
+        {
+            push @nested_pairs,
+              [ $inner_seqno, $outer_seqno, $K_inner_closing ];
+        }
+        next;
+    }
+
+    # The weld routine expects the pairs in order in the form
+    #   [$seqno_inner, $seqno_outer]
+    # And they must be in the same order as the inner closing tokens
+    # (otherwise, welds of three or more adjacent tokens will not work).  The K
+    # value of this inner closing token has temporarily been stored for
+    # sorting.
+    @nested_pairs =
+
+      # Drop the K index after sorting (it would cause trouble downstream)
+      map { [ $_->[0], $_->[1] ] }
+
+      # Sort on the K values
+      sort { $a->[2] <=> $b->[2] } @nested_pairs;
+
+    return \@nested_pairs;
+} ## end sub find_nested_pairs
+
+sub match_paren_control_flag {
+
+    # Decide if this paren is excluded by user request:
+    #   undef matches no parens
+    #   '*' matches all parens
+    #   'k' matches only if the previous nonblank token is a perl builtin
+    #       keyword (such as 'if', 'while'),
+    #   'K' matches if 'k' does not, meaning if the previous token is not a
+    #       keyword.
+    #   'f' matches if the previous token is a function other than a keyword.
+    #   'F' matches if 'f' does not.
+    #   'w' matches if either 'k' or 'f' match.
+    #   'W' matches if 'w' does not.
+    my ( $self, $seqno, $flag, $rLL ) = @_;
+
+    # Input parameters:
+    # $seqno = sequence number of the container (should be paren)
+    # $flag  = the flag which defines what matches
+    # $rLL   = an optional alternate token list needed for respace operations
+    $rLL = $self->[_rLL_] unless ( defined($rLL) );
+
+    return 0 unless ( defined($flag) );
+    return 0 if $flag eq '0';
+    return 1 if $flag eq '1';
+    return 1 if $flag eq '*';
+    return 0 unless ($seqno);
+    my $K_opening = $self->[_K_opening_container_]->{$seqno};
+    return unless ( defined($K_opening) );
+
+    my ( $is_f, $is_k, $is_w );
+    my $Kp = $self->K_previous_nonblank( $K_opening, $rLL );
+    if ( defined($Kp) ) {
+        my $type_p = $rLL->[$Kp]->[_TYPE_];
+
+        # keyword?
+        $is_k = $type_p eq 'k';
+
+        # function call?
         $is_f = $self->[_ris_function_call_paren_]->{$seqno};
 
         # either keyword or function call?
@@ -8526,7 +9536,7 @@ sub match_paren_flag {
     elsif ( $flag eq 'w' ) { $match = $is_w }
     elsif ( $flag eq 'W' ) { $match = !$is_w }
     return $match;
-} ## end sub match_paren_flag
+} ## end sub match_paren_control_flag
 
 sub is_excluded_weld {
 
@@ -8540,7 +9550,8 @@ sub is_excluded_weld {
     my $flag = $is_leading ? $rflags->[0] : $rflags->[1];
     return 0 unless ( defined($flag) );
     return 1 if $flag eq '*';
-    return $self->match_paren_flag( $KK, $flag );
+    my $seqno = $rtoken_vars->[_TYPE_SEQUENCE_];
+    return $self->match_paren_control_flag( $seqno, $flag );
 } ## end sub is_excluded_weld
 
 # hashes to simplify welding logic
@@ -8716,10 +9727,12 @@ sub setup_new_weld_measurements {
     # - Add ';' to fix case b1139
     # - Convert from '$ok_to_weld' to '$new_weld_ok' to fix b1162.
     # - relaxed constraints for b1227
+    # - added skip if type is 'q' for b1349 and b1350 b1351 b1352 b1353
     if (   $starting_ci
         && $rOpts_line_up_parentheses
         && $rOpts_delete_old_whitespace
         && !$rOpts_add_whitespace
+        && $rLL->[$Kinner_opening]->[_TYPE_] ne 'q'
         && defined($Kprev) )
     {
         my $type_first  = $rLL->[$Kfirst]->[_TYPE_];
@@ -8808,6 +9821,8 @@ sub weld_nested_containers {
     my $rblock_type_of_seqno      = $self->[_rblock_type_of_seqno_];
     my $ris_excluded_lp_container = $self->[_ris_excluded_lp_container_];
     my $ris_asub_block            = $self->[_ris_asub_block_];
+    my $rmax_vertical_tightness   = $self->[_rmax_vertical_tightness_];
+
     my $rOpts_asbl = $rOpts->{'opening-anonymous-sub-brace-on-new-line'};
 
     # Find nested pairs of container tokens for any welding.
@@ -8820,29 +9835,6 @@ sub weld_nested_containers {
     # pairs.  But it isn't clear if this is possible because we don't know
     # which sequences might actually start a weld.
 
-    # Setup a hash to avoid instabilities with combination -lp -wn -pvt=2.
-    # We do this by reducing -vt=2 to -vt=1 where there could be a conflict
-    # with welding at the same tokens.
-    # See issues b1338, b1339, b1340, b1341, b1342, b1343.
-    if ($rOpts_line_up_parentheses) {
-
-        # NOTE: just parens for now but this could be applied to all types if
-        # necessary.
-        if ( $opening_vertical_tightness{'('} == 2 ) {
-            my $rreduce_vertical_tightness_by_seqno =
-              $self->[_rreduce_vertical_tightness_by_seqno_];
-            foreach my $item ( @{$rnested_pairs} ) {
-                my ( $inner_seqno, $outer_seqno ) = @{$item};
-                if ( !$ris_excluded_lp_container->{$outer_seqno} ) {
-
-                    # Set a flag which means that if a token has -vt=2
-                    # then reduce it to -vt=1.
-                    $rreduce_vertical_tightness_by_seqno->{$outer_seqno} = 1;
-                }
-            }
-        }
-    }
-
     my $rOpts_break_at_old_method_breakpoints =
       $rOpts->{'break-at-old-method-breakpoints'};
 
@@ -8870,8 +9862,9 @@ sub weld_nested_containers {
     # We use the minimum of two criteria, either of which may be more
     # restrictive.  The 'alpha' value is more restrictive in (b1206, b1252) and
     # the 'beta' value is more restrictive in other cases (b1243).
-
-    my $weld_cutoff_level = min( $stress_level_alpha, $stress_level_beta + 3 );
+    # Reduced beta term from beta+3 to beta+2 to fix b1401. Previously:
+    # my $weld_cutoff_level = min($stress_level_alpha, $stress_level_beta + 2);
+    # This is now '$high_stress_level'.
 
     # The vertical tightness flags can throw off line length calculations.
     # This patch was added to fix instability issue b1284.
@@ -8880,22 +9873,6 @@ sub weld_nested_containers {
     # It may be necessary to include '[' and '{' here in the future.
     my $one_line_tol = $opening_vertical_tightness{'('} ? 1 : 0;
 
-    my $length_to_opening_seqno = sub {
-        my ($seqno) = @_;
-        my $KK      = $K_opening_container->{$seqno};
-        my $lentot  = defined($KK)
-          && $KK > 0 ? $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_] : 0;
-        return $lentot;
-    };
-
-    my $length_to_closing_seqno = sub {
-        my ($seqno) = @_;
-        my $KK      = $K_closing_container->{$seqno};
-        my $lentot  = defined($KK)
-          && $KK > 0 ? $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_] : 0;
-        return $lentot;
-    };
-
     # Abbreviations:
     #  _oo=outer opening, i.e. first of  { {
     #  _io=inner opening, i.e. second of { {
@@ -8940,9 +9917,7 @@ sub weld_nested_containers {
         # RULE: do not weld to a square bracket which does not contain commas
         if ( $inner_opening->[_TYPE_] eq '[' ) {
             my $rtype_count = $self->[_rtype_count_by_seqno_]->{$inner_seqno};
-            next unless ($rtype_count);
-            my $comma_count = $rtype_count->{','};
-            next unless ($comma_count);
+            next unless ( $rtype_count && $rtype_count->{','} );
 
             # Do not weld if there is text before a '[' such as here:
             #      curr_opt ( @beg [2,5] )
@@ -8962,7 +9937,7 @@ sub weld_nested_containers {
         # welds can still be made.  This rule will seldom be a limiting factor
         # in actual working code. Fixes b1206, b1243.
         my $inner_level = $inner_opening->[_LEVEL_];
-        if ( $inner_level >= $weld_cutoff_level ) { next }
+        if ( $inner_level >= $high_stress_level ) { next }
 
         # Set flag saying if this pair starts a new weld
         my $starting_new_weld = !( @welds && $outer_seqno == $welds[-1]->[0] );
@@ -8984,6 +9959,38 @@ sub weld_nested_containers {
         my $token_oo = $outer_opening->[_TOKEN_];
         my $token_io = $inner_opening->[_TOKEN_];
 
+        # DO-NOT-WELD RULE 7: Do not weld if this conflicts with -bom
+        # Added for case b973. Moved here from below to fix b1423.
+        if (  !$do_not_weld_rule
+            && $rOpts_break_at_old_method_breakpoints
+            && $iline_io > $iline_oo )
+        {
+
+            foreach my $iline ( $iline_oo + 1 .. $iline_io ) {
+                my $rK_range = $rlines->[$iline]->{_rK_range};
+                next unless defined($rK_range);
+                my ( $Kfirst, $Klast ) = @{$rK_range};
+                next unless defined($Kfirst);
+                if ( $rLL->[$Kfirst]->[_TYPE_] eq '->' ) {
+                    $do_not_weld_rule = 7;
+                    last;
+                }
+            }
+        }
+        next if ($do_not_weld_rule);
+
+        # Turn off vertical tightness at possible one-line welds.  Fixes b1402,
+        # b1419, b1421, b1424, b1425. This also fixes issues b1338, b1339,
+        # b1340, b1341, b1342, b1343, which previously used a separate fix.
+        # Issue c161 is the latest and simplest check, using
+        # $iline_ic==$iline_io as the test.
+        if (   %opening_vertical_tightness
+            && $iline_ic == $iline_io
+            && $opening_vertical_tightness{$token_oo} )
+        {
+            $rmax_vertical_tightness->{$outer_seqno} = 0;
+        }
+
         my $is_multiline_weld =
              $iline_oo == $iline_io
           && $iline_ic == $iline_oc
@@ -9223,13 +10230,10 @@ EOM
         # instead of -asbl, and this fixed most cases. But it turns out that
         # the real problem was the -asbl flag, and switching to this was
         # necessary to fixe b1268.  This also fixes b1269, b1277, b1278.
-        if (
-            !$do_not_weld_rule
-            ##&& $is_one_line_weld
+        if (  !$do_not_weld_rule
             && $rOpts_line_up_parentheses
             && $rOpts_asbl
-            && $ris_asub_block->{$outer_seqno}
-          )
+            && $ris_asub_block->{$outer_seqno} )
         {
             $do_not_weld_rule = '2A';
         }
@@ -9335,25 +10339,6 @@ EOM
 
         # DO-NOT-WELD RULE 6: This has been merged into RULE 3 above.
 
-        # DO-NOT-WELD RULE 7: Do not weld if this conflicts with -bom
-        # (case b973)
-        if (  !$do_not_weld_rule
-            && $rOpts_break_at_old_method_breakpoints
-            && $iline_io > $iline_oo )
-        {
-
-            foreach my $iline ( $iline_oo + 1 .. $iline_io ) {
-                my $rK_range = $rlines->[$iline]->{_rK_range};
-                next unless defined($rK_range);
-                my ( $Kfirst, $Klast ) = @{$rK_range};
-                next unless defined($Kfirst);
-                if ( $rLL->[$Kfirst]->[_TYPE_] eq '->' ) {
-                    $do_not_weld_rule = 7;
-                    last;
-                }
-            }
-        }
-
         if ($do_not_weld_rule) {
 
             # After neglecting a pair, we start measuring from start of point
@@ -9513,7 +10498,7 @@ sub weld_nested_quotes {
             my $next_type  = $rLL->[$Kn]->[_TYPE_];
             next
               unless ( ( $next_type eq 'q' || $next_type eq 'Q' )
-                && $next_token =~ /^q/ );
+                && substr( $next_token, 0, 1 ) eq 'q' );
 
             # The token before the closing container must also be a quote
             my $Kouter_closing = $K_closing_container->{$outer_seqno};
@@ -9847,7 +10832,7 @@ sub mark_short_nested_blocks {
     return;
 } ## end sub mark_short_nested_blocks
 
-sub adjust_indentation_levels {
+sub special_indentation_adjustments {
 
     my ($self) = @_;
 
@@ -9894,7 +10879,7 @@ sub adjust_indentation_levels {
     $self->clip_adjusted_levels();
 
     return;
-} ## end sub adjust_indentation_levels
+} ## end sub special_indentation_adjustments
 
 sub clip_adjusted_levels {
 
@@ -9903,7 +10888,12 @@ sub clip_adjusted_levels {
     my ($self) = @_;
     my $radjusted_levels = $self->[_radjusted_levels_];
     return unless defined($radjusted_levels) && @{$radjusted_levels};
-    foreach ( @{$radjusted_levels} ) { $_ = 0 if ( $_ < 0 ) }
+    my $min = min( @{$radjusted_levels} );    # fast check for min
+    if ( $min < 0 ) {
+
+        # slow loop, but rarely needed
+        foreach ( @{$radjusted_levels} ) { $_ = 0 if ( $_ < 0 ) }
+    }
     return;
 } ## end sub clip_adjusted_levels
 
@@ -10117,6 +11107,7 @@ sub break_before_list_opening_containers {
         next unless ($break_option);
 
         # Do not use -bbx under stress for stability ... fixes b1300
+        # TODO: review this; do we also need to look at stress_level_lalpha?
         my $level = $rLL->[$KK]->[_LEVEL_];
         if ( $level >= $stress_level_beta ) {
             DEBUG_BBX
@@ -10278,7 +11269,7 @@ sub break_before_list_opening_containers {
         next unless ($ci_flag);
 
         # -bbxi=1: This option removes ci and is handled in
-        # later sub final_indentation_adjustment
+        # later sub get_final_indentation
         if ( $ci_flag == 1 ) {
             $rwant_reduced_ci->{$seqno} = 1;
             next;
@@ -10316,62 +11307,65 @@ sub break_before_list_opening_containers {
             && $rOpts_continuation_indentation > $rOpts_indent_columns );
 
         # Always ok to change ci for permanently broken containers
-        if ( $ris_permanently_broken->{$seqno} ) {
-            goto OK;
-        }
+        if ( $ris_permanently_broken->{$seqno} ) { }
 
         # Always OK if this list contains a broken sub-container with
         # a non-terminal line-ending comma
-        if ($has_list_with_lec) { goto OK }
+        elsif ($has_list_with_lec) { }
+
+        # Otherwise, we are considering a single container...
+        else {
 
-        # From here on we are considering a single container...
+            # A single container must have at least 1 line-ending comma:
+            next unless ( $rlec_count_by_seqno->{$seqno} );
 
-        # A single container must have at least 1 line-ending comma:
-        next unless ( $rlec_count_by_seqno->{$seqno} );
+            my $OK;
 
-        # Since it has a line-ending comma, it will stay broken if the -boc
-        # flag is set
-        if ($rOpts_break_at_old_comma_breakpoints) { goto OK }
+            # Since it has a line-ending comma, it will stay broken if the
+            # -boc flag is set
+            if ($rOpts_break_at_old_comma_breakpoints) { $OK = 1 }
 
-        # OK if the container contains multiple fat commas
-        # Better: multiple lines with fat commas
-        if ( !$rOpts_ignore_old_breakpoints ) {
-            my $rtype_count = $rtype_count_by_seqno->{$seqno};
-            next unless ($rtype_count);
-            my $fat_comma_count = $rtype_count->{'=>'};
-            DEBUG_BBX
-              && print STDOUT "BBX: fat comma count=$fat_comma_count\n";
-            if ( $fat_comma_count && $fat_comma_count >= 2 ) { goto OK }
-        }
-
-        # The last check we can make is to see if this container could fit on a
-        # single line.  Use the least possible indentation estimate, ci=0,
-        # so we are not subtracting $ci * $rOpts_continuation_indentation from
-        # tabulated $maximum_text_length  value.
-        my $maximum_text_length = $maximum_text_length_at_level[$level];
-        my $K_closing           = $K_closing_container->{$seqno};
-        my $length = $self->cumulative_length_before_K($K_closing) -
-          $self->cumulative_length_before_K($KK);
-        my $excess_length = $length - $maximum_text_length;
-        DEBUG_BBX
-          && print STDOUT
+            # OK if the container contains multiple fat commas
+            # Better: multiple lines with fat commas
+            if ( !$OK && !$rOpts_ignore_old_breakpoints ) {
+                my $rtype_count = $rtype_count_by_seqno->{$seqno};
+                next unless ($rtype_count);
+                my $fat_comma_count = $rtype_count->{'=>'};
+                DEBUG_BBX
+                  && print STDOUT "BBX: fat comma count=$fat_comma_count\n";
+                if ( $fat_comma_count && $fat_comma_count >= 2 ) { $OK = 1 }
+            }
+
+            # The last check we can make is to see if this container could
+            # fit on a single line.  Use the least possible indentation
+            # estimate, ci=0, so we are not subtracting $ci *
+            # $rOpts_continuation_indentation from tabulated
+            # $maximum_text_length  value.
+            if ( !$OK ) {
+                my $maximum_text_length = $maximum_text_length_at_level[$level];
+                my $K_closing           = $K_closing_container->{$seqno};
+                my $length = $self->cumulative_length_before_K($K_closing) -
+                  $self->cumulative_length_before_K($KK);
+                my $excess_length = $length - $maximum_text_length;
+                DEBUG_BBX
+                  && print STDOUT
 "BBX: excess=$excess_length: maximum_text_length=$maximum_text_length, length=$length, ci=$ci\n";
 
-        # OK if the net container definitely breaks on length
-        if ( $excess_length > $length_tol ) {
-            DEBUG_BBX
-              && print STDOUT "BBX: excess_length=$excess_length\n";
-            goto OK;
-        }
+                # OK if the net container definitely breaks on length
+                if ( $excess_length > $length_tol ) {
+                    $OK = 1;
+                    DEBUG_BBX
+                      && print STDOUT "BBX: excess_length=$excess_length\n";
+                }
 
-        # Otherwise skip it
-        next;
+                # Otherwise skip it
+                else { next }
+            }
+        }
 
-        #################################################################
+        #------------------------------------------------------------
         # Part 3: Looks OK: apply -bbx=n and any related -bbxi=n flag
-        #################################################################
-
-      OK:
+        #------------------------------------------------------------
 
         DEBUG_BBX && print STDOUT "BBX: OK to break\n";
 
@@ -10508,8 +11502,7 @@ sub extended_ci {
         my $KK = $KNEXT;
         $KNEXT = $rLL->[$KNEXT]->[_KNEXT_SEQ_ITEM_];
 
-        my $seqno     = $rLL->[$KK]->[_TYPE_SEQUENCE_];
-        my $K_opening = $K_opening_container->{$seqno};
+        my $seqno = $rLL->[$KK]->[_TYPE_SEQUENCE_];
 
         # see if we have reached the end of the current controlling container
         if ( $seqno_top && $seqno == $seqno_top ) {
@@ -10539,20 +11532,8 @@ sub extended_ci {
             next;
         }
 
-        # Skip if requested by -bbx to avoid blinkers
-        if ( $rno_xci_by_seqno->{$seqno} ) {
-            next;
-        }
-
-        # Skip if this is a -bli container (this fixes case b1065) Note: case
-        # b1065 is also fixed by the update for b1055, so this update is not
-        # essential now.  But there does not seem to be a good reason to add
-        # xci and bli together, so the update is retained.
-        if ( $ris_bli_container->{$seqno} ) {
-            next;
-        }
-
         # We are looking for opening container tokens with ci
+        my $K_opening = $K_opening_container->{$seqno};
         next unless ( defined($K_opening) && $KK == $K_opening );
 
         # Make sure there is a corresponding closing container
@@ -10560,6 +11541,15 @@ sub extended_ci {
         my $K_closing = $K_closing_container->{$seqno};
         next unless defined($K_closing);
 
+        # Skip if requested by -bbx to avoid blinkers
+        next if ( $rno_xci_by_seqno->{$seqno} );
+
+        # Skip if this is a -bli container (this fixes case b1065) Note: case
+        # b1065 is also fixed by the update for b1055, so this update is not
+        # essential now.  But there does not seem to be a good reason to add
+        # xci and bli together, so the update is retained.
+        next if ( $ris_bli_container->{$seqno} );
+
         # Require different input lines. This will filter out a large number
         # of small hash braces and array brackets.  If we accidentally filter
         # out an important container, it will get fixed on the next pass.
@@ -10586,6 +11576,7 @@ sub extended_ci {
 
         # Fix for b1197 b1198 b1199 b1200 b1201 b1202
         # Do not apply -xci if we are running out of space
+        # TODO: review this; do we also need to look at stress_level_alpha?
         if ( $level >= $stress_level_beta ) {
             DEBUG_XCI
               && print
@@ -10697,17 +11688,37 @@ sub bli_adjustment {
 
 sub find_multiline_qw {
 
-    my $self = shift;
+    my ( $self, $rqw_lines ) = @_;
 
     # Multiline qw quotes are not sequenced items like containers { [ (
     # but behave in some respects in a similar way. So this routine finds them
     # and creates a separate sequence number system for later use.
 
     # This is straightforward because they always begin at the end of one line
-    # and and at the beginning of a later line. This is true no matter how we
+    # and end at the beginning of a later line. This is true no matter how we
     # finally make our line breaks, so we can find them before deciding on new
     # line breaks.
 
+    # Input parameter:
+    #   if $rqw_lines is defined it is a ref to array of all line index numbers
+    #   for which there is a type 'q' qw quote at either end of the line. This
+    #   was defined by sub resync_lines_and_tokens for efficiency.
+    #
+
+    my $rlines = $self->[_rlines_];
+
+    # if $rqw_lines is not defined (this will occur with -io option) then we
+    # will have to scan all lines.
+    if ( !defined($rqw_lines) ) {
+        $rqw_lines = [ 0 .. @{$rlines} - 1 ];
+    }
+
+    # if $rqw_lines is defined but empty, just return because there are no
+    # multiline qw's
+    else {
+        if ( !@{$rqw_lines} ) { return }
+    }
+
     my $rstarting_multiline_qw_seqno_by_K = {};
     my $rending_multiline_qw_seqno_by_K   = {};
     my $rKrange_multiline_qw_by_seqno     = {};
@@ -10715,19 +11726,25 @@ sub find_multiline_qw {
 
     my $ris_excluded_lp_container = $self->[_ris_excluded_lp_container_];
 
-    my $rlines = $self->[_rlines_];
-    my $rLL    = $self->[_rLL_];
+    my $rLL = $self->[_rLL_];
     my $qw_seqno;
     my $num_qw_seqno = 0;
     my $K_start_multiline_qw;
 
-    foreach my $line_of_tokens ( @{$rlines} ) {
+    # For reference, here is the old loop, before $rqw_lines became available:
+    ##  foreach my $line_of_tokens ( @{$rlines} ) {
+    foreach my $iline ( @{$rqw_lines} ) {
+        my $line_of_tokens = $rlines->[$iline];
 
+        # Note that these first checks are required in case we have to scan
+        # all lines, not just lines with type 'q' at the ends.
         my $line_type = $line_of_tokens->{_line_type};
         next unless ( $line_type eq 'CODE' );
         my $rK_range = $line_of_tokens->{_rK_range};
         my ( $Kfirst, $Klast ) = @{$rK_range};
         next unless ( defined($Kfirst) && defined($Klast) );   # skip blank line
+
+        # Continuing a sequence of qw lines ...
         if ( defined($K_start_multiline_qw) ) {
             my $type = $rLL->[$Kfirst]->[_TYPE_];
 
@@ -10751,6 +11768,8 @@ EOM
                 $qw_seqno             = undef;
             }
         }
+
+        # Starting a new a sequence of qw lines ?
         if ( !defined($K_start_multiline_qw)
             && $rLL->[$Klast]->[_TYPE_] eq 'q' )
         {
@@ -10878,7 +11897,7 @@ BEGIN {
     };
 }
 
-sub collapsed_lengths {
+sub xlp_collapsed_lengths {
 
     my $self = shift;
 
@@ -10934,9 +11953,17 @@ sub collapsed_lengths {
     push @stack,
       [ $max_prong_len, $handle_len_x, SEQ_ROOT, undef, undef, undef, undef ];
 
+    #--------------------------------
+    # Loop over all lines in the file
+    #--------------------------------
     my $iline = -1;
+    my $skip_next_line;
     foreach my $line_of_tokens ( @{$rlines} ) {
         $iline++;
+        if ($skip_next_line) {
+            $skip_next_line = 0;
+            next;
+        }
         my $line_type = $line_of_tokens->{_line_type};
         next if ( $line_type ne 'CODE' );
         my $CODE_type = $line_of_tokens->{_code_type};
@@ -10992,41 +12019,41 @@ sub collapsed_lengths {
                     else {
 
                         # Fix for b1319, b1320
-                        goto NOT_MULTILINE_QW;
+                        $K_start_multiline_qw = undef;
                     }
                 }
             }
 
-            $len = $rLL->[$KK]->[_CUMULATIVE_LENGTH_] -
-              $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_];
-
-            # We may have to add the spaces of one level or ci level ...  it
-            # depends depends on the -xci flag, the -wn flag, and if the qw
-            # uses a container token as the quote delimiter.
+            if ( defined($K_start_multiline_qw) ) {
+                $len = $rLL->[$KK]->[_CUMULATIVE_LENGTH_] -
+                  $rLL->[ $KK - 1 ]->[_CUMULATIVE_LENGTH_];
 
-            # First rule: add ci if there is a $ci_level
-            if ($ci_level) {
-                $len += $rOpts_continuation_indentation;
-            }
+                # We may have to add the spaces of one level or ci level ...  it
+                # depends depends on the -xci flag, the -wn flag, and if the qw
+                # uses a container token as the quote delimiter.
 
-            # Second rule: otherwise, look for an extra indentation level
-            # from the start and add one indentation level if found.
-            elsif ( $level > $level_start_multiline_qw ) {
-                $len += $rOpts_indent_columns;
-            }
+                # First rule: add ci if there is a $ci_level
+                if ($ci_level) {
+                    $len += $rOpts_continuation_indentation;
+                }
 
-            if ( $len > $max_prong_len ) { $max_prong_len = $len }
+                # Second rule: otherwise, look for an extra indentation level
+                # from the start and add one indentation level if found.
+                elsif ( $level > $level_start_multiline_qw ) {
+                    $len += $rOpts_indent_columns;
+                }
 
-            $last_nonblank_type = 'q';
+                if ( $len > $max_prong_len ) { $max_prong_len = $len }
 
-            $K_begin_loop = $K_first + 1;
+                $last_nonblank_type = 'q';
 
-            # We can skip to the next line if more tokens
-            next if ( $K_begin_loop > $K_last );
+                $K_begin_loop = $K_first + 1;
 
+                # We can skip to the next line if more tokens
+                next if ( $K_begin_loop > $K_last );
+            }
         }
 
-      NOT_MULTILINE_QW:
         $K_start_multiline_qw = undef;
 
         # Find the terminal token, before any side comment
@@ -11041,44 +12068,110 @@ sub collapsed_lengths {
         # Use length to terminal comma if interrupted list rule applies
         if ( @stack && $stack[-1]->[_interrupted_list_rule_] ) {
             my $K_c = $stack[-1]->[_K_c_];
-            if (
-                defined($K_c)
-                && $rLL->[$K_terminal]->[_TYPE_] eq ','
+            if ( defined($K_c) ) {
+
+                #--------------------------------------------------------------
+                # BEGIN patch for issue b1408: If this line ends in an opening
+                # token, look for the closing token and comma at the end of the
+                # next line. If so, combine the two lines to get the correct
+                # sums.  This problem seems to require -xlp -vtc=2 and blank
+                # lines to occur.
+                #--------------------------------------------------------------
+                if ( $rLL->[$K_terminal]->[_TYPE_] eq '{' && !$has_comment ) {
+                    my $seqno_end = $rLL->[$K_terminal]->[_TYPE_SEQUENCE_];
+                    my $Kc_test   = $rLL->[$K_terminal]->[_KNEXT_SEQ_ITEM_];
+
+                    # We are looking for a short broken remnant on the next
+                    # line; something like the third line here (b1408):
+                    #     parent =>
+                    #       Moose::Util::TypeConstraints::find_type_constraint(
+                    #               'RefXX' ),
+                    # or this
+                    #
+                    #  Help::WorkSubmitter->_filter_chores_and_maybe_warn_user(
+                    #                                    $story_set_all_chores),
+                    if (   defined($Kc_test)
+                        && $seqno_end == $rLL->[$Kc_test]->[_TYPE_SEQUENCE_]
+                        && $rLL->[$Kc_test]->[_LINE_INDEX_] == $iline + 1 )
+                    {
+                        my $line_of_tokens_next = $rlines->[ $iline + 1 ];
+                        my $rtype_count = $rtype_count_by_seqno->{$seqno_end};
+                        my $comma_count =
+                          defined($rtype_count) ? $rtype_count->{','} : 0;
+                        my ( $K_first_next, $K_terminal_next ) =
+                          @{ $line_of_tokens_next->{_rK_range} };
+
+                        # NOTE: Do not try to do this if there is a side comment
+                        # because then the instability does not seem to occur.
+                        if (
+                            defined($K_terminal_next)
 
-                # Ignore if terminal comma, causes instability (b1297, b1330)
-                && (
-                    $K_c - $K_terminal > 2
-                    || (   $K_c - $K_terminal == 2
-                        && $rLL->[ $K_terminal + 1 ]->[_TYPE_] ne 'b' )
-                )
-              )
-            {
-                my $Kend = $K_terminal;
+                            # next line ends with a comma
+                            && $rLL->[$K_terminal_next]->[_TYPE_] eq ','
+
+                            # which follows the closing container token
+                            && (
+                                $K_terminal_next - $Kc_test == 1
+                                || (   $K_terminal_next - $Kc_test == 2
+                                    && $rLL->[ $K_terminal_next - 1 ]->[_TYPE_]
+                                    eq 'b' )
+                            )
 
-                # This caused an instability in b1311 by making the result
-                # dependent on input.  It is not really necessary because the
-                # comment length is added at the end of the loop.
-                ##if ( $has_comment
-                ##    && !$rOpts_ignore_side_comment_lengths )
-                ##{
-                ##    $Kend = $K_last;
-                ##}
+                            # no commas in the container
+                            && (   !defined($rtype_count)
+                                || !$rtype_count->{','} )
 
-                # changed from $len to my $leng to fix b1302 b1306 b1317 b1321
-                my $leng = $rLL->[$Kend]->[_CUMULATIVE_LENGTH_] -
-                  $rLL->[ $K_first - 1 ]->[_CUMULATIVE_LENGTH_];
+                            # for now, restrict this to a container with just 1
+                            # or two tokens
+                            && $K_terminal_next - $K_terminal <= 5
 
-                # Fix for b1331: at a broken => item, include the length of
-                # the previous half of the item plus one for the missing space
-                if ( $last_nonblank_type eq '=>' ) {
-                    $leng += $len + 1;
+                          )
+                        {
+
+                            # combine the next line with the current line
+                            $K_terminal     = $K_terminal_next;
+                            $skip_next_line = 1;
+                            if (DEBUG_COLLAPSED_LENGTHS) {
+                                print "Combining lines at line $iline\n";
+                            }
+                        }
+                    }
                 }
 
-                if ( $leng > $max_prong_len ) { $max_prong_len = $leng }
+                #--------------------------
+                # END patch for issue b1408
+                #--------------------------
+
+                if (
+                    $rLL->[$K_terminal]->[_TYPE_] eq ','
+
+                   # Ignore if terminal comma, causes instability (b1297, b1330)
+                    && (
+                        $K_c - $K_terminal > 2
+                        || (   $K_c - $K_terminal == 2
+                            && $rLL->[ $K_terminal + 1 ]->[_TYPE_] ne 'b' )
+                    )
+                  )
+                {
+
+                    # changed $len to my $leng to fix b1302 b1306 b1317 b1321
+                    my $leng = $rLL->[$K_terminal]->[_CUMULATIVE_LENGTH_] -
+                      $rLL->[ $K_first - 1 ]->[_CUMULATIVE_LENGTH_];
+
+                    # Fix for b1331: at a broken => item, include the length of
+                    # the previous half of the item plus one for the missing
+                    # space
+                    if ( $last_nonblank_type eq '=>' ) {
+                        $leng += $len + 1;
+                    }
+                    if ( $leng > $max_prong_len ) { $max_prong_len = $leng }
+                }
             }
         }
 
+        #----------------------------------
         # Loop over tokens on this line ...
+        #----------------------------------
         foreach my $KK ( $K_begin_loop .. $K_terminal ) {
 
             my $type = $rLL->[$KK]->[_TYPE_];
@@ -11157,18 +12250,12 @@ sub collapsed_lengths {
                     }
 
                     # Include length to a comma ending this line
+                    # note: any side comments are handled at loop end (b1332)
                     if (   $interrupted_list_rule
                         && $rLL->[$K_terminal]->[_TYPE_] eq ',' )
                     {
                         my $Kend = $K_terminal;
 
-                        # fix for b1332: side comments handled at end of loop
-                        ##if ( $Kend < $K_last
-                        ##    && !$rOpts_ignore_side_comment_lengths )
-                        ##{
-                        ##    $Kend = $K_last;
-                        ##}
-
                         # Measure from the next blank if any (fixes b1301)
                         my $Kbeg = $KK;
                         if (   $rLL->[ $Kbeg + 1 ]->[_TYPE_] eq 'b'
@@ -11196,97 +12283,95 @@ sub collapsed_lengths {
                 #--------------------
                 # Exiting a container
                 #--------------------
-                elsif ( $is_closing_token{$token} ) {
-                    if (@stack) {
-
-                        # The current prong ends - get its handle
-                        my $item          = pop @stack;
-                        my $handle_len    = $item->[_handle_len_];
-                        my $seqno_o       = $item->[_seqno_o_];
-                        my $iline_o       = $item->[_iline_o_];
-                        my $K_o           = $item->[_K_o_];
-                        my $K_c_expect    = $item->[_K_c_];
-                        my $collapsed_len = $max_prong_len;
-
-                        if ( $seqno_o ne $seqno ) {
-
-                            # This can happen if input file has brace errors.
-                            # Otherwise it shouldn't happen.  Not fatal but -lp
-                            # formatting could get messed up.
-                            if ( DEVEL_MODE && !get_saw_brace_error() ) {
-                                Fault(<<EOM);
+                elsif ( $is_closing_token{$token} && @stack ) {
+
+                    # The current prong ends - get its handle
+                    my $item          = pop @stack;
+                    my $handle_len    = $item->[_handle_len_];
+                    my $seqno_o       = $item->[_seqno_o_];
+                    my $iline_o       = $item->[_iline_o_];
+                    my $K_o           = $item->[_K_o_];
+                    my $K_c_expect    = $item->[_K_c_];
+                    my $collapsed_len = $max_prong_len;
+
+                    if ( $seqno_o ne $seqno ) {
+
+                        # This can happen if input file has brace errors.
+                        # Otherwise it shouldn't happen.  Not fatal but -lp
+                        # formatting could get messed up.
+                        if ( DEVEL_MODE && !get_saw_brace_error() ) {
+                            Fault(<<EOM);
 sequence numbers differ; at CLOSING line $iline, seq=$seqno, Kc=$KK .. at OPENING line $iline_o, seq=$seqno_o, Ko=$K_o, expecting Kc=$K_c_expect
 EOM
-                            }
                         }
+                    }
 
-                        #------------------------------------------
-                        # Rules to avoid scrunching code blocks ...
-                        #------------------------------------------
-                        # Some test cases:
-                        # c098/x107 x108 x110 x112 x114 x115 x117 x118 x119
-                        my $block_type = $rblock_type_of_seqno->{$seqno};
-                        if ($block_type) {
-
-                            my $K_c          = $KK;
-                            my $block_length = MIN_BLOCK_LEN;
-                            my $is_one_line_block;
-                            my $level = $rLL->[$K_o]->[_LEVEL_];
-                            if ( defined($K_o) && defined($K_c) ) {
-
-                                # note: fixed 3 May 2022 (removed 'my')
-                                $block_length =
-                                  $rLL->[ $K_c - 1 ]->[_CUMULATIVE_LENGTH_] -
-                                  $rLL->[$K_o]->[_CUMULATIVE_LENGTH_];
-                                $is_one_line_block = $iline == $iline_o;
-                            }
+                    #------------------------------------------
+                    # Rules to avoid scrunching code blocks ...
+                    #------------------------------------------
+                    # Some test cases:
+                    # c098/x107 x108 x110 x112 x114 x115 x117 x118 x119
+                    my $block_type = $rblock_type_of_seqno->{$seqno};
+                    if ($block_type) {
+
+                        my $K_c          = $KK;
+                        my $block_length = MIN_BLOCK_LEN;
+                        my $is_one_line_block;
+                        my $level = $rLL->[$K_o]->[_LEVEL_];
+                        if ( defined($K_o) && defined($K_c) ) {
+
+                            # note: fixed 3 May 2022 (removed 'my')
+                            $block_length =
+                              $rLL->[ $K_c - 1 ]->[_CUMULATIVE_LENGTH_] -
+                              $rLL->[$K_o]->[_CUMULATIVE_LENGTH_];
+                            $is_one_line_block = $iline == $iline_o;
+                        }
 
-                            # Code block rule 1: Use the total block length if
-                            # it is less than the minimum.
-                            if ( $block_length < MIN_BLOCK_LEN ) {
-                                $collapsed_len = $block_length;
-                            }
+                        # Code block rule 1: Use the total block length if
+                        # it is less than the minimum.
+                        if ( $block_length < MIN_BLOCK_LEN ) {
+                            $collapsed_len = $block_length;
+                        }
 
-                            # Code block rule 2: Use the full length of a
-                            # one-line block to avoid breaking it, unless
-                            # extremely long.  We do not need to do a precise
-                            # check here, because if it breaks then it will
-                            # stay broken on later iterations.
-                            elsif (
-                                   $is_one_line_block
-                                && $block_length <
-                                $maximum_line_length_at_level[$level]
-
-                                # But skip this for sort/map/grep/eval blocks
-                                # because they can reform (b1345)
-                                && !$is_sort_map_grep_eval{$block_type}
-                              )
-                            {
-                                $collapsed_len = $block_length;
-                            }
+                        # Code block rule 2: Use the full length of a
+                        # one-line block to avoid breaking it, unless
+                        # extremely long.  We do not need to do a precise
+                        # check here, because if it breaks then it will
+                        # stay broken on later iterations.
+                        elsif (
+                               $is_one_line_block
+                            && $block_length <
+                            $maximum_line_length_at_level[$level]
+
+                            # But skip this for sort/map/grep/eval blocks
+                            # because they can reform (b1345)
+                            && !$is_sort_map_grep_eval{$block_type}
+                          )
+                        {
+                            $collapsed_len = $block_length;
+                        }
 
-                            # Code block rule 3: Otherwise the length should be
-                            # at least MIN_BLOCK_LEN to avoid scrunching code
-                            # blocks.
-                            elsif ( $collapsed_len < MIN_BLOCK_LEN ) {
-                                $collapsed_len = MIN_BLOCK_LEN;
-                            }
+                        # Code block rule 3: Otherwise the length should be
+                        # at least MIN_BLOCK_LEN to avoid scrunching code
+                        # blocks.
+                        elsif ( $collapsed_len < MIN_BLOCK_LEN ) {
+                            $collapsed_len = MIN_BLOCK_LEN;
                         }
+                    }
 
-                        # Store the result.  Some extra space, '2', allows for
-                        # length of an opening token, inside space, comma, ...
-                        # This constant has been tuned to give good overall
-                        # results.
-                        $collapsed_len += 2;
-                        $rcollapsed_length_by_seqno->{$seqno} = $collapsed_len;
-
-                        # Restart scanning the lower level prong
-                        if (@stack) {
-                            $max_prong_len = $stack[-1]->[_max_prong_len_];
-                            $collapsed_len += $handle_len;
-                            if ( $collapsed_len > $max_prong_len ) {
-                                $max_prong_len = $collapsed_len;
-                            }
+                    # Store the result.  Some extra space, '2', allows for
+                    # length of an opening token, inside space, comma, ...
+                    # This constant has been tuned to give good overall
+                    # results.
+                    $collapsed_len += 2;
+                    $rcollapsed_length_by_seqno->{$seqno} = $collapsed_len;
+
+                    # Restart scanning the lower level prong
+                    if (@stack) {
+                        $max_prong_len = $stack[-1]->[_max_prong_len_];
+                        $collapsed_len += $handle_len;
+                        if ( $collapsed_len > $max_prong_len ) {
+                            $max_prong_len = $collapsed_len;
                         }
                     }
                 }
@@ -11335,7 +12420,7 @@ EOM
 
         } ## end loop over tokens on this line
 
-        # Now take care of any side comment
+        # Now take care of any side comment;
         if ($has_comment) {
             if ($rOpts_ignore_side_comment_lengths) {
                 $len = 0;
@@ -11366,7 +12451,7 @@ EOM
     }
 
     return;
-} ## end sub collapsed_lengths
+} ## end sub xlp_collapsed_lengths
 
 sub is_excluded_lp {
 
@@ -11378,6 +12463,9 @@ sub is_excluded_lp {
     #   what to exclude:  $line_up_parentheses_control_is_lxpl = 1, or
     #   what to include:  $line_up_parentheses_control_is_lxpl = 0
 
+    # Input parameter:
+    #   $KK = index of the container opening token
+
     my ( $self, $KK ) = @_;
     my $rLL         = $self->[_rLL_];
     my $rtoken_vars = $rLL->[$KK];
@@ -11425,6 +12513,7 @@ sub is_excluded_lp {
         elsif ( $flag1 eq 'F' ) { $match_flag1 = !$is_f }
         elsif ( $flag1 eq 'w' ) { $match_flag1 = $is_w }
         elsif ( $flag1 eq 'W' ) { $match_flag1 = !$is_w }
+        ## else { no match found }
     }
 
     # See if we can exclude this based on the flag1 test...
@@ -11512,26 +12601,6 @@ sub process_all_lines {
     my $vertical_aligner_object    = $self->[_vertical_aligner_object_];
     my $save_logfile               = $self->[_save_logfile_];
 
-    # Note for RT#118553, leave only one newline at the end of a file.
-    # Example code to do this is in comments below:
-    # my $Opt_trim_ending_blank_lines = 0;
-    # if ($Opt_trim_ending_blank_lines) {
-    #     while ( my $line_of_tokens = pop @{$rlines} ) {
-    #         my $line_type = $line_of_tokens->{_line_type};
-    #         if ( $line_type eq 'CODE' ) {
-    #             my $CODE_type = $line_of_tokens->{_code_type};
-    #             next if ( $CODE_type eq 'BL' );
-    #         }
-    #         push @{$rlines}, $line_of_tokens;
-    #         last;
-    #     }
-    # }
-
-   # But while this would be a trivial update, it would have very undesirable
-   # side effects when perltidy is run from within an editor on a small snippet.
-   # So this is best done with a separate filter, such
-   # as 'delete_ending_blank_lines.pl' in the examples folder.
-
     # Flag to prevent blank lines when POD occurs in a format skipping sect.
     my $in_format_skipping_section;
 
@@ -11542,16 +12611,16 @@ sub process_all_lines {
     my $i_last_POD_END = -10;
     my $i              = -1;
     foreach my $line_of_tokens ( @{$rlines} ) {
-        $i++;
 
         # insert blank lines requested for keyword sequences
-        if (   $i > 0
-            && defined( $rwant_blank_line_after->{ $i - 1 } )
-            && $rwant_blank_line_after->{ $i - 1 } == 1 )
+        if ( defined( $rwant_blank_line_after->{$i} )
+            && $rwant_blank_line_after->{$i} == 1 )
         {
             $self->want_blank_line();
         }
 
+        $i++;
+
         my $last_line_type = $line_type;
         $line_type = $line_of_tokens->{_line_type};
         my $input_line = $line_of_tokens->{_line_text};
@@ -11982,31 +13051,29 @@ EOM
 
         # First check: skip if next line is not one deeper
         my $Knext_nonblank = $self->K_next_nonblank($K_last);
-        goto RETURN if ( !defined($Knext_nonblank) );
+        return if ( !defined($Knext_nonblank) );
         my $level_next = $rLL->[$Knext_nonblank]->[_LEVEL_];
-        goto RETURN if ( $level_next != $level_beg + 1 );
+        return if ( $level_next != $level_beg + 1 );
 
         # Find the parent container of the first token on the next line
         my $parent_seqno = $self->parent_seqno_by_K($Knext_nonblank);
-        goto RETURN unless ( defined($parent_seqno) );
+        return unless ( defined($parent_seqno) );
 
         # Must not be a weld (can be unstable)
-        goto RETURN
+        return
           if ( $total_weld_count && $self->is_welded_at_seqno($parent_seqno) );
 
         # Opening container must exist and be on this line
         my $Ko = $K_opening_container->{$parent_seqno};
-        goto RETURN unless ( defined($Ko) && $Ko > $K_first && $Ko <= $K_last );
+        return unless ( defined($Ko) && $Ko > $K_first && $Ko <= $K_last );
 
         # Verify that the closing container exists and is on a later line
         my $Kc = $K_closing_container->{$parent_seqno};
-        goto RETURN unless ( defined($Kc) && $Kc > $K_last );
+        return unless ( defined($Kc) && $Kc > $K_last );
 
         # That's it
         $K_closing = $Kc;
-        goto RETURN;
 
-      RETURN:
         return;
     };
 
@@ -12286,17 +13353,17 @@ EOM
     # Batch variables: these describe the current batch of code being formed
     # and sent down the pipeline.  They are initialized in the next
     # sub.
-    my ( $rbrace_follower, $index_start_one_line_block,
-        $semicolons_before_block_self_destruct,
-        $starting_in_quote, $ending_in_quote, );
+    my (
+        $rbrace_follower,   $index_start_one_line_block,
+        $starting_in_quote, $ending_in_quote,
+    );
 
     # Called before the start of each new batch
     sub initialize_batch_variables {
 
-        $max_index_to_go         = UNDEFINED_INDEX;
-        $summed_lengths_to_go[0] = 0;
-        $nesting_depth_to_go[0]  = 0;
-        ##@summed_lengths_to_go       = @nesting_depth_to_go = (0);
+        $max_index_to_go            = UNDEFINED_INDEX;
+        $summed_lengths_to_go[0]    = 0;
+        $nesting_depth_to_go[0]     = 0;
         $ri_starting_one_line_block = [];
 
         # The initialization code for the remaining batch arrays is as follows
@@ -12333,9 +13400,7 @@ EOM
         $rbrace_follower = undef;
         $ending_in_quote = 0;
 
-        # These get re-initialized by calls to sub destroy_one_line_block():
-        $index_start_one_line_block            = UNDEFINED_INDEX;
-        $semicolons_before_block_self_destruct = 0;
+        $index_start_one_line_block = undef;
 
         # initialize forced breakpoint vars associated with each output batch
         $forced_breakpoint_count      = 0;
@@ -12357,14 +13422,10 @@ EOM
     } ## end sub leading_spaces_to_go
 
     sub create_one_line_block {
-        ( $index_start_one_line_block, $semicolons_before_block_self_destruct )
-          = @_;
-        return;
-    }
 
-    sub destroy_one_line_block {
-        $index_start_one_line_block            = UNDEFINED_INDEX;
-        $semicolons_before_block_self_destruct = 0;
+        # set index starting next one-line block
+        # call with no args to delete the current one-line block
+        ($index_start_one_line_block) = @_;
         return;
     }
 
@@ -12377,16 +13438,37 @@ EOM
 
         my ( $self, $Ktoken_vars, $rtoken_vars ) = @_;
 
-        # Add one token to the next batch.
+        #-------------------------------------------------------
+        # Token storage utility for sub process_line_of_CODE.
+        # Add one token to the next batch of '_to_go' variables.
+        #-------------------------------------------------------
+
+        # Input parameters:
         #   $Ktoken_vars = the index K in the global token array
         #   $rtoken_vars = $rLL->[$Ktoken_vars] = the corresponding token values
         #                  unless they are temporarily being overridden
 
-        #------------------------------------------------------------------
         # NOTE: called once per token so coding efficiency is critical here
-        #------------------------------------------------------------------
 
-        my $type = $rtoken_vars->[_TYPE_];
+        my (
+
+            $type,
+            $token,
+            $ci_level,
+            $level,
+            $seqno,
+            $length,
+
+          ) = @{$rtoken_vars}[
+
+          _TYPE_,
+          _TOKEN_,
+          _CI_LEVEL_,
+          _LEVEL_,
+          _TYPE_SEQUENCE_,
+          _TOKEN_LENGTH_,
+
+          ];
 
         # Check for emergency flush...
         # The K indexes in the batch must always be a continuous sequence of
@@ -12425,37 +13507,48 @@ EOM
             if ( $type eq 'b' ) { return }
         }
 
+        # Clip levels to zero if there are level errors in the file.
+        # We had to wait until now for reasons explained in sub 'write_line'.
+        if ( $level < 0 ) { $level = 0 }
+
+        # Safety check that length is defined. Should not be needed now.
+        # Former patch for indent-only, in which the entire set of tokens is
+        # turned into type 'q'. Lengths may have not been defined because sub
+        # 'respace_tokens' is bypassed. We do not need lengths in this case,
+        # but we will use the character count to have a defined value.  In the
+        # future, it would be nicer to have 'respace_tokens' convert the lines
+        # to quotes and get correct lengths.
+        if ( !defined($length) ) { $length = length($token) }
+
         #----------------------------
         # add this token to the batch
         #----------------------------
-        $K_to_go[ ++$max_index_to_go ] = $Ktoken_vars;
-        $types_to_go[$max_index_to_go] = $type;
-
+        $K_to_go[ ++$max_index_to_go ]             = $Ktoken_vars;
+        $types_to_go[$max_index_to_go]             = $type;
         $old_breakpoint_to_go[$max_index_to_go]    = 0;
         $forced_breakpoint_to_go[$max_index_to_go] = 0;
         $mate_index_to_go[$max_index_to_go]        = -1;
+        $tokens_to_go[$max_index_to_go]            = $token;
+        $ci_levels_to_go[$max_index_to_go]         = $ci_level;
+        $levels_to_go[$max_index_to_go]            = $level;
+        $type_sequence_to_go[$max_index_to_go]     = $seqno;
+        $nobreak_to_go[$max_index_to_go]           = $no_internal_newlines;
+        $token_lengths_to_go[$max_index_to_go]     = $length;
 
-        my $token = $tokens_to_go[$max_index_to_go] = $rtoken_vars->[_TOKEN_];
-
-        my $ci_level = $ci_levels_to_go[$max_index_to_go] =
-          $rtoken_vars->[_CI_LEVEL_];
-
-        # Clip levels to zero if there are level errors in the file.
-        # We had to wait until now for reasons explained in sub 'write_line'.
-        my $level = $rtoken_vars->[_LEVEL_];
-        if ( $level < 0 ) { $level = 0 }
-        $levels_to_go[$max_index_to_go] = $level;
-
-        my $seqno = $type_sequence_to_go[$max_index_to_go] =
-          $rtoken_vars->[_TYPE_SEQUENCE_];
-
-        my $in_continued_quote =
-          ( $Ktoken_vars == $K_first ) && $line_of_tokens->{_starting_in_quote};
+        # We keep a running sum of token lengths from the start of this batch:
+        #   summed_lengths_to_go[$i]   = total length to just before token $i
+        #   summed_lengths_to_go[$i+1] = total length to just after token $i
+        $summed_lengths_to_go[ $max_index_to_go + 1 ] =
+          $summed_lengths_to_go[$max_index_to_go] + $length;
 
         # Initializations for first token of new batch
-        if ( $max_index_to_go == 0 ) {
+        if ( !$max_index_to_go ) {
 
-            $starting_in_quote = $in_continued_quote;
+            # Reset flag '$starting_in_quote' for a new batch.  It must be set
+            # to the value of '$in_continued_quote', but here for efficiency we
+            # set it to zero, which is its normal value. Then in coding below
+            # we will change it if we find we are actually in a continued quote.
+            $starting_in_quote = 0;
 
             # Update the next parent sequence number for each new batch.
 
@@ -12536,33 +13629,15 @@ EOM
             }
         }
 
-        $nobreak_to_go[$max_index_to_go] = $no_internal_newlines;
-
-        my $length = $rtoken_vars->[_TOKEN_LENGTH_];
-
-        # Safety check that length is defined. Should not be needed now.
-        # Former patch for indent-only, in which the entire set of tokens is
-        # turned into type 'q'. Lengths may have not been defined because sub
-        # 'respace_tokens' is bypassed. We do not need lengths in this case,
-        # but we will use the character count to have a defined value.  In the
-        # future, it would be nicer to have 'respace_tokens' convert the lines
-        # to quotes and get correct lengths.
-        if ( !defined($length) ) {
-            $length = length($token);
-        }
-
-        $token_lengths_to_go[$max_index_to_go] = $length;
-
-        # We keep a running sum of token lengths from the start of this batch:
-        #   summed_lengths_to_go[$i]   = total length to just before token $i
-        #   summed_lengths_to_go[$i+1] = total length to just after token $i
-        $summed_lengths_to_go[ $max_index_to_go + 1 ] =
-          $summed_lengths_to_go[$max_index_to_go] + $length;
-
         # Define the indentation that this token will have in two cases:
         # Without CI = reduced_spaces_to_go
         # With CI    = leading_spaces_to_go
-        if ($in_continued_quote) {
+        if ( ( $Ktoken_vars == $K_first )
+            && $line_of_tokens->{_starting_in_quote} )
+        {
+            # in a continued quote - correct value set above if first token
+            if ( $max_index_to_go == 0 ) { $starting_in_quote = 1 }
+
             $leading_spaces_to_go[$max_index_to_go] = 0;
             $reduced_spaces_to_go[$max_index_to_go] = 0;
         }
@@ -12586,10 +13661,11 @@ EOM
 
     sub flush_batch_of_CODE {
 
-        # Finish any batch packaging and call the process routine.
+        # Finish and process the current batch.
         # This must be the only call to grind_batch_of_CODE()
         my ($self) = @_;
 
+        # If a batch has been started ...
         if ( $max_index_to_go >= 0 ) {
 
             # Create an array to hold variables for this batch
@@ -12620,6 +13696,9 @@ EOM
 
             $self->[_this_batch_] = $this_batch;
 
+            #-------------------
+            # process this batch
+            #-------------------
             $self->grind_batch_of_CODE();
 
             # Done .. this batch is history
@@ -12633,14 +13712,14 @@ EOM
 
     sub end_batch {
 
-        # end the current batch, EXCEPT for a few special cases
+        # End the current batch, EXCEPT for a few special cases
         my ($self) = @_;
 
         if ( $max_index_to_go < 0 ) {
 
-            # This is harmless but should be eliminated in development
+            # nothing to do .. this is harmless but wastes time.
             if (DEVEL_MODE) {
-                Fault("End batch called with nothing to do; please fix\n");
+                Fault("sub end_batch called with nothing to do; please fix\n");
             }
             return;
         }
@@ -12655,7 +13734,7 @@ EOM
 
             # Exception 2: just set a tentative breakpoint if we might be in a
             # one-line block
-            if ( $index_start_one_line_block != UNDEFINED_INDEX ) {
+            if ( defined($index_start_one_line_block) ) {
                 $self->set_forced_breakpoint($max_index_to_go);
                 return;
             }
@@ -12679,7 +13758,7 @@ EOM
 
         # end the current batch with 1 exception
 
-        destroy_one_line_block();
+        $index_start_one_line_block = undef;
 
         # Exception: if we are flushing within the code stream only to insert
         # blank line(s), then we can keep the batch intact at a weld. This
@@ -12706,25 +13785,16 @@ EOM
 
         # It outputs full-line comments and blank lines immediately.
 
-        # The tokens are copied one-by-one from the global token array $rLL to
-        # a set of '_to_go' arrays which collect batches of tokens for a
-        # further processing via calls to 'sub store_token_to_go', until a well
-        # defined 'structural' break point* or 'forced' breakpoint* is reached.
-        # Then, the batch of collected '_to_go' tokens is passed along to 'sub
-        # grind_batch_of_CODE' for further processing.
-
-        # * 'structural' break points are basically line breaks corresponding
-        # to code blocks.  An example is a chain of if-elsif-else statements,
-        # which should typically be broken at the opening and closing braces.
-
-        # * 'forced' break points are breaks required by side comments or by
-        # special user controls.
-
-        # So this routine is just making an initial set of required line
-        # breaks, basically regardless of the maximum requested line length.
-        # The subsequent stage of formatting make additional line breaks
-        # appropriate for lists and logical structures, and to keep line
-        # lengths below the requested maximum line length.
+        # For lines of code:
+        # - Tokens are copied one-by-one from the global token
+        #   array $rLL to a set of '_to_go' arrays which collect batches of
+        #   tokens. This is done with calls to 'store_token_to_go'.
+        # - A batch is closed and processed upon reaching a well defined
+        #   structural break point (i.e. code block boundary) or forced
+        #   breakpoint (i.e. side comment or special user controls).
+        # - Subsequent stages of formatting make additional line breaks
+        #   appropriate for lists and logical structures, and as necessary to
+        #   keep line lengths below the requested maximum line length.
 
         #-----------------------------------
         # begin initialize closure variables
@@ -12809,7 +13879,7 @@ EOM
                 return;
             }
 
-            destroy_one_line_block();
+            $index_start_one_line_block = undef;
             $self->end_batch() if ( $max_index_to_go >= 0 );
 
             # output a blank line before block comments
@@ -12871,30 +13941,33 @@ EOM
             return;
         }
 
-        # Compare input/output indentation except for:
-        #  - hanging side comments
-        #  - continuation lines (have unknown amount of initial blank space)
-        #  - and lines which are quotes (because they may have been outdented)
-        my $guessed_indentation_level =
-          $line_of_tokens->{_guessed_indentation_level};
-
-        unless ( $CODE_type eq 'HSC'
-            || $rtok_first->[_CI_LEVEL_] > 0
-            || $guessed_indentation_level == 0 && $rtok_first->[_TYPE_] eq 'Q' )
-        {
-            my $input_line_number = $line_of_tokens->{_line_number};
-            $self->compare_indentation_levels( $K_first,
-                $guessed_indentation_level, $input_line_number );
+        #--------------------------------------------
+        # Compare input/output indentation in logfile
+        #--------------------------------------------
+        if ( $self->[_save_logfile_] ) {
+
+            # Compare input/output indentation except for:
+            #  - hanging side comments
+            #  - continuation lines (have unknown leading blank space)
+            #  - and lines which are quotes (they may have been outdented)
+            my $guessed_indentation_level =
+              $line_of_tokens->{_guessed_indentation_level};
+
+            unless ( $CODE_type eq 'HSC'
+                || $rtok_first->[_CI_LEVEL_] > 0
+                || $guessed_indentation_level == 0
+                && $rtok_first->[_TYPE_] eq 'Q' )
+            {
+                my $input_line_number = $line_of_tokens->{_line_number};
+                $self->compare_indentation_levels( $K_first,
+                    $guessed_indentation_level, $input_line_number );
+            }
         }
 
-        #------------------------
-        # Handle indentation-only
-        #------------------------
+        #-----------------------------------------
+        # Handle a line marked as indentation-only
+        #-----------------------------------------
 
-        # NOTE: In previous versions we sent all qw lines out immediately here.
-        # No longer doing this: also write a line which is entirely a 'qw' list
-        # to allow stacking of opening and closing tokens.  Note that interior
-        # qw lines will still go out at the end of this routine.
         if ( $CODE_type eq 'IO' ) {
             $self->flush();
             my $line = $input_line;
         # if we do not see another elseif or an else.
         if ($looking_for_else) {
 
-            ##     /^(elsif|else)$/
-            if ( !$is_elsif_else{ $rLL->[$K_first_true]->[_TOKEN_] } ) {
-                write_logfile_entry("(No else block)\n");
-            }
-            $looking_for_else = 0;
-        }
+            ##     /^(elsif|else)$/
+            if ( !$is_elsif_else{ $rLL->[$K_first_true]->[_TOKEN_] } ) {
+                write_logfile_entry("(No else block)\n");
+            }
+            $looking_for_else = 0;
+        }
+
+        # This is a good place to kill incomplete one-line blocks
+        if ( $max_index_to_go >= 0 ) {
+            if (
+
+                # this check needed -mangle (for example rt125012)
+                (
+                       ( !$index_start_one_line_block )
+                    && ( $last_old_nonblank_type eq ';' )
+                    && ( $first_new_nonblank_token ne '}' )
+                )
+
+                # Patch for RT #98902. Honor request to break at old commas.
+                || (   $rOpts_break_at_old_comma_breakpoints
+                    && $last_old_nonblank_type eq ',' )
+              )
+            {
+                $forced_breakpoint_to_go[$max_index_to_go] = 1
+                  if ($rOpts_break_at_old_comma_breakpoints);
+                $index_start_one_line_block = undef;
+                $self->end_batch();
+            }
+
+            # Keep any requested breaks before this line.  Note that we have to
+            # use the original K_first because it may have been reduced above
+            # to add a blank.  The value of the flag is as follows:
+            #   1 => hard break, flush the batch
+            #   2 => soft break, set breakpoint and continue building the batch
+            if ( $self->[_rbreak_before_Kfirst_]->{$K_first_true} ) {
+                $index_start_one_line_block = undef;
+                if ( $self->[_rbreak_before_Kfirst_]->{$K_first_true} == 2 ) {
+                    $self->set_forced_breakpoint($max_index_to_go);
+                }
+                else {
+                    $self->end_batch() if ( $max_index_to_go >= 0 );
+                }
+            }
+        }
+
+        #--------------------------------------
+        # loop to process the tokens one-by-one
+        #--------------------------------------
+        $self->process_line_inner_loop($has_side_comment);
+
+        # if there is anything left in the output buffer ...
+        if ( $max_index_to_go >= 0 ) {
+
+            my $type       = $rLL->[$K_last]->[_TYPE_];
+            my $break_flag = $self->[_rbreak_after_Klast_]->{$K_last};
+
+            # we have to flush ..
+            if (
+
+                # if there is a side comment...
+                $type eq '#'
+
+                # if this line ends in a quote
+                # NOTE: This is critically important for insuring that quoted
+                # lines do not get processed by things like -sot and -sct
+                || $in_quote
+
+                # if this is a VERSION statement
+                || $CODE_type eq 'VER'
+
+                # to keep a label at the end of a line
+                || ( $type eq 'J' && $rOpts_break_after_labels != 2 )
+
+                # if we have a hard break request
+                || $break_flag && $break_flag != 2
 
-        # This is a good place to kill incomplete one-line blocks
-        if ( $max_index_to_go >= 0 ) {
-            if (
-                (
-                       ( $semicolons_before_block_self_destruct == 0 )
-                    && ( $last_old_nonblank_type eq ';' )
-                    && ( $first_new_nonblank_token ne '}' )
-                )
+                # if we are instructed to keep all old line breaks
+                || !$rOpts->{'delete-old-newlines'}
 
-                # Patch for RT #98902. Honor request to break at old commas.
-                || (   $rOpts_break_at_old_comma_breakpoints
-                    && $last_old_nonblank_type eq ',' )
+                # if this is a line of the form 'use overload'. A break here in
+                # the input file is a good break because it will allow the
+                # operators which follow to be formatted well. Without this
+                # break the formatting with -ci=4 -xci is poor, for example.
+
+                #   use overload
+                #     '+' => sub {
+                #       print length $_[2], "\n";
+                #       my ( $x, $y ) = _order(@_);
+                #       Number::Roman->new( int $x + $y );
+                #     },
+                #     '-' => sub {
+                #       my ( $x, $y ) = _order(@_);
+                #       Number::Roman->new( int $x - $y );
+                #     };
+                || (   $max_index_to_go == 2
+                    && $types_to_go[0] eq 'k'
+                    && $tokens_to_go[0] eq 'use'
+                    && $tokens_to_go[$max_index_to_go] eq 'overload' )
               )
             {
-                $forced_breakpoint_to_go[$max_index_to_go] = 1
-                  if ($rOpts_break_at_old_comma_breakpoints);
-                destroy_one_line_block();
+                $index_start_one_line_block = undef;
                 $self->end_batch();
             }
 
-            # Keep any requested breaks before this line.  Note that we have to
-            # use the original K_first because it may have been reduced above
-            # to add a blank.  The value of the flag is as follows:
-            #   1 => hard break, flush the batch
-            #   2 => soft break, set breakpoint and continue building the batch
-            if ( $self->[_rbreak_before_Kfirst_]->{$K_first_true} ) {
-                destroy_one_line_block();
-                if ( $self->[_rbreak_before_Kfirst_]->{$K_first_true} == 2 ) {
+            else {
+
+                # Check for a soft break request
+                if ( $break_flag && $break_flag == 2 ) {
                     $self->set_forced_breakpoint($max_index_to_go);
                 }
-                else {
-                    $self->end_batch() if ( $max_index_to_go >= 0 );
+
+                # mark old line breakpoints in current output stream
+                if (  !$rOpts_ignore_old_breakpoints
+                    || $self->[_ris_essential_old_breakpoint_]->{$K_last} )
+                {
+                    my $jobp = $max_index_to_go;
+                    if (   $types_to_go[$max_index_to_go] eq 'b'
+                        && $max_index_to_go > 0 )
+                    {
+                        $jobp--;
+                    }
+                    $old_breakpoint_to_go[$jobp] = 1;
                 }
             }
         }
 
-        #--------------------------------------
-        # loop to process the tokens one-by-one
-        #--------------------------------------
+        return;
+    } ## end sub process_line_of_CODE
+
+    sub process_line_inner_loop {
 
-        # We do not want a leading blank if the previous batch just got output
+        my ( $self, $has_side_comment ) = @_;
 
+        #--------------------------------------------------------------------
+        # Loop to move all tokens from one input line to a newly forming batch
+        #--------------------------------------------------------------------
+
+        # Do not start a new batch with a blank space
         if ( $max_index_to_go < 0 && $rLL->[$K_first]->[_TYPE_] eq 'b' ) {
             $K_first++;
         }
@@ -13037,28 +14199,25 @@ EOM
                 }
             }
 
-            # if at last token ...
-            if ( $Ktoken_vars == $K_last ) {
+            #---------------------
+            # handle side comments
+            #---------------------
+            if ($has_side_comment) {
 
-                #---------------------
-                # handle side comments
-                #---------------------
-                if ($has_side_comment) {
+                # if at last token ...
+                if ( $Ktoken_vars == $K_last ) {
                     $self->store_token_to_go( $Ktoken_vars, $rtoken_vars );
                     next;
                 }
-            }
 
-            # if before last token ... do not allow breaks which would promote
-            # a side comment to a block comment
-            elsif (
-                $has_side_comment
-                && (   $Ktoken_vars == $K_last - 1
+                # if before last token ... do not allow breaks which would
+                # promote a side comment to a block comment
+                elsif ($Ktoken_vars == $K_last - 1
                     || $Ktoken_vars == $K_last - 2
                     && $rLL->[ $K_last - 1 ]->[_TYPE_] eq 'b' )
-              )
-            {
-                $no_internal_newlines = 2;
+                {
+                    $no_internal_newlines = 2;
+                }
             }
 
             # Process non-blank and non-comment tokens ...
@@ -13077,22 +14236,12 @@ EOM
                     $next_nonblank_token_type = $rLL->[$Knnb]->[_TYPE_];
                 }
 
-                my $break_before_semicolon = ( $Ktoken_vars == $K_first )
-                  && $rOpts_break_at_old_semicolon_breakpoints;
-
-                # kill one-line blocks with too many semicolons
-                $semicolons_before_block_self_destruct--;
-                if (
-                       $break_before_semicolon
-                    || ( $semicolons_before_block_self_destruct < 0 )
-                    || (   $semicolons_before_block_self_destruct == 0
-                        && $next_nonblank_token_type !~ /^[b\}]$/ )
-                  )
+                if (   $rOpts_break_at_old_semicolon_breakpoints
+                    && ( $Ktoken_vars == $K_first )
+                    && $max_index_to_go >= 0
+                    && !defined($index_start_one_line_block) )
                 {
-                    destroy_one_line_block();
-                    $self->end_batch()
-                      if ( $break_before_semicolon
-                        && $max_index_to_go >= 0 );
+                    $self->end_batch();
                 }
 
                 $self->store_token_to_go( $Ktoken_vars, $rtoken_vars );
@@ -13153,7 +14302,7 @@ EOM
                     $want_break
 
                     # and we were unable to start looking for a block,
-                    && $index_start_one_line_block == UNDEFINED_INDEX
+                    && !defined($index_start_one_line_block)
 
                     # or if it will not be on same line as its keyword, so that
                     # it will be outdented (eval.t, overload.t), and the user
@@ -13198,7 +14347,7 @@ EOM
                 }
 
                 # If there is a pending one-line block ..
-                if ( $index_start_one_line_block != UNDEFINED_INDEX ) {
+                if ( defined($index_start_one_line_block) ) {
 
                     # Fix for b1208: if a side comment follows this closing
                     # brace then we must include its length in the length test
@@ -13220,14 +14369,9 @@ EOM
                         # token
                         $self->excess_line_length( $index_start_one_line_block,
                             $max_index_to_go ) + $added_length >= 0
-
-                        # or if it has too many semicolons
-                        || (   $semicolons_before_block_self_destruct == 0
-                            && defined($K_last_nonblank_code)
-                            && $rLL->[$K_last_nonblank_code]->[_TYPE_] ne ';' )
                       )
                     {
-                        destroy_one_line_block();
+                        $index_start_one_line_block = undef;
                     }
                 }
 
@@ -13235,7 +14379,7 @@ EOM
                 $self->end_batch()
                   if ( $max_index_to_go >= 0
                     && !$nobreak_BEFORE_BLOCK
-                    && $index_start_one_line_block == UNDEFINED_INDEX );
+                    && !defined($index_start_one_line_block) );
 
                 # store the closing curly brace
                 $self->store_token_to_go( $Ktoken_vars, $rtoken_vars );
@@ -13245,14 +14389,14 @@ EOM
                 # So now we have to check for special cases.
 
                 # if this '}' successfully ends a one-line block..
-                my $is_one_line_block = 0;
-                my $keep_going        = 0;
-                if ( $index_start_one_line_block != UNDEFINED_INDEX ) {
+                my $one_line_block_type = EMPTY_STRING;
+                my $keep_going;
+                if ( defined($index_start_one_line_block) ) {
 
                     # Remember the type of token just before the
                     # opening brace.  It would be more general to use
                     # a stack, but this will work for one-line blocks.
-                    $is_one_line_block =
+                    $one_line_block_type =
                       $types_to_go[$index_start_one_line_block];
 
                     # we have to actually make it by removing tentative
@@ -13284,7 +14428,7 @@ EOM
                       $index_start_one_line_block;
 
                     # then re-initialize for the next one-line block
-                    destroy_one_line_block();
+                    $index_start_one_line_block = undef;
 
                     # then decide if we want to break after the '}' ..
                     # We will keep going to allow certain brace followers as in:
@@ -13298,7 +14442,7 @@ EOM
 
                             # Follow users break point for
                             # one line block types U & G, such as a 'try' block
-                            || $is_one_line_block =~ /^[UG]$/
+                            || $one_line_block_type =~ /^[UG]$/
                             && $Ktoken_vars == $K_last
                         )
 
@@ -13328,7 +14472,7 @@ EOM
 
                 # added eval for borris.t
                 elsif ($is_sort_map_grep_eval{$block_type}
-                    || $is_one_line_block eq 'G' )
+                    || $one_line_block_type eq 'G' )
                 {
                     $rbrace_follower = undef;
                     $keep_going      = 1;
@@ -13336,7 +14480,7 @@ EOM
 
                 # anonymous sub
                 elsif ( $self->[_ris_asub_block_]->{$type_sequence} ) {
-                    if ($is_one_line_block) {
+                    if ($one_line_block_type) {
 
                         $rbrace_follower = \%is_anon_sub_1_brace_follower;
 
@@ -13364,14 +14508,12 @@ EOM
                             my $Kc      = $K_closing_container->{$p_seqno};
                             my $is_excluded =
                               $self->[_ris_excluded_lp_container_]->{$p_seqno};
-                            if (   defined($Kc)
-                                && $rLL->[$Kc]->[_TOKEN_] eq '}'
-                                && !$is_excluded
-                                && $Kc - $Ktoken_vars <= 2 )
-                            {
-                                $rbrace_follower = undef;
-                                $keep_going      = 1;
-                            }
+                            $keep_going =
+                              (      defined($Kc)
+                                  && $rLL->[$Kc]->[_TOKEN_] eq '}'
+                                  && !$is_excluded
+                                  && $Kc - $Ktoken_vars <= 2 );
+                            $rbrace_follower = undef if ($keep_going);
                         }
                     }
                     else {
@@ -13408,6 +14550,8 @@ EOM
                 if ($keep_going) {
 
                     # keep going
+                    $rbrace_follower = undef;
+
                 }
 
                 # if no more tokens, postpone decision until re-entering
@@ -13422,11 +14566,27 @@ EOM
                 }
                 elsif ($rbrace_follower) {
 
-                    unless ( $rbrace_follower->{$next_nonblank_token} ) {
+                    if ( $rbrace_follower->{$next_nonblank_token} ) {
+
+                        # Fix for b1385: keep break after a comma following a
+                        # 'do' block. This could also be used for other block
+                        # types, but that would cause a significant change in
+                        # existing formatting without much benefit.
+                        if (   $next_nonblank_token eq ','
+                            && $Knnb eq $K_last
+                            && $block_type eq 'do'
+                            && $rOpts_add_newlines
+                            && $self->is_trailing_comma($Knnb) )
+                        {
+                            $self->[_rbreak_after_Klast_]->{$K_last} = 1;
+                        }
+                    }
+                    else {
                         $self->end_batch()
                           unless ( $no_internal_newlines
                             || $max_index_to_go < 0 );
                     }
+
                     $rbrace_follower = undef;
                 }
 
@@ -13445,7 +14605,6 @@ EOM
 
                 # no newlines after seeing here-target
                 $no_internal_newlines = 2;
-                ## destroy_one_line_block();  # deleted to fix case b529
                 $self->store_token_to_go( $Ktoken_vars, $rtoken_vars );
             }
 
@@ -13470,86 +14629,37 @@ EOM
             $K_last_nonblank_code = $Ktoken_vars;
 
         } ## end of loop over all tokens in this line
+        return;
+    } ## end sub process_line_inner_loop
 
-        # if there is anything left in the output buffer ...
-        if ( $max_index_to_go >= 0 ) {
-
-            my $type       = $rLL->[$K_last]->[_TYPE_];
-            my $break_flag = $self->[_rbreak_after_Klast_]->{$K_last};
-
-            # we have to flush ..
-            if (
-
-                # if there is a side comment...
-                $type eq '#'
-
-                # if this line ends in a quote
-                # NOTE: This is critically important for insuring that quoted
-                # lines do not get processed by things like -sot and -sct
-                || $in_quote
-
-                # if this is a VERSION statement
-                || $CODE_type eq 'VER'
-
-                # to keep a label at the end of a line
-                || ( $type eq 'J' && $rOpts_break_after_labels != 2 )
-
-                # if we have a hard break request
-                || $break_flag && $break_flag != 2
-
-                # if we are instructed to keep all old line breaks
-                || !$rOpts->{'delete-old-newlines'}
-
-                # if this is a line of the form 'use overload'. A break here in
-                # the input file is a good break because it will allow the
-                # operators which follow to be formatted well. Without this
-                # break the formatting with -ci=4 -xci is poor, for example.
-
-                #   use overload
-                #     '+' => sub {
-                #       print length $_[2], "\n";
-                #       my ( $x, $y ) = _order(@_);
-                #       Number::Roman->new( int $x + $y );
-                #     },
-                #     '-' => sub {
-                #       my ( $x, $y ) = _order(@_);
-                #       Number::Roman->new( int $x - $y );
-                #     };
-                || (   $max_index_to_go == 2
-                    && $types_to_go[0] eq 'k'
-                    && $tokens_to_go[0] eq 'use'
-                    && $tokens_to_go[$max_index_to_go] eq 'overload' )
-              )
-            {
-                destroy_one_line_block();
-                $self->end_batch();
-            }
-
-            else {
+} ## end closure process_line_of_CODE
 
-                # Check for a soft break request
-                if ( $break_flag && $break_flag == 2 ) {
-                    $self->set_forced_breakpoint($max_index_to_go);
-                }
+sub is_trailing_comma {
+    my ( $self, $KK ) = @_;
 
-                # mark old line breakpoints in current output stream
-                if (  !$rOpts_ignore_old_breakpoints
-                    || $self->[_ris_essential_old_breakpoint_]->{$K_last} )
-                {
-                    my $jobp = $max_index_to_go;
-                    if (   $types_to_go[$max_index_to_go] eq 'b'
-                        && $max_index_to_go > 0 )
-                    {
-                        $jobp--;
-                    }
-                    $old_breakpoint_to_go[$jobp] = 1;
-                }
-            }
-        }
+    # Given:
+    #   $KK - index of a comma in token list
+    # Return:
+    #   true if the comma at index $KK is a trailing comma
+    #   false if not
 
+    my $rLL     = $self->[_rLL_];
+    my $type_KK = $rLL->[$KK]->[_TYPE_];
+    if ( $type_KK ne ',' ) {
+        DEVEL_MODE
+          && Fault("Bad call: expected type ',' but received '$type_KK'\n");
         return;
-    } ## end sub process_line_of_CODE
-} ## end closure process_line_of_CODE
+    }
+    my $Knnb = $self->K_next_nonblank($KK);
+    if ( defined($Knnb) ) {
+        my $type_sequence = $rLL->[$Knnb]->[_TYPE_SEQUENCE_];
+        my $type_Knnb     = $rLL->[$Knnb]->[_TYPE_];
+        if ( $type_sequence && $is_closing_type{$type_Knnb} ) {
+            return 1;
+        }
+    }
+    return;
+} ## end sub is_trailing_comma
 
 sub tight_paren_follows {
 
@@ -13698,13 +14808,27 @@ BEGIN {
 
 sub starting_one_line_block {
 
-    # after seeing an opening curly brace, look for the closing brace and see
+    # After seeing an opening curly brace, look for the closing brace and see
     # if the entire block will fit on a line.  This routine is not always right
     # so a check is made later (at the closing brace) to make sure we really
     # have a one-line block.  We have to do this preliminary check, though,
     # because otherwise we would always break at a semicolon within a one-line
     # block if the block contains multiple statements.
 
+    # Given:
+    #  $Kj              = index of opening brace
+    #  $K_last_nonblank = index of previous nonblank code token
+    #  $K_last          = index of last token of input line
+
+    # Calls 'create_one_line_block' if one-line block might be formed.
+
+    # Also returns a flag '$too_long':
+    #  true  = distance from opening keyword to OPENING brace exceeds
+    #          the maximum line length.
+    #  false (simple return) => not too long
+    # Note that this flag is for distance from the statement start to the
+    # OPENING brace, not the closing brace.
+
     my ( $self, $Kj, $K_last_nonblank, $K_last ) = @_;
 
     my $rbreak_container     = $self->[_rbreak_container_];
@@ -13714,11 +14838,7 @@ sub starting_one_line_block {
     my $rblock_type_of_seqno = $self->[_rblock_type_of_seqno_];
 
     # kill any current block - we can only go 1 deep
-    destroy_one_line_block();
-
-    # return value:
-    #  1=distance from start of block to opening brace exceeds line length
-    #  0=otherwise
+    create_one_line_block();
 
     my $i_start = 0;
 
@@ -13730,13 +14850,13 @@ sub starting_one_line_block {
     if ( !defined($max_index_to_go) || $max_index_to_go < 0 ) {
         Fault("program bug: store_token_to_go called incorrectly\n")
           if (DEVEL_MODE);
-        return 0;
+        return;
     }
 
     # Return if block should be broken
     my $type_sequence_j = $rLL->[$Kj]->[_TYPE_SEQUENCE_];
     if ( $rbreak_container->{$type_sequence_j} ) {
-        return 0;
+        return;
     }
 
     my $ris_bli_container = $self->[_ris_bli_container_];
@@ -13754,7 +14874,9 @@ sub starting_one_line_block {
         }
     }
 
+    #---------------------------------------------------------------------
     # find the starting keyword for this block (such as 'if', 'else', ...)
+    #---------------------------------------------------------------------
     if (
         $max_index_to_go == 0
         ##|| $block_type =~ /^[\{\}\;\:]$/
@@ -13785,22 +14907,22 @@ sub starting_one_line_block {
 
             # Find the opening paren
             my $K_start = $K_to_go[$i_start];
-            return unless defined($K_start);
+            return unless defined($K_start);
             my $seqno = $type_sequence_to_go[$i_start];
-            return unless ($seqno);
+            return unless ($seqno);
             my $K_opening = $K_opening_container->{$seqno};
-            return unless defined($K_opening);
+            return unless defined($K_opening);
             my $i_opening = $i_start + ( $K_opening - $K_start );
 
             # give up if not on this line
-            return unless ( $i_opening >= 0 );
-            $i_start = $i_opening;    ##$index_max_forced_break + 1;
+            return unless ( $i_opening >= 0 );
+            $i_start = $i_opening;
 
             # go back one token before the opening paren
             if ( $i_start > 0 )                                  { $i_start-- }
             if ( $types_to_go[$i_start] eq 'b' && $i_start > 0 ) { $i_start--; }
             my $lev = $levels_to_go[$i_start];
-            if ( $lev > $rLL->[$Kj]->[_LEVEL_] ) { return }
+            if ( $lev > $rLL->[$Kj]->[_LEVEL_] ) { return }
         }
     }
 
@@ -13827,7 +14949,7 @@ sub starting_one_line_block {
             $stripped_block_type = substr( $block_type, 0, -2 );
         }
         unless ( $tokens_to_go[$i_start] eq $stripped_block_type ) {
-            return 0;
+            return;
         }
     }
 
@@ -13841,11 +14963,14 @@ sub starting_one_line_block {
             $i_start++;
         }
         unless ( $tokens_to_go[$i_start] eq $block_type ) {
-            return 0;
+            return;
         }
     }
-
     else {
+
+        #-------------------------------------------
+        # Couldn't find start - return too_long flag
+        #-------------------------------------------
         return 1;
     }
 
@@ -13854,15 +14979,23 @@ sub starting_one_line_block {
     my $maximum_line_length =
       $maximum_line_length_at_level[ $levels_to_go[$i_start] ];
 
-    # see if block starting location is too great to even start
+    # see if distance to the opening container is too great to even start
     if ( $pos > $maximum_line_length ) {
+
+        #------------------------------
+        # too long to the opening token
+        #------------------------------
         return 1;
     }
 
-    # See if everything to the closing token will fit on one line
+    #-----------------------------------------------------------------------
+    # OK so far: the statement is not to long just to the OPENING token. Now
+    # see if everything to the closing token will fit on one line
+    #-----------------------------------------------------------------------
+
     # This is part of an update to fix cases b562 .. b983
     my $K_closing = $self->[_K_closing_container_]->{$type_sequence_j};
-    return unless ( defined($K_closing) );
+    return unless ( defined($K_closing) );
     my $container_length = $rLL->[$K_closing]->[_CUMULATIVE_LENGTH_] -
       $rLL->[$Kj]->[_CUMULATIVE_LENGTH_];
 
@@ -13877,7 +15010,7 @@ sub starting_one_line_block {
 
         # line is too long...  there is no chance of forming a one line block
         # if the excess is more than 1 char
-        return if ( $excess > 1 );
+        return if ( $excess > 1 );
 
         # ... and give up if it is not a one-line block on input.
         # note: for a one-line block on input, it may be possible to keep
@@ -13885,9 +15018,12 @@ sub starting_one_line_block {
         my $K_start = $K_to_go[$i_start];
         my $ldiff =
           $rLL->[$K_closing]->[_LINE_INDEX_] - $rLL->[$K_start]->[_LINE_INDEX_];
-        return if ($ldiff);
+        return if ($ldiff);
     }
 
+    #------------------------------------------------------------------
+    # Loop to check contents and length of the potential one-line block
+    #------------------------------------------------------------------
     foreach my $Ki ( $Kj + 1 .. $K_last ) {
 
         # old whitespace could be arbitrarily large, so don't use it
@@ -13900,7 +15036,7 @@ sub starting_one_line_block {
 
         # Return false result if we exceed the maximum line length,
         if ( $pos > $maximum_line_length ) {
-            return 0;
+            return;
         }
 
         # keep going for non-containers
@@ -13915,7 +15051,7 @@ sub starting_one_line_block {
             && $rblock_type_of_seqno->{$type_sequence_i}
             && !$nobreak )
         {
-            return 0;
+            return;
         }
 
         # if we find our closing brace..
@@ -13997,14 +15133,16 @@ sub starting_one_line_block {
                     }
 
                     if ( $pos >= $maximum_line_length ) {
-                        return 0;
+                        return;
                     }
                 }
             }
 
+            #--------------------------
             # ok, it's a one-line block
-            create_one_line_block( $i_start, 20 );
-            return 0;
+            #--------------------------
+            create_one_line_block($i_start);
+            return;
         }
 
         # just keep going for other characters
@@ -14012,6 +15150,10 @@ sub starting_one_line_block {
         }
     }
 
+    #--------------------------------------------------
+    # End Loop to examine tokens in potential one-block
+    #--------------------------------------------------
+
     # We haven't hit the closing brace, but there is still space. So the
     # question here is, should we keep going to look at more lines in hopes of
     # forming a new one-line block, or should we stop right now. The problem
@@ -14027,9 +15169,33 @@ sub starting_one_line_block {
     # The blocks which we can keep going are in a hash, but we never want
     # to continue if we are at a '-bli' block.
     if ( $want_one_line_block{$block_type} && !$is_bli ) {
-        create_one_line_block( $i_start, 1 );
+        my $rtype_count = $self->[_rtype_count_by_seqno_]->{$type_sequence_j};
+        my $semicolon_count = $rtype_count
+          && $rtype_count->{';'} ? $rtype_count->{';'} : 0;
+
+        # Ignore a terminal semicolon in the count
+        if ( $semicolon_count <= 2 ) {
+            my $K_closing_container = $self->[_K_closing_container_];
+            my $K_closing_j         = $K_closing_container->{$type_sequence_j};
+            my $Kp                  = $self->K_previous_nonblank($K_closing_j);
+            if ( defined($Kp)
+                && $rLL->[$Kp]->[_TYPE_] eq ';' )
+            {
+                $semicolon_count -= 1;
+            }
+        }
+        if ( $semicolon_count <= 0 ) {
+            create_one_line_block($i_start);
+        }
+        elsif ( $semicolon_count == 1 && $block_type eq 'eval' ) {
+
+            # Mark short broken eval blocks for possible later use in
+            # avoiding adding spaces before a 'package' line. This is not
+            # essential but helps keep newer and older formatting the same.
+            $self->[_ris_short_broken_eval_block_]->{$type_sequence_j} = 1;
+        }
     }
-    return 0;
+    return;
 } ## end sub starting_one_line_block
 
 sub unstore_token_to_go {
@@ -14152,15 +15318,6 @@ sub compare_indentation_levels {
         @break_before_or_after_token{@q} = (1) x scalar(@q);
     }
 
-    # This is no longer called - global vars - moved into initialize_batch_vars
-    sub initialize_forced_breakpoint_vars {
-        $forced_breakpoint_count      = 0;
-        $index_max_forced_break       = UNDEFINED_INDEX;
-        $forced_breakpoint_undo_count = 0;
-        ##@forced_breakpoint_undo_stack = (); # not needed
-        return;
-    }
-
     sub set_fake_breakpoint {
 
         # Just bump up the breakpoint count as a signal that there are breaks.
@@ -14350,11 +15507,12 @@ EOM
 
             # shouldn't happen, but not a critical error
             else {
-                DEBUG_UNDOBP && do {
+                if (DEVEL_MODE) {
                     my ( $a, $b, $c ) = caller();
-                    print STDOUT
-"Program Bug: undo_forced_breakpoint from $a $c has i=$i but max=$max_index_to_go";
-                };
+                    Fault(<<EOM);
+Program Bug: undo_forced_breakpoint from $a $c has i=$i but max=$max_index_to_go
+EOM
+                }
             }
         }
         return;
@@ -14421,9 +15579,8 @@ EOM
     my $peak_batch_size;
     my $batch_count;
 
-    # variables to keep track of unbalanced containers.
+    # variables to keep track of indentation of unmatched containers.
     my %saved_opening_indentation;
-    my @unmatched_opening_indexes_in_this_batch;
 
     sub initialize_grind_batch_of_CODE {
         @nonblank_lines_at_depth   = ();
@@ -14502,12 +15659,27 @@ EOM
         return;
     } ## end sub check_grind_input
 
+    # This filter speeds up a critical if-test
+    my %quick_filter;
+
+    BEGIN {
+        my @q = qw# L { ( [ R ] ) } ? : f => #;
+        push @q, ',';
+        @quick_filter{@q} = (1) x scalar(@q);
+    }
+
     sub grind_batch_of_CODE {
 
         my ($self) = @_;
 
+        #-----------------------------------------------------------------
+        # This sub directs the formatting of one complete batch of tokens.
+        # The tokens of the batch are in the '_to_go' arrays.
+        #-----------------------------------------------------------------
+
         my $this_batch = $self->[_this_batch_];
-        $batch_count++;
+        $this_batch->[_peak_batch_size_] = $peak_batch_size;
+        $this_batch->[_batch_count_]     = ++$batch_count;
 
         $self->check_grind_input() if (DEVEL_MODE);
 
@@ -14539,26 +15711,18 @@ EOM
 
         return if ( $max_index_to_go < 0 );
 
-        $self->set_lp_indentation()
-          if ($rOpts_line_up_parentheses);
+        if ($rOpts_line_up_parentheses) {
+            $self->set_lp_indentation();
+        }
 
-        #----------------------------
+        #--------------------------------------------------
         # Shortcut for block comments
-        #----------------------------
-        if (
-               $max_index_to_go == 0
-            && $types_to_go[0] eq '#'
-
-            # this shortcut does not work for -lp yet
-            && !$rOpts_line_up_parentheses
-          )
-        {
+        # Note that this shortcut does not work for -lp yet
+        #--------------------------------------------------
+        elsif ( !$max_index_to_go && $types_to_go[0] eq '#' ) {
             my $ibeg = 0;
             $this_batch->[_ri_first_]                 = [$ibeg];
             $this_batch->[_ri_last_]                  = [$ibeg];
-            $this_batch->[_peak_batch_size_]          = $peak_batch_size;
-            $this_batch->[_do_not_pad_]               = 0;
-            $this_batch->[_batch_count_]              = $batch_count;
             $this_batch->[_rix_seqno_controlling_ci_] = [];
 
             $self->convey_batch_to_vertical_aligner();
@@ -14576,9 +15740,7 @@ EOM
         # Normal route
         #-------------
 
-        my $rLL                      = $self->[_rLL_];
-        my $ris_seqno_controlling_ci = $self->[_ris_seqno_controlling_ci_];
-        my $rwant_container_open     = $self->[_rwant_container_open_];
+        my $rLL = $self->[_rLL_];
 
         #-------------------------------------------------------
         # Loop over the batch to initialize some batch variables
         my %comma_arrow_count;
         my $comma_arrow_count_contained = 0;
         my @unmatched_closing_indexes_in_this_batch;
+        my @unmatched_opening_indexes_in_this_batch;
 
-        @unmatched_opening_indexes_in_this_batch = ();
-
+        my @i_for_semicolon;
         foreach my $i ( 0 .. $max_index_to_go ) {
-            $iprev_to_go[$i] = $ilast_nonblank;
-            $inext_to_go[$i] = $i + 1;
+            $iprev_to_go[$i] = $ilast_nonblank;    # correct value
+            $inext_to_go[$i] = $i + 1;             # just a first guess
 
-            my $type = $types_to_go[$i];
-            if ( $type ne 'b' ) {
-                if ( $ilast_nonblank >= 0 ) {
-                    $inext_to_go[$ilast_nonblank] = $i;
+            next if ( $types_to_go[$i] eq 'b' );
 
-                    # just in case there are two blanks in a row (shouldn't
-                    # happen)
-                    if ( ++$ilast_nonblank < $i ) {
-                        $inext_to_go[$ilast_nonblank] = $i;
-                    }
-                }
-                $ilast_nonblank = $i;
+            if ( $ilast_nonblank >= 0 ) {
+                $inext_to_go[$ilast_nonblank] = $i;    # correction
+            }
+            $ilast_nonblank = $i;
 
-                # This is a good spot to efficiently collect information needed
-                # for breaking lines...
+            # This is an optional shortcut to save a bit of time by skipping
+            # most tokens.  Note: the filter may need to be updated if the
+            # next 'if' tests are ever changed to include more token types.
+            next if ( !$quick_filter{ $types_to_go[$i] } );
 
-                # gather info needed by sub break_long_lines
-                if ( $type_sequence_to_go[$i] ) {
-                    my $seqno = $type_sequence_to_go[$i];
-                    my $token = $tokens_to_go[$i];
+            my $type = $types_to_go[$i];
 
-                    # remember indexes of any tokens controlling xci
-                    # in this batch. This list is needed by sub undo_ci.
-                    if ( $ris_seqno_controlling_ci->{$seqno} ) {
-                        push @ix_seqno_controlling_ci, $i;
-                    }
+            # gather info needed by sub break_long_lines
+            if ( $type_sequence_to_go[$i] ) {
+                my $seqno = $type_sequence_to_go[$i];
+                my $token = $tokens_to_go[$i];
 
-                    if ( $is_opening_sequence_token{$token} ) {
-                        if ( $rwant_container_open->{$seqno} ) {
-                            $self->set_forced_breakpoint($i);
-                        }
-                        push @unmatched_opening_indexes_in_this_batch, $i;
-                        if ( $type eq '?' ) {
-                            push @colon_list, $type;
-                        }
+                # remember indexes of any tokens controlling xci
+                # in this batch. This list is needed by sub undo_ci.
+                if ( $self->[_ris_seqno_controlling_ci_]->{$seqno} ) {
+                    push @ix_seqno_controlling_ci, $i;
+                }
+
+                if ( $is_opening_sequence_token{$token} ) {
+                    if ( $self->[_rwant_container_open_]->{$seqno} ) {
+                        $self->set_forced_breakpoint($i);
                     }
-                    elsif ( $is_closing_sequence_token{$token} ) {
+                    push @unmatched_opening_indexes_in_this_batch, $i;
+                    if ( $type eq '?' ) {
+                        push @colon_list, $type;
+                    }
+                }
+                elsif ( $is_closing_sequence_token{$token} ) {
 
-                        if ( $i > 0 && $rwant_container_open->{$seqno} ) {
-                            $self->set_forced_breakpoint( $i - 1 );
-                        }
+                    if ( $i > 0 && $self->[_rwant_container_open_]->{$seqno} ) {
+                        $self->set_forced_breakpoint( $i - 1 );
+                    }
 
-                        my $i_mate =
-                          pop @unmatched_opening_indexes_in_this_batch;
-                        if ( defined($i_mate) && $i_mate >= 0 ) {
-                            if ( $type_sequence_to_go[$i_mate] ==
-                                $type_sequence_to_go[$i] )
-                            {
-                                $mate_index_to_go[$i]      = $i_mate;
-                                $mate_index_to_go[$i_mate] = $i;
-                                if ( $comma_arrow_count{$seqno} ) {
-                                    $comma_arrow_count_contained +=
-                                      $comma_arrow_count{$seqno};
-                                }
-                            }
-                            else {
-                                push @unmatched_opening_indexes_in_this_batch,
-                                  $i_mate;
-                                push @unmatched_closing_indexes_in_this_batch,
-                                  $i;
-                            }
+                    my $i_mate = pop @unmatched_opening_indexes_in_this_batch;
+                    if ( defined($i_mate) && $i_mate >= 0 ) {
+                        if ( $type_sequence_to_go[$i_mate] ==
+                            $type_sequence_to_go[$i] )
+                        {
+                            $mate_index_to_go[$i]      = $i_mate;
+                            $mate_index_to_go[$i_mate] = $i;
+                            my $cac = $comma_arrow_count{$seqno};
+                            $comma_arrow_count_contained += $cac if ($cac);
                         }
                         else {
+                            push @unmatched_opening_indexes_in_this_batch,
+                              $i_mate;
                             push @unmatched_closing_indexes_in_this_batch, $i;
                         }
-                        if ( $type eq ':' ) {
-                            push @colon_list, $type;
-                        }
-                    } ## end elsif ( $is_closing_sequence_token...)
+                    }
+                    else {
+                        push @unmatched_closing_indexes_in_this_batch, $i;
+                    }
+                    if ( $type eq ':' ) {
+                        push @colon_list, $type;
+                    }
+                } ## end elsif ( $is_closing_sequence_token...)
 
-                } ## end if ($seqno)
+            } ## end if ($seqno)
 
-                elsif ( $type eq ',' ) { $comma_count_in_batch++; }
-                elsif ( $tokens_to_go[$i] eq '=>' ) {
-                    if (@unmatched_opening_indexes_in_this_batch) {
-                        my $j = $unmatched_opening_indexes_in_this_batch[-1];
-                        my $seqno = $type_sequence_to_go[$j];
-                        $comma_arrow_count{$seqno}++;
-                    }
+            elsif ( $type eq ',' ) { $comma_count_in_batch++; }
+            elsif ( $type eq '=>' ) {
+                if (@unmatched_opening_indexes_in_this_batch) {
+                    my $j     = $unmatched_opening_indexes_in_this_batch[-1];
+                    my $seqno = $type_sequence_to_go[$j];
+                    $comma_arrow_count{$seqno}++;
                 }
-            } ## end if ( $type ne 'b' )
+            }
+            elsif ( $type eq 'f' ) {
+                push @i_for_semicolon, $i;
+            }
+
         } ## end for ( my $i = 0 ; $i <=...)
 
+        # Break at a single interior C-style for semicolon in this batch (c154)
+        if ( @i_for_semicolon && @i_for_semicolon == 1 ) {
+            my $i     = $i_for_semicolon[0];
+            my $inext = $inext_to_go[$i];
+            if ( $inext <= $max_index_to_go && $types_to_go[$inext] ne '#' ) {
+                $self->set_forced_breakpoint($i);
+            }
+        }
+
         my $is_unbalanced_batch = @unmatched_opening_indexes_in_this_batch +
           @unmatched_closing_indexes_in_this_batch;
 
+        if (@unmatched_opening_indexes_in_this_batch) {
+            $this_batch->[_runmatched_opening_indexes_] =
+              \@unmatched_opening_indexes_in_this_batch;
+        }
+
         #------------------------
         # Set special breakpoints
         #------------------------
@@ -14691,7 +15864,7 @@ EOM
         # blocks on one line.  This is very rare but can happen for
         # user-defined subs.  For example we might be looking at this:
         #  BOOL { $server_data{uptime} > 0; } NUM { $server_data{load}; } STR {
-        my $saw_good_break = 0;    # flag to force breaks even if short line
+        my $saw_good_break;    # flag to force breaks even if short line
         if (
 
             # looking for opening or closing block brace
@@ -14756,16 +15929,16 @@ EOM
         my $last_last_line_leading_level =
           $self->[_last_last_line_leading_level_];
 
-        # add a blank line before certain key types but not after a comment
+        # add blank line(s) before certain key types but not after a comment
         if ( $last_line_leading_type ne '#' ) {
-            my $want_blank    = 0;
+            my $blank_count   = 0;
             my $leading_token = $tokens_to_go[$imin];
             my $leading_type  = $types_to_go[$imin];
 
             # break before certain key blocks except one-liners
             if ( $leading_type eq 'k' ) {
                 if ( $leading_token eq 'BEGIN' || $leading_token eq 'END' ) {
-                    $want_blank = $rOpts->{'blank-lines-before-subs'}
+                    $blank_count = $rOpts->{'blank-lines-before-subs'}
                       if ( terminal_type_i( $imin, $imax ) ne '}' );
                 }
 
@@ -14784,12 +15957,14 @@ EOM
                         $lc = 0;
                     }
 
-                    $want_blank =
-                         $rOpts->{'blanks-before-blocks'}
-                      && $lc >= $rOpts->{'long-block-line-count'}
-                      && $self->consecutive_nonblank_lines() >=
-                      $rOpts->{'long-block-line-count'}
-                      && terminal_type_i( $imin, $imax ) ne '}';
+                    if (   $rOpts->{'blanks-before-blocks'}
+                        && $lc >= $rOpts->{'long-block-line-count'}
+                        && $self->consecutive_nonblank_lines() >=
+                        $rOpts->{'long-block-line-count'}
+                        && terminal_type_i( $imin, $imax ) ne '}' )
+                    {
+                        $blank_count = 1;
+                    }
                 }
             }
 
@@ -14807,13 +15982,17 @@ EOM
                     && $leading_token =~ /$SUB_PATTERN/
                   )
                 {
-                    $want_blank = $rOpts->{'blank-lines-before-subs'}
+                    $blank_count = $rOpts->{'blank-lines-before-subs'}
                       if ( terminal_type_i( $imin, $imax ) !~ /^[\;\}\,]$/ );
                 }
 
                 # break before all package declarations
                 elsif ( substr( $leading_token, 0, 8 ) eq 'package ' ) {
-                    $want_blank = $rOpts->{'blank-lines-before-packages'};
+
+                    # ... except in a very short eval block
+                    my $pseqno = $parent_seqno_to_go[$imin];
+                    $blank_count = $rOpts->{'blank-lines-before-packages'}
+                      if ( !$self->[_ris_short_broken_eval_block_]->{$pseqno} );
                 }
             }
 
@@ -14825,18 +16004,18 @@ EOM
                     /$blank_lines_before_closing_block_pattern/ )
                 {
                     my $nblanks = $rOpts->{'blank-lines-before-closing-block'};
-                    if ( $nblanks > $want_blank ) {
-                        $want_blank = $nblanks;
+                    if ( $nblanks > $blank_count ) {
+                        $blank_count = $nblanks;
                     }
                 }
             }
 
-            if ($want_blank) {
+            if ($blank_count) {
 
-                # future: send blank line down normal path to VerticalAligner
+                # future: send blank line down normal path to VerticalAligner?
                 $self->flush_vertical_aligner();
                 my $file_writer_object = $self->[_file_writer_object_];
-                $file_writer_object->require_blank_code_lines($want_blank);
+                $file_writer_object->require_blank_code_lines($blank_count);
             }
         }
 
@@ -14873,21 +16052,22 @@ EOM
         my $called_pad_array_to_go;
 
         # set all forced breakpoints for good list formatting
-        my $is_long_line = $max_index_to_go > 0
-          && $self->excess_line_length( $imin, $max_index_to_go ) > 0;
-
-        my $old_line_count_in_batch = 1;
+        my $is_long_line;
+        my $multiple_old_lines_in_batch;
         if ( $max_index_to_go > 0 ) {
+            $is_long_line =
+              $self->excess_line_length( $imin, $max_index_to_go ) > 0;
+
             my $Kbeg = $K_to_go[0];
             my $Kend = $K_to_go[$max_index_to_go];
-            $old_line_count_in_batch +=
+            $multiple_old_lines_in_batch =
               $rLL->[$Kend]->[_LINE_INDEX_] - $rLL->[$Kbeg]->[_LINE_INDEX_];
         }
 
         my $rbond_strength_bias = [];
         if (
                $is_long_line
-            || $old_line_count_in_batch > 1
+            || $multiple_old_lines_in_batch
 
             # must always call break_lists() with unbalanced batches because
             # it is maintaining some stacks
@@ -14919,97 +16099,97 @@ EOM
         # first and last tokens of line fragments to output..
         my ( $ri_first, $ri_last );
 
-        #-------------------------
-        # write a single line if..
-        #-------------------------
-        if (
+        #-----------------------------
+        # a single token uses one line
+        #-----------------------------
+        if ( !$max_index_to_go ) {
+            $ri_first = [$imin];
+            $ri_last  = [$imax];
+        }
+
+        # for multiple tokens
+        else {
 
-            # we aren't allowed to add any newlines
-            !$rOpts_add_newlines
+            #-------------------------
+            # write a single line if..
+            #-------------------------
+            if (
+                (
 
-            # or,
-            || (
+                    # this line is 'short'
+                    !$is_long_line
 
-                # this line is 'short'
-                !$is_long_line
+                    # and we didn't see a good breakpoint
+                    && !$saw_good_break
 
-                # and we didn't see a good breakpoint
-                && !$saw_good_break
+                    # and we don't already have an interior breakpoint
+                    && !$forced_breakpoint_count
+                )
 
-                # and we don't already have an interior breakpoint
-                && !$forced_breakpoint_count
-            )
-          )
-        {
-            @{$ri_first} = ($imin);
-            @{$ri_last}  = ($imax);
-        }
+                # or, we aren't allowed to add any newlines
+                || !$rOpts_add_newlines
 
-        #-----------------------------
-        # otherwise use multiple lines
-        #-----------------------------
-        else {
+              )
+            {
+                $ri_first = [$imin];
+                $ri_last  = [$imax];
+            }
 
-            # add a couple of extra terminal blank tokens if we haven't
-            # already done so
-            $self->pad_array_to_go() unless ($called_pad_array_to_go);
+            #-----------------------------
+            # otherwise use multiple lines
+            #-----------------------------
+            else {
 
-            ( $ri_first, $ri_last, my $rbond_strength_to_go ) =
-              $self->break_long_lines( $saw_good_break, \@colon_list,
-                $rbond_strength_bias );
+                # add a couple of extra terminal blank tokens if we haven't
+                # already done so
+                $self->pad_array_to_go() unless ($called_pad_array_to_go);
 
-            $self->break_all_chain_tokens( $ri_first, $ri_last );
+                ( $ri_first, $ri_last, my $rbond_strength_to_go ) =
+                  $self->break_long_lines( $saw_good_break, \@colon_list,
+                    $rbond_strength_bias );
 
-            $self->break_equals( $ri_first, $ri_last );
+                $self->break_all_chain_tokens( $ri_first, $ri_last );
 
-            # now we do a correction step to clean this up a bit
-            # (The only time we would not do this is for debugging)
-            $self->recombine_breakpoints( $ri_first, $ri_last,
-                $rbond_strength_to_go )
-              if ( $rOpts_recombine && @{$ri_first} > 1 );
+                $self->break_equals( $ri_first, $ri_last )
+                  if @{$ri_first} >= 3;
 
-            $self->insert_final_ternary_breaks( $ri_first, $ri_last )
-              if (@colon_list);
-        }
+                # now we do a correction step to clean this up a bit
+                # (The only time we would not do this is for debugging)
+                $self->recombine_breakpoints( $ri_first, $ri_last,
+                    $rbond_strength_to_go )
+                  if ( $rOpts_recombine && @{$ri_first} > 1 );
 
-        $self->insert_breaks_before_list_opening_containers( $ri_first,
-            $ri_last )
-          if ( %break_before_container_types && $max_index_to_go > 0 );
+                $self->insert_final_ternary_breaks( $ri_first, $ri_last )
+                  if (@colon_list);
+            }
 
-        #-------------------
-        # -lp corrector step
-        #-------------------
-        my $do_not_pad = 0;
-        if ($rOpts_line_up_parentheses) {
-            $do_not_pad = $self->correct_lp_indentation( $ri_first, $ri_last );
-        }
+            $self->insert_breaks_before_list_opening_containers( $ri_first,
+                $ri_last )
+              if ( %break_before_container_types && $max_index_to_go > 0 );
 
-        #--------------------------
-        # unmask phantom semicolons
-        #--------------------------
-        if ( !$tokens_to_go[$imax] && $types_to_go[$imax] eq ';' ) {
-            my $i       = $imax;
-            my $tok     = ';';
-            my $tok_len = 1;
-            if ( $want_left_space{';'} != WS_NO ) {
-                $tok     = ' ;';
-                $tok_len = 2;
+            # Check for a phantom semicolon at the end of the batch
+            if ( !$token_lengths_to_go[$imax] && $types_to_go[$imax] eq ';' ) {
+                $self->unmask_phantom_token($imax);
+            }
+
+            if ( $rOpts_one_line_block_semicolons == 0 ) {
+                $self->delete_one_line_semicolons( $ri_first, $ri_last );
             }
-            $tokens_to_go[$i]        = $tok;
-            $token_lengths_to_go[$i] = $tok_len;
-            my $KK = $K_to_go[$i];
-            $rLL->[$KK]->[_TOKEN_]        = $tok;
-            $rLL->[$KK]->[_TOKEN_LENGTH_] = $tok_len;
-            my $line_number = 1 + $rLL->[$KK]->[_LINE_INDEX_];
-            $self->note_added_semicolon($line_number);
 
-            foreach ( $imax .. $max_index_to_go ) {
-                $summed_lengths_to_go[ $_ + 1 ] += $tok_len;
+            # Remember the largest batch size processed. This is needed by the
+            # logical padding routine to avoid padding the first nonblank token
+            if ( $max_index_to_go > $peak_batch_size ) {
+                $peak_batch_size = $max_index_to_go;
             }
         }
 
-        if ( $rOpts_one_line_block_semicolons == 0 ) {
-            $self->delete_one_line_semicolons( $ri_first, $ri_last );
+        #-------------------
+        # -lp corrector step
+        #-------------------
+        if ($rOpts_line_up_parentheses) {
+            my $do_not_pad =
+              $self->correct_lp_indentation( $ri_first, $ri_last );
+            $this_batch->[_do_not_pad_] = $do_not_pad;
         }
 
         #--------------------
@@ -15017,9 +16197,6 @@ EOM
         #--------------------
         $this_batch->[_ri_first_]                 = $ri_first;
         $this_batch->[_ri_last_]                  = $ri_last;
-        $this_batch->[_peak_batch_size_]          = $peak_batch_size;
-        $this_batch->[_do_not_pad_]               = $do_not_pad;
-        $this_batch->[_batch_count_]              = $batch_count;
         $this_batch->[_rix_seqno_controlling_ci_] = \@ix_seqno_controlling_ci;
 
         $self->convey_batch_to_vertical_aligner();
@@ -15048,14 +16225,49 @@ EOM
             }
         }
 
-        # Remember the largest batch size processed. This is needed by the
-        # logical padding routine to avoid padding the first nonblank token
-        if ( $max_index_to_go && $max_index_to_go > $peak_batch_size ) {
-            $peak_batch_size = $max_index_to_go;
+        return;
+    } ## end sub grind_batch_of_CODE
+
+    sub unmask_phantom_token {
+        my ( $self, $iend ) = @_;
+
+        # Turn a phantom token into a real token.
+
+        # Input parameter:
+        #   $iend = the index in the output batch array of this token.
+
+        # Phantom tokens are specially marked token types (such as ';')  with
+        # no token text which only become real tokens if they occur at the end
+        # of an output line.  At one time phantom ',' tokens were handled
+        # here, but now they are processed elsewhere.
+
+        my $rLL         = $self->[_rLL_];
+        my $KK          = $K_to_go[$iend];
+        my $line_number = 1 + $rLL->[$KK]->[_LINE_INDEX_];
+
+        my $type = $types_to_go[$iend];
+        return unless ( $type eq ';' );
+        my $tok     = $type;
+        my $tok_len = length($tok);
+        if ( $want_left_space{$type} != WS_NO ) {
+            $tok = SPACE . $tok;
+            $tok_len += 1;
         }
 
+        $tokens_to_go[$iend]        = $tok;
+        $token_lengths_to_go[$iend] = $tok_len;
+
+        $rLL->[$KK]->[_TOKEN_]        = $tok;
+        $rLL->[$KK]->[_TOKEN_LENGTH_] = $tok_len;
+
+        $self->note_added_semicolon($line_number);
+
+        # This changes the summed lengths of the rest of this batch
+        foreach ( $iend .. $max_index_to_go ) {
+            $summed_lengths_to_go[ $_ + 1 ] += $tok_len;
+        }
         return;
-    } ## end sub grind_batch_of_CODE
+    }
 
     sub save_opening_indentation {
 
@@ -15063,7 +16275,12 @@ EOM
         # saves indentations of lines of all unmatched opening tokens.
         # These will be used by sub get_opening_indentation.
 
-        my ( $self, $ri_first, $ri_last, $rindentation_list ) = @_;
+        my ( $self, $ri_first, $ri_last, $rindentation_list,
+            $runmatched_opening_indexes )
+          = @_;
+
+        $runmatched_opening_indexes = []
+          if ( !defined($runmatched_opening_indexes) );
 
         # QW INDENTATION PATCH 1:
         # Also save indentation for multiline qw quotes
@@ -15080,7 +16297,7 @@ EOM
 
         # we need to save indentations of any unmatched opening tokens
         # in this batch because we may need them in a subsequent batch.
-        foreach ( @unmatched_opening_indexes_in_this_batch, @i_qw ) {
+        foreach ( @{$runmatched_opening_indexes}, @i_qw ) {
 
             my $seqno = $type_sequence_to_go[$_];
 
@@ -15092,6 +16309,7 @@ EOM
 
                     # shouldn't happen
                     $seqno = 'UNKNOWN';
+                    DEVEL_MODE && Fault("unable to find sequence number\n");
                 }
             }
 
@@ -15325,6 +16543,7 @@ sub break_all_chain_tokens {
 
     # now look for any interior tokens of the same types
     $count = 0;
+    my $has_interior_dot_or_plus;
     for my $n ( 0 .. $nmax ) {
         my $il = $ri_left->[$n];
         my $ir = $ri_right->[$n];
@@ -15336,21 +16555,27 @@ sub break_all_chain_tokens {
             if ( $saw_chain_type{$key} ) {
                 push @{ $interior_chain_type{$key} }, $i;
                 $count++;
+                $has_interior_dot_or_plus ||= ( $key eq '.' || $key eq '+' );
             }
         }
     }
     return unless $count;
 
+    my @keys = keys %saw_chain_type;
+
+    # quit if just ONE continuation line with leading .  For example--
+    # print LATEXFILE '\framebox{\parbox[c][' . $h . '][t]{' . $w . '}{'
+    #  . $contents;
+    # Fixed for b1399.
+    if ( $has_interior_dot_or_plus && $nmax == 1 && @keys == 1 ) {
+        return;
+    }
+
     # now make a list of all new break points
     my @insert_list;
 
     # loop over all chain types
-    foreach my $key ( keys %saw_chain_type ) {
-
-        # quit if just ONE continuation line with leading .  For example--
-        # print LATEXFILE '\framebox{\parbox[c][' . $h . '][t]{' . $w . '}{'
-        #  . $contents;
-        last if ( $nmax == 1 && $key =~ /^[\.\+]$/ );
+    foreach my $key (@keys) {
 
         # loop over all interior chain tokens
         foreach my $itest ( @{ $interior_chain_type{$key} } ) {
@@ -15784,7 +17009,7 @@ sub break_equals {
         # That's the task of this routine.
 
         # do nothing under extreme stress
-        return if ( $stress_level_alpha < 1 && !DEVEL_MODE );
+        return if ( $high_stress_level < 1 );
 
         my $rK_weld_right = $self->[_rK_weld_right_];
         my $rK_weld_left  = $self->[_rK_weld_left_];
@@ -15792,12 +17017,15 @@ sub break_equals {
         my $nmax_start = @{$ri_end} - 1;
         return if ( $nmax_start <= 0 );
 
-        # Make a list of all good joining tokens between the lines
+        #----------------------------------------------------------------
+        # Break into small sub-sections to decrease the maximum n-squared
+        # operations and avoid excess run time. See comments below.
+        #----------------------------------------------------------------
+
+        # Also make a list of all good joining tokens between the lines
         # n-1 and n.
         my @joint;
 
-        # Break the total batch sub-sections with lengths short enough to
-        # recombine
         my $rsections = [];
         my $nbeg_sec  = 0;
         my $nend_sec;
@@ -15808,7 +17036,7 @@ sub break_equals {
             my $iend_2 = $ri_end->[$nn];
             my $ibeg_2 = $ri_beg->[$nn];
 
-            # Define the joint variable
+            # Define certain good joint tokens
             my ( $itok, $itokp, $itokm );
             foreach my $itest ( $iend_1, $ibeg_2 ) {
                 my $type = $types_to_go[$itest];
@@ -15846,6 +17074,7 @@ sub break_equals {
                 $nbeg_sec = $nn;
             }
         }
+
         if ( defined($nend_sec) ) {
             push @{$rsections}, [ $nbeg_sec, $nend_sec ];
             my $num = $nend_sec - $nbeg_sec;
@@ -15889,6 +17118,7 @@ sub break_equals {
         # Loop over all sub-sections.  Note that we have to work backwards
         # from the end of the batch since the sections use original line
         # numbers, and the line numbers change as we go.
+      OUTER_LOOP:
         while ( my $section = pop @{$rsections} ) {
             my ( $nbeg, $nend ) = @{$section};
 
@@ -15908,7 +17138,7 @@ sub break_equals {
                 # Safety check for excess total iterations
                 $it_count++;
                 if ( $it_count > $it_count_max ) {
-                    goto RETURN;
+                    last OUTER_LOOP;
                 }
 
                 my $n_best = 0;
@@ -15931,8 +17161,13 @@ sub break_equals {
                 }
                 $nmax_last  = $nmax;
                 $more_to_do = 0;
-                my $skip_Section_3;
-                my $leading_amp_count = 0;
+
+                # Count lines with leading &&, ||, :, at any level.
+                # This is used to avoid some recombinations which might
+                # be hard to read.
+                my $rleading_amp_count;
+                ${$rleading_amp_count} = 0;
+
                 my $this_line_is_semicolon_terminated;
 
                 # loop over all remaining lines in this batch
@@ -15961,8 +17196,8 @@ sub break_equals {
                     # between the tokens at $iend_1 and $ibeg_2
                     #
                     # We will apply a number of ad-hoc tests to see if joining
-                    # here will look ok.  The code will just issue a 'next'
-                    # command if the join doesn't look good.  If we get through
+                    # here will look ok.  The code will just move to the next
+                    # pair if the join doesn't look good.  If we get through
                     # the gauntlet of tests, the lines will be recombined.
                     #----------------------------------------------------------
                     #
@@ -15973,989 +17208,1109 @@ sub break_equals {
                     my $ibeg_2    = $ri_beg->[$n];
                     my $ibeg_nmax = $ri_beg->[$nmax];
 
-                    # combined line cannot be too long
-                    my $excess =
-                      $self->excess_line_length( $ibeg_1, $iend_2, 1 );
-                    next if ( $excess > 0 );
+                    # combined line cannot be too long
+                    my $excess =
+                      $self->excess_line_length( $ibeg_1, $iend_2, 1 );
+                    next if ( $excess > 0 );
+
+                    my $type_iend_1 = $types_to_go[$iend_1];
+                    my $type_iend_2 = $types_to_go[$iend_2];
+                    my $type_ibeg_1 = $types_to_go[$ibeg_1];
+                    my $type_ibeg_2 = $types_to_go[$ibeg_2];
+
+                    # terminal token of line 2 if any side comment is ignored:
+                    my $iend_2t      = $iend_2;
+                    my $type_iend_2t = $type_iend_2;
+
+                    DEBUG_RECOMBINE > 1 && do {
+                        print STDERR
+"RECOMBINE: n=$n imid=$iend_1 if=$ibeg_1 type=$type_ibeg_1 =$tokens_to_go[$ibeg_1] next_type=$type_ibeg_2 next_tok=$tokens_to_go[$ibeg_2]\n";
+                    };
+
+                    # If line $n is the last line, we set some flags and
+                    # do any special checks for it
+                    if ( $n == $nmax ) {
+
+                        # a terminal '{' should stay where it is
+                        # unless preceded by a fat comma
+                        next if ( $type_ibeg_2 eq '{' && $type_iend_1 ne '=>' );
+
+                        if (   $type_iend_2 eq '#'
+                            && $iend_2 - $ibeg_2 >= 2
+                            && $types_to_go[ $iend_2 - 1 ] eq 'b' )
+                        {
+                            $iend_2t      = $iend_2 - 2;
+                            $type_iend_2t = $types_to_go[$iend_2t];
+                        }
+
+                        $this_line_is_semicolon_terminated =
+                          $type_iend_2t eq ';';
+                    }
+
+                    #----------------------------------------------------------
+                    # Recombine Section 0:
+                    # Examine the special token joining this line pair, if any.
+                    # Put as many tests in this section to avoid duplicate code
+                    # and to make formatting independent of whether breaks are
+                    # to the left or right of an operator.
+                    #----------------------------------------------------------
+
+                    # Note that parens around ($itok) are essential here:
+                    my ($itok) = @{ $joint[$n] };
+                    if ($itok) {
+                        my $ok_0 =
+                          recombine_section_0( $itok, $ri_beg, $ri_end, $n,
+                            $rleading_amp_count );
+                        next if ( !$ok_0 );
+                    }
+
+                    #----------------------------------------------------------
+                    # Recombine Section 1:
+                    # Join welded nested containers immediately
+                    #----------------------------------------------------------
+
+                    if (
+                        $total_weld_count
+                        && ( $type_sequence_to_go[$iend_1]
+                            && defined( $rK_weld_right->{ $K_to_go[$iend_1] } )
+                            || $type_sequence_to_go[$ibeg_2]
+                            && defined( $rK_weld_left->{ $K_to_go[$ibeg_2] } ) )
+                      )
+                    {
+                        $n_best = $n;
+                        last;
+                    }
+
+                    $reverse = 0;
+
+                    #----------------------------------------------------------
+                    # Recombine Section 2:
+                    # Examine token at $iend_1 (right end of first line of pair)
+                    #----------------------------------------------------------
+
+                    my ( $ok_2, $skip_Section_3 ) =
+                      recombine_section_2( $ri_beg, $ri_end, $n,
+                        $this_line_is_semicolon_terminated,
+                        $rleading_amp_count );
+                    next if ( !$ok_2 );
+
+                    #----------------------------------------------------------
+                    # Recombine Section 3:
+                    # Examine token at $ibeg_2 (left end of second line of pair)
+                    #----------------------------------------------------------
+
+                    # Join lines identified above as capable of
+                    # causing an outdented line with leading closing paren.
+                    # Note that we are skipping the rest of this section
+                    # and the rest of the loop to do the join.
+                    if ($skip_Section_3) {
+                        $forced_breakpoint_to_go[$iend_1] = 0;
+                        $n_best = $n;
+                        last;
+                    }
+
+                    my ( $ok_3, $bs_tweak ) =
+                      recombine_section_3( $ri_beg, $ri_end, $n,
+                        $this_line_is_semicolon_terminated,
+                        $rleading_amp_count );
+                    next if ( !$ok_3 );
+
+                    #----------------------------------------------------------
+                    # Recombine Section 4:
+                    # Combine the lines if we arrive here and it is possible
+                    #----------------------------------------------------------
+
+                    # honor hard breakpoints
+                    next if ( $forced_breakpoint_to_go[$iend_1] > 0 );
+
+                    my $bs = $rbond_strength_to_go->[$iend_1] + $bs_tweak;
+
+                    # Require a few extra spaces before recombining lines if we
+                    # are at an old breakpoint unless this is a simple list or
+                    # terminal line.  The goal is to avoid oscillating between
+                    # two quasi-stable end states.  For example this snippet
+                    # caused problems:
+
+##    my $this =
+##    bless {
+##        TText => "[" . ( join ',', map { "\"$_\"" } split "\n", $_ ) . "]"
+##      },
+##      $type;
+                    next
+                      if ( $old_breakpoint_to_go[$iend_1]
+                        && !$this_line_is_semicolon_terminated
+                        && $n < $nmax
+                        && $excess + 4 > 0
+                        && $type_iend_2 ne ',' );
+
+                    # do not recombine if we would skip in indentation levels
+                    if ( $n < $nmax ) {
+                        my $if_next = $ri_beg->[ $n + 1 ];
+                        next
+                          if (
+                               $levels_to_go[$ibeg_1] < $levels_to_go[$ibeg_2]
+                            && $levels_to_go[$ibeg_2] < $levels_to_go[$if_next]
+
+                            # but an isolated 'if (' is undesirable
+                            && !(
+                                   $n == 1
+                                && $iend_1 - $ibeg_1 <= 2
+                                && $type_ibeg_1 eq 'k'
+                                && $tokens_to_go[$ibeg_1] eq 'if'
+                                && $tokens_to_go[$iend_1] ne '('
+                            )
+                          );
+                    }
+
+                    ## OLD: honor no-break's
+                    ## next if ( $bs >= NO_BREAK - 1 );  # removed for b1257
+
+                    # remember the pair with the greatest bond strength
+                    if ( !$n_best ) {
+                        $n_best  = $n;
+                        $bs_best = $bs;
+                    }
+                    else {
+
+                        if ( $bs > $bs_best ) {
+                            $n_best  = $n;
+                            $bs_best = $bs;
+                        }
+                    }
+                }
+
+                # recombine the pair with the greatest bond strength
+                if ($n_best) {
+                    splice @{$ri_beg}, $n_best,     1;
+                    splice @{$ri_end}, $n_best - 1, 1;
+                    splice @joint,     $n_best,     1;
+
+                    # keep going if we are still making progress
+                    $more_to_do++;
+                }
+            }    # end iteration loop
+
+        }    # end loop over sections
+
+        if (DEBUG_RECOMBINE) {
+            my $nmax_last = @{$ri_end} - 1;
+            print STDERR
+"exiting recombine with $nmax_last lines, starting lines=$nmax_start, iterations=$it_count, max_it=$it_count_max numsec=$num_sections\n";
+        }
+        return;
+    } ## end sub recombine_breakpoints
+
+    sub recombine_section_0 {
+        my ( $itok, $ri_beg, $ri_end, $n, $rleading_amp_count ) = @_;
 
-                    my $type_iend_1 = $types_to_go[$iend_1];
-                    my $type_iend_2 = $types_to_go[$iend_2];
-                    my $type_ibeg_1 = $types_to_go[$ibeg_1];
-                    my $type_ibeg_2 = $types_to_go[$ibeg_2];
+        # Recombine Section 0:
+        # Examine special candidate joining token $itok
 
-                    # terminal token of line 2 if any side comment is ignored:
-                    my $iend_2t      = $iend_2;
-                    my $type_iend_2t = $type_iend_2;
+        # Given:
+        #  $itok = index of token at a possible join of lines $n-1 and $n
 
-                    # some beginning indexes of other lines, which may not exist
-                    my $ibeg_0 = $n > 1          ? $ri_beg->[ $n - 2 ] : -1;
-                    my $ibeg_3 = $n < $nmax      ? $ri_beg->[ $n + 1 ] : -1;
-                    my $ibeg_4 = $n + 2 <= $nmax ? $ri_beg->[ $n + 2 ] : -1;
+        # Return:
+        #  true  => ok to combine
+        #  false => do not combine lines
 
-                    my $bs_tweak = 0;
+        # Here are Indexes of the endpoint tokens of the two lines:
+        #
+        #  -----line $n-1--- | -----line $n-----
+        #  $ibeg_1   $iend_1 | $ibeg_2   $iend_2
+        #              ^         ^
+        #              |         |
+        #              ------------$itok is one of these tokens
 
-                    #my $depth_increase=( $nesting_depth_to_go[$ibeg_2] -
-                    #        $nesting_depth_to_go[$ibeg_1] );
+        # Put as many tests in this section to avoid duplicate code
+        # and to make formatting independent of whether breaks are
+        # to the left or right of an operator.
 
-                    DEBUG_RECOMBINE > 1 && do {
-                        print STDERR
-"RECOMBINE: n=$n imid=$iend_1 if=$ibeg_1 type=$type_ibeg_1 =$tokens_to_go[$ibeg_1] next_type=$type_ibeg_2 next_tok=$tokens_to_go[$ibeg_2]\n";
-                    };
+        my $nmax   = @{$ri_end} - 1;
+        my $ibeg_1 = $ri_beg->[ $n - 1 ];
+        my $iend_1 = $ri_end->[ $n - 1 ];
+        my $ibeg_2 = $ri_beg->[$n];
+        my $iend_2 = $ri_end->[$n];
 
-                    # If line $n is the last line, we set some flags and
-                    # do any special checks for it
-                    if ( $n == $nmax ) {
+        if ($itok) {
 
-                        # a terminal '{' should stay where it is
-                        # unless preceded by a fat comma
-                        next if ( $type_ibeg_2 eq '{' && $type_iend_1 ne '=>' );
+            my $type = $types_to_go[$itok];
 
-                        if (   $type_iend_2 eq '#'
-                            && $iend_2 - $ibeg_2 >= 2
-                            && $types_to_go[ $iend_2 - 1 ] eq 'b' )
-                        {
-                            $iend_2t      = $iend_2 - 2;
-                            $type_iend_2t = $types_to_go[$iend_2t];
-                        }
+            if ( $type eq ':' ) {
 
-                        $this_line_is_semicolon_terminated =
-                          $type_iend_2t eq ';';
-                    }
+                # do not join at a colon unless it disobeys the
+                # break request
+                if ( $itok eq $iend_1 ) {
+                    return unless $want_break_before{$type};
+                }
+                else {
+                    ${$rleading_amp_count}++;
+                    return if $want_break_before{$type};
+                }
+            } ## end if ':'
 
-                    #----------------------------------------------------------
-                    # Recombine Section 0:
-                    # Examine the special token joining this line pair, if any.
-                    # Put as many tests in this section to avoid duplicate code
-                    # and to make formatting independent of whether breaks are
-                    # to the left or right of an operator.
-                    #----------------------------------------------------------
+            # handle math operators + - * /
+            elsif ( $is_math_op{$type} ) {
 
-                    my ($itok) = @{ $joint[$n] };
-                    if ($itok) {
+                # Combine these lines if this line is a single
+                # number, or if it is a short term with same
+                # operator as the previous line.  For example, in
+                # the following code we will combine all of the
+                # short terms $A, $B, $C, $D, $E, $F, together
+                # instead of leaving them one per line:
+                #  my $time =
+                #    $A * $B * $C * $D * $E * $F *
+                #    ( 2. * $eps * $sigma * $area ) *
+                #    ( 1. / $tcold**3 - 1. / $thot**3 );
 
-                        my $type = $types_to_go[$itok];
+                # This can be important in math-intensive code.
 
-                        if ( $type eq ':' ) {
+                my $good_combo;
 
-                            # do not join at a colon unless it disobeys the
-                            # break request
-                            if ( $itok eq $iend_1 ) {
-                                next unless $want_break_before{$type};
-                            }
-                            else {
-                                $leading_amp_count++;
-                                next if $want_break_before{$type};
-                            }
-                        } ## end if ':'
-
-                        # handle math operators + - * /
-                        elsif ( $is_math_op{$type} ) {
-
-                            # Combine these lines if this line is a single
-                            # number, or if it is a short term with same
-                            # operator as the previous line.  For example, in
-                            # the following code we will combine all of the
-                            # short terms $A, $B, $C, $D, $E, $F, together
-                            # instead of leaving them one per line:
-                            #  my $time =
-                            #    $A * $B * $C * $D * $E * $F *
-                            #    ( 2. * $eps * $sigma * $area ) *
-                            #    ( 1. / $tcold**3 - 1. / $thot**3 );
-
-                            # This can be important in math-intensive code.
-
-                            my $good_combo;
-
-                            my $itokp  = min( $inext_to_go[$itok],  $iend_2 );
-                            my $itokpp = min( $inext_to_go[$itokp], $iend_2 );
-                            my $itokm  = max( $iprev_to_go[$itok],  $ibeg_1 );
-                            my $itokmm = max( $iprev_to_go[$itokm], $ibeg_1 );
-
-                            # check for a number on the right
-                            if ( $types_to_go[$itokp] eq 'n' ) {
-
-                                # ok if nothing else on right
-                                if ( $itokp == $iend_2 ) {
-                                    $good_combo = 1;
-                                }
-                                else {
-
-                                    # look one more token to right..
-                                    # okay if math operator or some termination
-                                    $good_combo =
-                                      ( ( $itokpp == $iend_2 )
-                                          && $is_math_op{ $types_to_go[$itokpp]
-                                          } )
-                                      || $types_to_go[$itokpp] =~ /^[#,;]$/;
-                                }
-                            }
+                my $itokp  = min( $inext_to_go[$itok],  $iend_2 );
+                my $itokpp = min( $inext_to_go[$itokp], $iend_2 );
+                my $itokm  = max( $iprev_to_go[$itok],  $ibeg_1 );
+                my $itokmm = max( $iprev_to_go[$itokm], $ibeg_1 );
 
-                            # check for a number on the left
-                            if ( !$good_combo && $types_to_go[$itokm] eq 'n' ) {
+                # check for a number on the right
+                if ( $types_to_go[$itokp] eq 'n' ) {
 
-                                # okay if nothing else to left
-                                if ( $itokm == $ibeg_1 ) {
-                                    $good_combo = 1;
-                                }
+                    # ok if nothing else on right
+                    if ( $itokp == $iend_2 ) {
+                        $good_combo = 1;
+                    }
+                    else {
 
-                                # otherwise look one more token to left
-                                else {
+                        # look one more token to right..
+                        # okay if math operator or some termination
+                        $good_combo =
+                          ( ( $itokpp == $iend_2 )
+                              && $is_math_op{ $types_to_go[$itokpp] } )
+                          || $types_to_go[$itokpp] =~ /^[#,;]$/;
+                    }
+                }
 
-                                   # okay if math operator, comma, or assignment
-                                    $good_combo = ( $itokmm == $ibeg_1 )
-                                      && ( $is_math_op{ $types_to_go[$itokmm] }
-                                        || $types_to_go[$itokmm] =~ /^[,]$/
-                                        || $is_assignment{ $types_to_go[$itokmm]
-                                        } );
-                                }
-                            }
+                # check for a number on the left
+                if ( !$good_combo && $types_to_go[$itokm] eq 'n' ) {
 
-                            # look for a single short token either side of the
-                            # operator
-                            if ( !$good_combo ) {
+                    # okay if nothing else to left
+                    if ( $itokm == $ibeg_1 ) {
+                        $good_combo = 1;
+                    }
 
-                                # Slight adjustment factor to make results
-                                # independent of break before or after operator
-                                # in long summed lists.  (An operator and a
-                                # space make two spaces).
-                                my $two = ( $itok eq $iend_1 ) ? 2 : 0;
+                    # otherwise look one more token to left
+                    else {
 
-                                $good_combo =
+                        # okay if math operator, comma, or assignment
+                        $good_combo = ( $itokmm == $ibeg_1 )
+                          && ( $is_math_op{ $types_to_go[$itokmm] }
+                            || $types_to_go[$itokmm] =~ /^[,]$/
+                            || $is_assignment{ $types_to_go[$itokmm] } );
+                    }
+                }
 
-                                  # numbers or id's on both sides of this joint
-                                  $types_to_go[$itokp] =~ /^[in]$/
-                                  && $types_to_go[$itokm] =~ /^[in]$/
+                # look for a single short token either side of the
+                # operator
+                if ( !$good_combo ) {
 
-                                  # one of the two lines must be short:
-                                  && (
-                                    (
-                                        # no more than 2 nonblank tokens right
-                                        # of joint
-                                        $itokpp == $iend_2
-
-                                        # short
-                                        && token_sequence_length(
-                                            $itokp, $iend_2
-                                        ) < $two +
-                                        $rOpts_short_concatenation_item_length
-                                    )
-                                    || (
-                                        # no more than 2 nonblank tokens left of
-                                        # joint
-                                        $itokmm == $ibeg_1
-
-                                        # short
-                                        && token_sequence_length(
-                                            $ibeg_1, $itokm
-                                        ) < 2 - $two +
-                                        $rOpts_short_concatenation_item_length
-                                    )
+                    # Slight adjustment factor to make results
+                    # independent of break before or after operator
+                    # in long summed lists.  (An operator and a
+                    # space make two spaces).
+                    my $two = ( $itok eq $iend_1 ) ? 2 : 0;
 
-                                  )
+                    $good_combo =
 
-                                  # keep pure terms; don't mix +- with */
-                                  && !(
-                                    $is_plus_minus{$type}
-                                    && (   $is_mult_div{ $types_to_go[$itokmm] }
-                                        || $is_mult_div{ $types_to_go[$itokpp] }
-                                    )
-                                  )
-                                  && !(
-                                    $is_mult_div{$type}
-                                    && ( $is_plus_minus{ $types_to_go[$itokmm] }
-                                        || $is_plus_minus{ $types_to_go[$itokpp]
-                                        } )
-                                  )
+                      # numbers or id's on both sides of this joint
+                      $types_to_go[$itokp] =~ /^[in]$/
+                      && $types_to_go[$itokm] =~ /^[in]$/
 
-                                  ;
-                            }
+                      # one of the two lines must be short:
+                      && (
+                        (
+                            # no more than 2 nonblank tokens right
+                            # of joint
+                            $itokpp == $iend_2
 
-                            # it is also good to combine if we can reduce to 2
-                            # lines
-                            if ( !$good_combo ) {
+                            # short
+                            && token_sequence_length( $itokp, $iend_2 ) <
+                            $two + $rOpts_short_concatenation_item_length
+                        )
+                        || (
+                            # no more than 2 nonblank tokens left of
+                            # joint
+                            $itokmm == $ibeg_1
 
-                                # index on other line where same token would be
-                                # in a long chain.
-                                my $iother =
-                                  ( $itok == $iend_1 ) ? $iend_2 : $ibeg_1;
+                            # short
+                            && token_sequence_length( $ibeg_1, $itokm ) <
+                            2 - $two + $rOpts_short_concatenation_item_length
+                        )
 
-                                $good_combo =
-                                     $n == 2
-                                  && $n == $nmax
-                                  && $types_to_go[$iother] ne $type;
-                            }
+                      )
 
-                            next unless ($good_combo);
+                      # keep pure terms; don't mix +- with */
+                      && !(
+                        $is_plus_minus{$type}
+                        && (   $is_mult_div{ $types_to_go[$itokmm] }
+                            || $is_mult_div{ $types_to_go[$itokpp] } )
+                      )
+                      && !(
+                        $is_mult_div{$type}
+                        && (   $is_plus_minus{ $types_to_go[$itokmm] }
+                            || $is_plus_minus{ $types_to_go[$itokpp] } )
+                      )
 
-                        } ## end math
+                      ;
+                }
 
-                        elsif ( $is_amp_amp{$type} ) {
-                            ##TBD
-                        } ## end &&, ||
+                # it is also good to combine if we can reduce to 2
+                # lines
+                if ( !$good_combo ) {
 
-                        elsif ( $is_assignment{$type} ) {
-                            ##TBD
-                        } ## end assignment
-                    }
+                    # index on other line where same token would be
+                    # in a long chain.
+                    my $iother = ( $itok == $iend_1 ) ? $iend_2 : $ibeg_1;
 
-                    #----------------------------------------------------------
-                    # Recombine Section 1:
-                    # Join welded nested containers immediately
-                    #----------------------------------------------------------
+                    $good_combo =
+                         $n == 2
+                      && $n == $nmax
+                      && $types_to_go[$iother] ne $type;
+                }
 
-                    if (
-                        $total_weld_count
-                        && ( $type_sequence_to_go[$iend_1]
-                            && defined( $rK_weld_right->{ $K_to_go[$iend_1] } )
-                            || $type_sequence_to_go[$ibeg_2]
-                            && defined( $rK_weld_left->{ $K_to_go[$ibeg_2] } ) )
-                      )
-                    {
-                        $n_best = $n;
-                        last;
-                    }
+                return unless ($good_combo);
 
-                    $reverse = 0;
+            } ## end math
 
-                    #----------------------------------------------------------
-                    # Recombine Section 2:
-                    # Examine token at $iend_1 (right end of first line of pair)
-                    #----------------------------------------------------------
+            elsif ( $is_amp_amp{$type} ) {
+                ##TBD
+            } ## end &&, ||
 
-                    # an isolated '}' may join with a ';' terminated segment
-                    if ( $type_iend_1 eq '}' ) {
-
-                    # Check for cases where combining a semicolon terminated
-                    # statement with a previous isolated closing paren will
-                    # allow the combined line to be outdented.  This is
-                    # generally a good move.  For example, we can join up
-                    # the last two lines here:
-                    #  (
-                    #      $dev,  $ino,   $mode,  $nlink, $uid,     $gid, $rdev,
-                    #      $size, $atime, $mtime, $ctime, $blksize, $blocks
-                    #    )
-                    #    = stat($file);
-                    #
-                    # to get:
-                    #  (
-                    #      $dev,  $ino,   $mode,  $nlink, $uid,     $gid, $rdev,
-                    #      $size, $atime, $mtime, $ctime, $blksize, $blocks
-                    #  ) = stat($file);
-                    #
-                    # which makes the parens line up.
-                    #
-                    # Another example, from Joe Matarazzo, probably looks best
-                    # with the 'or' clause appended to the trailing paren:
-                    #  $self->some_method(
-                    #      PARAM1 => 'foo',
-                    #      PARAM2 => 'bar'
-                    #  ) or die "Some_method didn't work";
-                    #
-                    # But we do not want to do this for something like the -lp
-                    # option where the paren is not outdentable because the
-                    # trailing clause will be far to the right.
-                    #
-                    # The logic here is synchronized with the logic in sub
-                    # sub final_indentation_adjustment, which actually does
-                    # the outdenting.
-                    #
-                        $skip_Section_3 ||= $this_line_is_semicolon_terminated
-
-                          # only one token on last line
-                          && $ibeg_1 == $iend_1
-
-                          # must be structural paren
-                          && $tokens_to_go[$iend_1] eq ')'
-
-                          # style must allow outdenting,
-                          && !$closing_token_indentation{')'}
-
-                          # only leading '&&', '||', and ':' if no others seen
-                          # (but note: our count made below could be wrong
-                          # due to intervening comments)
-                          && ( $leading_amp_count == 0
-                            || $type_ibeg_2 !~ /^(:|\&\&|\|\|)$/ )
-
-                          # but leading colons probably line up with a
-                          # previous colon or question (count could be wrong).
-                          && $type_ibeg_2 ne ':'
-
-                          # only one step in depth allowed.  this line must not
-                          # begin with a ')' itself.
-                          && ( $nesting_depth_to_go[$iend_1] ==
-                            $nesting_depth_to_go[$iend_2] + 1 );
-
-                        # YVES patch 2 of 2:
-                        # Allow cuddled eval chains, like this:
-                        #   eval {
-                        #       #STUFF;
-                        #       1; # return true
-                        #   } or do {
-                        #       #handle error
-                        #   };
-                        # This patch works together with a patch in
-                        # setting adjusted indentation (where the closing eval
-                        # brace is outdented if possible).
-                        # The problem is that an 'eval' block has continuation
-                        # indentation and it looks better to undo it in some
-                        # cases.  If we do not use this patch we would get:
-                        #   eval {
-                        #       #STUFF;
-                        #       1; # return true
-                        #       }
-                        #       or do {
-                        #       #handle error
-                        #     };
-                        # The alternative, for uncuddled style, is to create
-                        # a patch in final_indentation_adjustment which undoes
-                        # the indentation of a leading line like 'or do {'.
-                        # This doesn't work well with -icb through
-                        if (
-                               $block_type_to_go[$iend_1] eq 'eval'
-                            && !ref( $leading_spaces_to_go[$iend_1] )
-                            && !$rOpts_indent_closing_brace
-                            && $tokens_to_go[$iend_2] eq '{'
-                            && (
-                                ( $type_ibeg_2 =~ /^(\&\&|\|\|)$/ )
-                                || (   $type_ibeg_2 eq 'k'
-                                    && $is_and_or{ $tokens_to_go[$ibeg_2] } )
-                                || $is_if_unless{ $tokens_to_go[$ibeg_2] }
-                            )
-                          )
-                        {
-                            $skip_Section_3 ||= 1;
-                        }
+            elsif ( $is_assignment{$type} ) {
+                ##TBD
+            } ## end assignment
+        }
 
-                        next
-                          unless (
-                            $skip_Section_3
+        # ok to combine lines
+        return 1;
+    } ## end sub recombine_section_0
 
-                            # handle '.' and '?' specially below
-                            || ( $type_ibeg_2 =~ /^[\.\?]$/ )
+    sub recombine_section_2 {
 
-                            # fix for c054 (unusual -pbp case)
-                            || $type_ibeg_2 eq '=='
+        my ( $ri_beg, $ri_end, $n, $this_line_is_semicolon_terminated,
+            $rleading_amp_count )
+          = @_;
 
-                          );
-                    }
+        # Recombine Section 2:
+        # Examine token at $iend_1 (right end of first line of pair)
 
-                    elsif ( $type_iend_1 eq '{' ) {
+        # Here are Indexes of the endpoint tokens of the two lines:
+        #
+        #  -----line $n-1--- | -----line $n-----
+        #  $ibeg_1   $iend_1 | $ibeg_2   $iend_2
+        #              ^
+        #              |
+        #              -----Section 2 looks at this token
 
-                        # YVES
-                        # honor breaks at opening brace
-                        # Added to prevent recombining something like this:
-                        #  } || eval { package main;
-                        next if $forced_breakpoint_to_go[$iend_1];
-                    }
+        # Returns:
+        #   (nothing)         => do not join lines
+        #   1, skip_Section_3 => ok to join lines
+
+        # $skip_Section_3 is a flag for skipping the next section
+        my $skip_Section_3 = 0;
+
+        my $nmax      = @{$ri_end} - 1;
+        my $ibeg_1    = $ri_beg->[ $n - 1 ];
+        my $iend_1    = $ri_end->[ $n - 1 ];
+        my $iend_2    = $ri_end->[$n];
+        my $ibeg_2    = $ri_beg->[$n];
+        my $ibeg_3    = $n < $nmax ? $ri_beg->[ $n + 1 ] : -1;
+        my $ibeg_nmax = $ri_beg->[$nmax];
+
+        my $type_iend_1 = $types_to_go[$iend_1];
+        my $type_iend_2 = $types_to_go[$iend_2];
+        my $type_ibeg_1 = $types_to_go[$ibeg_1];
+        my $type_ibeg_2 = $types_to_go[$ibeg_2];
+
+        # an isolated '}' may join with a ';' terminated segment
+        if ( $type_iend_1 eq '}' ) {
+
+            # Check for cases where combining a semicolon terminated
+            # statement with a previous isolated closing paren will
+            # allow the combined line to be outdented.  This is
+            # generally a good move.  For example, we can join up
+            # the last two lines here:
+            #  (
+            #      $dev,  $ino,   $mode,  $nlink, $uid,     $gid, $rdev,
+            #      $size, $atime, $mtime, $ctime, $blksize, $blocks
+            #    )
+            #    = stat($file);
+            #
+            # to get:
+            #  (
+            #      $dev,  $ino,   $mode,  $nlink, $uid,     $gid, $rdev,
+            #      $size, $atime, $mtime, $ctime, $blksize, $blocks
+            #  ) = stat($file);
+            #
+            # which makes the parens line up.
+            #
+            # Another example, from Joe Matarazzo, probably looks best
+            # with the 'or' clause appended to the trailing paren:
+            #  $self->some_method(
+            #      PARAM1 => 'foo',
+            #      PARAM2 => 'bar'
+            #  ) or die "Some_method didn't work";
+            #
+            # But we do not want to do this for something like the -lp
+            # option where the paren is not outdentable because the
+            # trailing clause will be far to the right.
+            #
+            # The logic here is synchronized with the logic in sub
+            # sub get_final_indentation, which actually does
+            # the outdenting.
+            #
+            $skip_Section_3 ||= $this_line_is_semicolon_terminated
+
+              # only one token on last line
+              && $ibeg_1 == $iend_1
+
+              # must be structural paren
+              && $tokens_to_go[$iend_1] eq ')'
+
+              # style must allow outdenting,
+              && !$closing_token_indentation{')'}
+
+              # only leading '&&', '||', and ':' if no others seen
+              # (but note: our count made below could be wrong
+              # due to intervening comments).  Note that this
+              # count includes these tokens at all levels.  The idea is
+              # that seeing these at any level can make it hard to read
+              # formatting if we recombine.
+              && ( !${$rleading_amp_count}
+                || $type_ibeg_2 !~ /^(:|\&\&|\|\|)$/ )
+
+              # but leading colons probably line up with a
+              # previous colon or question (count could be wrong).
+              && $type_ibeg_2 ne ':'
+
+              # only one step in depth allowed.  this line must not
+              # begin with a ')' itself.
+              && ( $nesting_depth_to_go[$iend_1] ==
+                $nesting_depth_to_go[$iend_2] + 1 );
+
+            # YVES patch 2 of 2:
+            # Allow cuddled eval chains, like this:
+            #   eval {
+            #       #STUFF;
+            #       1; # return true
+            #   } or do {
+            #       #handle error
+            #   };
+            # This patch works together with a patch in
+            # setting adjusted indentation (where the closing eval
+            # brace is outdented if possible).
+            # The problem is that an 'eval' block has continuation
+            # indentation and it looks better to undo it in some
+            # cases.  If we do not use this patch we would get:
+            #   eval {
+            #       #STUFF;
+            #       1; # return true
+            #       }
+            #       or do {
+            #       #handle error
+            #     };
+            # The alternative, for uncuddled style, is to create
+            # a patch in get_final_indentation which undoes
+            # the indentation of a leading line like 'or do {'.
+            # This doesn't work well with -icb through
+            if (
+                   $block_type_to_go[$iend_1] eq 'eval'
+                && !ref( $leading_spaces_to_go[$iend_1] )
+                && !$rOpts_indent_closing_brace
+                && $tokens_to_go[$iend_2] eq '{'
+                && (
+                    ( $type_ibeg_2 =~ /^(\&\&|\|\|)$/ )
+                    || (   $type_ibeg_2 eq 'k'
+                        && $is_and_or{ $tokens_to_go[$ibeg_2] } )
+                    || $is_if_unless{ $tokens_to_go[$ibeg_2] }
+                )
+              )
+            {
+                $skip_Section_3 ||= 1;
+            }
 
-                    # do not recombine lines with ending &&, ||,
-                    elsif ( $is_amp_amp{$type_iend_1} ) {
-                        next unless $want_break_before{$type_iend_1};
-                    }
+            return
+              unless (
+                $skip_Section_3
 
-                    # Identify and recombine a broken ?/: chain
-                    elsif ( $type_iend_1 eq '?' ) {
+                # handle '.' and '?' specially below
+                || ( $type_ibeg_2 =~ /^[\.\?]$/ )
 
-                        # Do not recombine different levels
-                        next
-                          if (
-                            $levels_to_go[$ibeg_1] ne $levels_to_go[$ibeg_2] );
+                # fix for c054 (unusual -pbp case)
+                || $type_ibeg_2 eq '=='
 
-                        # do not recombine unless next line ends in :
-                        next unless $type_iend_2 eq ':';
-                    }
+              );
+        }
 
-                    # for lines ending in a comma...
-                    elsif ( $type_iend_1 eq ',' ) {
+        elsif ( $type_iend_1 eq '{' ) {
 
-                        # Do not recombine at comma which is following the
-                        # input bias.
-                        # TODO: might be best to make a special flag
-                        next if ( $old_breakpoint_to_go[$iend_1] );
+            # YVES
+            # honor breaks at opening brace
+            # Added to prevent recombining something like this:
+            #  } || eval { package main;
+            return if $forced_breakpoint_to_go[$iend_1];
+        }
 
-                        # An isolated '},' may join with an identifier + ';'
-                        # This is useful for the class of a 'bless' statement
-                        # (bless.t)
-                        if (   $type_ibeg_1 eq '}'
-                            && $type_ibeg_2 eq 'i' )
-                        {
-                            next
-                              unless ( ( $ibeg_1 == ( $iend_1 - 1 ) )
-                                && ( $iend_2 == ( $ibeg_2 + 1 ) )
-                                && $this_line_is_semicolon_terminated );
+        # do not recombine lines with ending &&, ||,
+        elsif ( $is_amp_amp{$type_iend_1} ) {
+            return unless $want_break_before{$type_iend_1};
+        }
 
-                            # override breakpoint
-                            $forced_breakpoint_to_go[$iend_1] = 0;
-                        }
+        # Identify and recombine a broken ?/: chain
+        elsif ( $type_iend_1 eq '?' ) {
 
-                        # but otherwise ..
-                        else {
+            # Do not recombine different levels
+            return
+              if ( $levels_to_go[$ibeg_1] ne $levels_to_go[$ibeg_2] );
 
-                            # do not recombine after a comma unless this will
-                            # leave just 1 more line
-                            next unless ( $n + 1 >= $nmax );
+            # do not recombine unless next line ends in :
+            return unless $type_iend_2 eq ':';
+        }
 
-                            # do not recombine if there is a change in
-                            # indentation depth
-                            next
-                              if ( $levels_to_go[$iend_1] !=
-                                $levels_to_go[$iend_2] );
-
-                            # do not recombine a "complex expression" after a
-                            # comma.  "complex" means no parens.
-                            my $saw_paren;
-                            foreach my $ii ( $ibeg_2 .. $iend_2 ) {
-                                if ( $tokens_to_go[$ii] eq '(' ) {
-                                    $saw_paren = 1;
-                                    last;
-                                }
-                            }
-                            next if $saw_paren;
-                        }
-                    }
+        # for lines ending in a comma...
+        elsif ( $type_iend_1 eq ',' ) {
 
-                    # opening paren..
-                    elsif ( $type_iend_1 eq '(' ) {
+            # Do not recombine at comma which is following the
+            # input bias.
+            # NOTE: this could be controlled by a special flag,
+            # but it seems to work okay.
+            return if ( $old_breakpoint_to_go[$iend_1] );
 
-                        # No longer doing this
-                    }
+            # An isolated '},' may join with an identifier + ';'
+            # This is useful for the class of a 'bless' statement
+            # (bless.t)
+            if (   $type_ibeg_1 eq '}'
+                && $type_ibeg_2 eq 'i' )
+            {
+                return
+                  unless ( ( $ibeg_1 == ( $iend_1 - 1 ) )
+                    && ( $iend_2 == ( $ibeg_2 + 1 ) )
+                    && $this_line_is_semicolon_terminated );
 
-                    elsif ( $type_iend_1 eq ')' ) {
+                # override breakpoint
+                $forced_breakpoint_to_go[$iend_1] = 0;
+            }
 
-                        # No longer doing this
-                    }
+            # but otherwise ..
+            else {
 
-                    # keep a terminal for-semicolon
-                    elsif ( $type_iend_1 eq 'f' ) {
-                        next;
+                # do not recombine after a comma unless this will
+                # leave just 1 more line
+                return unless ( $n + 1 >= $nmax );
+
+                # do not recombine if there is a change in
+                # indentation depth
+                return
+                  if ( $levels_to_go[$iend_1] != $levels_to_go[$iend_2] );
+
+                # do not recombine a "complex expression" after a
+                # comma.  "complex" means no parens.
+                my $saw_paren;
+                foreach my $ii ( $ibeg_2 .. $iend_2 ) {
+                    if ( $tokens_to_go[$ii] eq '(' ) {
+                        $saw_paren = 1;
+                        last;
                     }
+                }
+                return if $saw_paren;
+            }
+        }
 
-                    # if '=' at end of line ...
-                    elsif ( $is_assignment{$type_iend_1} ) {
+        # opening paren..
+        elsif ( $type_iend_1 eq '(' ) {
 
-                        # keep break after = if it was in input stream
-                        # this helps prevent 'blinkers'
-                        next
-                          if (
-                            $old_breakpoint_to_go[$iend_1]
+            # No longer doing this
+        }
 
-                            # don't strand an isolated '='
-                            && $iend_1 != $ibeg_1
-                          );
+        elsif ( $type_iend_1 eq ')' ) {
 
-                        my $is_short_quote =
-                          (      $type_ibeg_2 eq 'Q'
-                              && $ibeg_2 == $iend_2
-                              && token_sequence_length( $ibeg_2, $ibeg_2 ) <
-                              $rOpts_short_concatenation_item_length );
-                        my $is_ternary = (
-                            $type_ibeg_1 eq '?' && ( $ibeg_3 >= 0
-                                && $types_to_go[$ibeg_3] eq ':' )
-                        );
+            # No longer doing this
+        }
 
-                        # always join an isolated '=', a short quote, or if this
-                        # will put ?/: at start of adjacent lines
-                        if (   $ibeg_1 != $iend_1
-                            && !$is_short_quote
-                            && !$is_ternary )
-                        {
-                            next
-                              unless (
-                                (
+        # keep a terminal for-semicolon
+        elsif ( $type_iend_1 eq 'f' ) {
+            return;
+        }
 
-                                    # unless we can reduce this to two lines
-                                    $nmax < $n + 2
+        # if '=' at end of line ...
+        elsif ( $is_assignment{$type_iend_1} ) {
 
-                                    # or three lines, the last with a leading
-                                    # semicolon
-                                    || (   $nmax == $n + 2
-                                        && $types_to_go[$ibeg_nmax] eq ';' )
+            # keep break after = if it was in input stream
+            # this helps prevent 'blinkers'
+            return
+              if (
+                $old_breakpoint_to_go[$iend_1]
 
-                                    # or the next line ends with a here doc
-                                    || $type_iend_2 eq 'h'
+                # don't strand an isolated '='
+                && $iend_1 != $ibeg_1
+              );
 
-                                    # or the next line ends in an open paren or
-                                    # brace and the break hasn't been forced
-                                    # [dima.t]
-                                    || (  !$forced_breakpoint_to_go[$iend_1]
-                                        && $type_iend_2 eq '{' )
-                                )
+            my $is_short_quote =
+              (      $type_ibeg_2 eq 'Q'
+                  && $ibeg_2 == $iend_2
+                  && token_sequence_length( $ibeg_2, $ibeg_2 ) <
+                  $rOpts_short_concatenation_item_length );
+            my $is_ternary = (
+                $type_ibeg_1 eq '?' && ( $ibeg_3 >= 0
+                    && $types_to_go[$ibeg_3] eq ':' )
+            );
 
-                                # do not recombine if the two lines might align
-                                # well this is a very approximate test for this
-                                && (
+            # always join an isolated '=', a short quote, or if this
+            # will put ?/: at start of adjacent lines
+            if (   $ibeg_1 != $iend_1
+                && !$is_short_quote
+                && !$is_ternary )
+            {
+                return
+                  unless (
+                    (
 
-                                    # RT#127633 - the leading tokens are not
-                                    # operators
-                                    ( $type_ibeg_2 ne $tokens_to_go[$ibeg_2] )
+                        # unless we can reduce this to two lines
+                        $nmax < $n + 2
 
-                                    # or they are different
-                                    || (   $ibeg_3 >= 0
-                                        && $type_ibeg_2 ne
-                                        $types_to_go[$ibeg_3] )
-                                )
-                              );
-
-                            if (
-
-                                # Recombine if we can make two lines
-                                $nmax >= $n + 2
-
-                                # -lp users often prefer this:
-                                #  my $title = function($env, $env, $sysarea,
-                                #                       "bubba Borrower Entry");
-                                #  so we will recombine if -lp is used we have
-                                #  ending comma
-                                && !(
-                                       $ibeg_3 > 0
-                                    && ref( $leading_spaces_to_go[$ibeg_3] )
-                                    && $type_iend_2 eq ','
-                                )
-                              )
-                            {
+                        # or three lines, the last with a leading
+                        # semicolon
+                        || (   $nmax == $n + 2
+                            && $types_to_go[$ibeg_nmax] eq ';' )
 
-                                # otherwise, scan the rhs line up to last token
-                                # for complexity.  Note that we are not
-                                # counting the last token in case it is an
-                                # opening paren.
-                                my $tv    = 0;
-                                my $depth = $nesting_depth_to_go[$ibeg_2];
-                                foreach my $i ( $ibeg_2 + 1 .. $iend_2 - 1 ) {
-                                    if ( $nesting_depth_to_go[$i] != $depth ) {
-                                        $tv++;
-                                        last if ( $tv > 1 );
-                                    }
-                                    $depth = $nesting_depth_to_go[$i];
-                                }
-
-                                # ok to recombine if no level changes before
-                                # last token
-                                if ( $tv > 0 ) {
-
-                                    # otherwise, do not recombine if more than
-                                    # two level changes.
-                                    next if ( $tv > 1 );
-
-                                    # check total complexity of the two
-                                    # adjacent lines that will occur if we do
-                                    # this join
-                                    my $istop =
-                                      ( $n < $nmax )
-                                      ? $ri_end->[ $n + 1 ]
-                                      : $iend_2;
-                                    foreach my $i ( $iend_2 .. $istop ) {
-                                        if (
-                                            $nesting_depth_to_go[$i] != $depth )
-                                        {
-                                            $tv++;
-                                            last if ( $tv > 2 );
-                                        }
-                                        $depth = $nesting_depth_to_go[$i];
-                                    }
-
-                                    # do not recombine if total is more than 2
-                                    # level changes
-                                    next if ( $tv > 2 );
-                                }
-                            }
-                        }
+                        # or the next line ends with a here doc
+                        || $type_iend_2 eq 'h'
 
-                        unless ( $tokens_to_go[$ibeg_2] =~ /^[\{\(\[]$/ ) {
-                            $forced_breakpoint_to_go[$iend_1] = 0;
-                        }
-                    }
+                        # or the next line ends in an open paren or
+                        # brace and the break hasn't been forced
+                        # [dima.t]
+                        || (  !$forced_breakpoint_to_go[$iend_1]
+                            && $type_iend_2 eq '{' )
+                    )
 
-                    # for keywords..
-                    elsif ( $type_iend_1 eq 'k' ) {
+                    # do not recombine if the two lines might align
+                    # well this is a very approximate test for this
+                    && (
 
-                        # make major control keywords stand out
-                        # (recombine.t)
-                        next
-                          if (
+                        # RT#127633 - the leading tokens are not
+                        # operators
+                        ( $type_ibeg_2 ne $tokens_to_go[$ibeg_2] )
 
-                            #/^(last|next|redo|return)$/
-                            $is_last_next_redo_return{ $tokens_to_go[$iend_1] }
+                        # or they are different
+                        || (   $ibeg_3 >= 0
+                            && $type_ibeg_2 ne $types_to_go[$ibeg_3] )
+                    )
+                  );
 
-                            # but only if followed by multiple lines
-                            && $n < $nmax
-                          );
+                if (
 
-                        if ( $is_and_or{ $tokens_to_go[$iend_1] } ) {
-                            next
-                              unless $want_break_before{ $tokens_to_go[$iend_1]
-                              };
-                        }
-                    }
+                    # Recombine if we can make two lines
+                    $nmax >= $n + 2
 
-                    #----------------------------------------------------------
-                    # Recombine Section 3:
-                    # Examine token at $ibeg_2 (left end of second line of pair)
-                    #----------------------------------------------------------
+                    # -lp users often prefer this:
+                    #  my $title = function($env, $env, $sysarea,
+                    #                       "bubba Borrower Entry");
+                    #  so we will recombine if -lp is used we have
+                    #  ending comma
+                    && !(
+                           $ibeg_3 > 0
+                        && ref( $leading_spaces_to_go[$ibeg_3] )
+                        && $type_iend_2 eq ','
+                    )
+                  )
+                {
 
-                    # join lines identified above as capable of
-                    # causing an outdented line with leading closing paren
-                    # Note that we are skipping the rest of this section
-                    # and the rest of the loop to do the join
-                    if ($skip_Section_3) {
-                        $forced_breakpoint_to_go[$iend_1] = 0;
-                        $n_best = $n;
-                        last;
-                    }
+                    # otherwise, scan the rhs line up to last token for
+                    # complexity.  Note that we are not counting the last token
+                    # in case it is an opening paren.
+                    my $ok = simple_rhs( $ri_end, $n, $nmax, $ibeg_2, $iend_2 );
+                    return if ( !$ok );
 
-                    # handle lines with leading &&, ||
-                    elsif ( $is_amp_amp{$type_ibeg_2} ) {
+                }
+            }
 
-                        $leading_amp_count++;
+            unless ( $tokens_to_go[$ibeg_2] =~ /^[\{\(\[]$/ ) {
+                $forced_breakpoint_to_go[$iend_1] = 0;
+            }
+        }
 
-                        # ok to recombine if it follows a ? or :
-                        # and is followed by an open paren..
-                        my $ok =
-                          (      $is_ternary{$type_ibeg_1}
-                              && $tokens_to_go[$iend_2] eq '(' )
+        # for keywords..
+        elsif ( $type_iend_1 eq 'k' ) {
 
-                    # or is followed by a ? or : at same depth
-                    #
-                    # We are looking for something like this. We can
-                    # recombine the && line with the line above to make the
-                    # structure more clear:
-                    #  return
-                    #    exists $G->{Attr}->{V}
-                    #    && exists $G->{Attr}->{V}->{$u}
-                    #    ? %{ $G->{Attr}->{V}->{$u} }
-                    #    : ();
-                    #
-                    # We should probably leave something like this alone:
-                    #  return
-                    #       exists $G->{Attr}->{E}
-                    #    && exists $G->{Attr}->{E}->{$u}
-                    #    && exists $G->{Attr}->{E}->{$u}->{$v}
-                    #    ? %{ $G->{Attr}->{E}->{$u}->{$v} }
-                    #    : ();
-                    # so that we either have all of the &&'s (or ||'s)
-                    # on one line, as in the first example, or break at
-                    # each one as in the second example.  However, it
-                    # sometimes makes things worse to check for this because
-                    # it prevents multiple recombinations.  So this is not done.
-                          || ( $ibeg_3 >= 0
-                            && $is_ternary{ $types_to_go[$ibeg_3] }
-                            && $nesting_depth_to_go[$ibeg_3] ==
-                            $nesting_depth_to_go[$ibeg_2] );
-
-                        # Combine a trailing && term with an || term: fix for
-                        # c060 This is rare but can happen.
-                        $ok ||= 1
-                          if ( $ibeg_3 < 0
-                            && $type_ibeg_2 eq '&&'
-                            && $type_ibeg_1 eq '||'
-                            && $nesting_depth_to_go[$ibeg_2] ==
-                            $nesting_depth_to_go[$ibeg_1] );
-
-                        next if !$ok && $want_break_before{$type_ibeg_2};
-                        $forced_breakpoint_to_go[$iend_1] = 0;
+            # make major control keywords stand out
+            # (recombine.t)
+            return
+              if (
 
-                        # tweak the bond strength to give this joint priority
-                        # over ? and :
-                        $bs_tweak = 0.25;
-                    }
+                #/^(last|next|redo|return)$/
+                $is_last_next_redo_return{ $tokens_to_go[$iend_1] }
 
-                    # Identify and recombine a broken ?/: chain
-                    elsif ( $type_ibeg_2 eq '?' ) {
-
-                        # Do not recombine different levels
-                        my $lev = $levels_to_go[$ibeg_2];
-                        next if ( $lev ne $levels_to_go[$ibeg_1] );
-
-                        # Do not recombine a '?' if either next line or
-                        # previous line does not start with a ':'.  The reasons
-                        # are that (1) no alignment of the ? will be possible
-                        # and (2) the expression is somewhat complex, so the
-                        # '?' is harder to see in the interior of the line.
-                        my $follows_colon = $ibeg_1 >= 0 && $type_ibeg_1 eq ':';
-                        my $precedes_colon =
-                          $ibeg_3 >= 0 && $types_to_go[$ibeg_3] eq ':';
-                        next unless ( $follows_colon || $precedes_colon );
-
-                        # we will always combining a ? line following a : line
-                        if ( !$follows_colon ) {
-
-                            # ...otherwise recombine only if it looks like a
-                            # chain.  we will just look at a few nearby lines
-                            # to see if this looks like a chain.
-                            my $local_count = 0;
-                            foreach
-                              my $ii ( $ibeg_0, $ibeg_1, $ibeg_3, $ibeg_4 )
-                            {
-                                $local_count++
-                                  if $ii >= 0
-                                  && $types_to_go[$ii] eq ':'
-                                  && $levels_to_go[$ii] == $lev;
-                            }
-                            next unless ( $local_count > 1 );
-                        }
-                        $forced_breakpoint_to_go[$iend_1] = 0;
-                    }
+                # but only if followed by multiple lines
+                && $n < $nmax
+              );
 
-                    # do not recombine lines with leading '.'
-                    elsif ( $type_ibeg_2 eq '.' ) {
-                        my $i_next_nonblank =
-                          min( $inext_to_go[$ibeg_2], $iend_2 );
-                        next
-                          unless (
-
-                   # ... unless there is just one and we can reduce
-                   # this to two lines if we do.  For example, this
-                   #
-                   #
-                   #  $bodyA .=
-                   #    '($dummy, $pat) = &get_next_tex_cmd;' . '$args .= $pat;'
-                   #
-                   #  looks better than this:
-                   #  $bodyA .= '($dummy, $pat) = &get_next_tex_cmd;'
-                   #    . '$args .= $pat;'
-
-                            (
-                                   $n == 2
-                                && $n == $nmax
-                                && $type_ibeg_1 ne $type_ibeg_2
-                            )
+            if ( $is_and_or{ $tokens_to_go[$iend_1] } ) {
+                return
+                  unless $want_break_before{ $tokens_to_go[$iend_1] };
+            }
+        }
+        return ( 1, $skip_Section_3 );
+    } ## end sub recombine_section_2
 
-                            # ... or this would strand a short quote , like this
-                            #                . "some long quote"
-                            #                . "\n";
+    sub simple_rhs {
 
-                            || (   $types_to_go[$i_next_nonblank] eq 'Q'
-                                && $i_next_nonblank >= $iend_2 - 1
-                                && $token_lengths_to_go[$i_next_nonblank] <
-                                $rOpts_short_concatenation_item_length )
-                          );
-                    }
+        my ( $ri_end, $n, $nmax, $ibeg_2, $iend_2 ) = @_;
 
-                    # handle leading keyword..
-                    elsif ( $type_ibeg_2 eq 'k' ) {
+        # Scan line ibeg_2 to $iend_2 up to last token for complexity.
+        # We are not counting the last token in case it is an opening paren.
+        # Return:
+        #   true  if rhs is simple, ok to recombine
+        #   false otherwise
 
-                        # handle leading "or"
-                        if ( $tokens_to_go[$ibeg_2] eq 'or' ) {
-                            next
-                              unless (
-                                $this_line_is_semicolon_terminated
-                                && (
-                                    $type_ibeg_1 eq '}'
-                                    || (
-
-                                        # following 'if' or 'unless' or 'or'
-                                        $type_ibeg_1 eq 'k'
-                                        && $is_if_unless{ $tokens_to_go[$ibeg_1]
-                                        }
-
-                                        # important: only combine a very simple
-                                        # or statement because the step below
-                                        # may have combined a trailing 'and'
-                                        # with this or, and we do not want to
-                                        # then combine everything together
-                                        && ( $iend_2 - $ibeg_2 <= 7 )
-                                    )
-                                )
-                              );
+        my $tv    = 0;
+        my $depth = $nesting_depth_to_go[$ibeg_2];
+        foreach my $i ( $ibeg_2 + 1 .. $iend_2 - 1 ) {
+            if ( $nesting_depth_to_go[$i] != $depth ) {
+                $tv++;
+                last if ( $tv > 1 );
+            }
+            $depth = $nesting_depth_to_go[$i];
+        }
 
-                            #X: RT #81854
-                            $forced_breakpoint_to_go[$iend_1] = 0
-                              unless ( $old_breakpoint_to_go[$iend_1] );
-                        }
+        # ok to recombine if no level changes before
+        # last token
+        if ( $tv > 0 ) {
 
-                        # handle leading 'and' and 'xor'
-                        elsif ($tokens_to_go[$ibeg_2] eq 'and'
-                            || $tokens_to_go[$ibeg_2] eq 'xor' )
-                        {
+            # otherwise, do not recombine if more than
+            # two level changes.
+            return if ( $tv > 1 );
 
-                            # Decide if we will combine a single terminal 'and'
-                            # after an 'if' or 'unless'.
-
-                            #     This looks best with the 'and' on the same
-                            #     line as the 'if':
-                            #
-                            #         $a = 1
-                            #           if $seconds and $nu < 2;
-                            #
-                            #     But this looks better as shown:
-                            #
-                            #         $a = 1
-                            #           if !$this->{Parents}{$_}
-                            #           or $this->{Parents}{$_} eq $_;
-                            #
-                            next
-                              unless (
-                                $this_line_is_semicolon_terminated
-                                && (
-
-                                    # following 'if' or 'unless' or 'or'
-                                    $type_ibeg_1 eq 'k'
-                                    && ( $is_if_unless{ $tokens_to_go[$ibeg_1] }
-                                        || $tokens_to_go[$ibeg_1] eq 'or' )
-                                )
-                              );
-                        }
+            # check total complexity of the two
+            # adjacent lines that will occur if we do
+            # this join
+            my $istop =
+              ( $n < $nmax )
+              ? $ri_end->[ $n + 1 ]
+              : $iend_2;
+            foreach my $i ( $iend_2 .. $istop ) {
+                if ( $nesting_depth_to_go[$i] != $depth ) {
+                    $tv++;
+                    last if ( $tv > 2 );
+                }
+                $depth = $nesting_depth_to_go[$i];
+            }
+
+            # do not recombine if total is more than 2
+            # level changes
+            return if ( $tv > 2 );
+        }
+        return 1;
+    }
+
+    sub recombine_section_3 {
+
+        my ( $ri_beg, $ri_end, $n, $this_line_is_semicolon_terminated,
+            $rleading_amp_count )
+          = @_;
+
+        # Recombine Section 3:
+        # Examine token at $ibeg_2 (right end of first line of pair)
+
+        # Here are Indexes of the endpoint tokens of the two lines:
+        #
+        #  -----line $n-1--- | -----line $n-----
+        #  $ibeg_1   $iend_1 | $ibeg_2   $iend_2
+        #                        ^
+        #                        |
+        #                        -----Section 3 looks at this token
 
-                        # handle leading "if" and "unless"
-                        elsif ( $is_if_unless{ $tokens_to_go[$ibeg_2] } ) {
+        # Returns:
+        #   (nothing)         => do not join lines
+        #   1, bs_tweak => ok to join lines
+
+        # $bstweak is a small tolerance to add to bond strengths
+        my $bs_tweak = 0;
+
+        my $nmax   = @{$ri_end} - 1;
+        my $ibeg_1 = $ri_beg->[ $n - 1 ];
+        my $iend_1 = $ri_end->[ $n - 1 ];
+        my $iend_2 = $ri_end->[$n];
+        my $ibeg_2 = $ri_beg->[$n];
+
+        my $ibeg_0    = $n > 1          ? $ri_beg->[ $n - 2 ] : -1;
+        my $ibeg_3    = $n < $nmax      ? $ri_beg->[ $n + 1 ] : -1;
+        my $ibeg_4    = $n + 2 <= $nmax ? $ri_beg->[ $n + 2 ] : -1;
+        my $ibeg_nmax = $ri_beg->[$nmax];
+
+        my $type_iend_1 = $types_to_go[$iend_1];
+        my $type_iend_2 = $types_to_go[$iend_2];
+        my $type_ibeg_1 = $types_to_go[$ibeg_1];
+        my $type_ibeg_2 = $types_to_go[$ibeg_2];
+
+        # handle lines with leading &&, ||
+        if ( $is_amp_amp{$type_ibeg_2} ) {
+
+            ${$rleading_amp_count}++;
+
+            # ok to recombine if it follows a ? or :
+            # and is followed by an open paren..
+            my $ok =
+              ( $is_ternary{$type_ibeg_1} && $tokens_to_go[$iend_2] eq '(' )
+
+              # or is followed by a ? or : at same depth
+              #
+              # We are looking for something like this. We can
+              # recombine the && line with the line above to make the
+              # structure more clear:
+              #  return
+              #    exists $G->{Attr}->{V}
+              #    && exists $G->{Attr}->{V}->{$u}
+              #    ? %{ $G->{Attr}->{V}->{$u} }
+              #    : ();
+              #
+              # We should probably leave something like this alone:
+              #  return
+              #       exists $G->{Attr}->{E}
+              #    && exists $G->{Attr}->{E}->{$u}
+              #    && exists $G->{Attr}->{E}->{$u}->{$v}
+              #    ? %{ $G->{Attr}->{E}->{$u}->{$v} }
+              #    : ();
+              # so that we either have all of the &&'s (or ||'s)
+              # on one line, as in the first example, or break at
+              # each one as in the second example.  However, it
+              # sometimes makes things worse to check for this because
+              # it prevents multiple recombinations.  So this is not done.
+              || ( $ibeg_3 >= 0
+                && $is_ternary{ $types_to_go[$ibeg_3] }
+                && $nesting_depth_to_go[$ibeg_3] ==
+                $nesting_depth_to_go[$ibeg_2] );
+
+            # Combine a trailing && term with an || term: fix for
+            # c060 This is rare but can happen.
+            $ok ||= 1
+              if ( $ibeg_3 < 0
+                && $type_ibeg_2 eq '&&'
+                && $type_ibeg_1 eq '||'
+                && $nesting_depth_to_go[$ibeg_2] ==
+                $nesting_depth_to_go[$ibeg_1] );
+
+            return if !$ok && $want_break_before{$type_ibeg_2};
+            $forced_breakpoint_to_go[$iend_1] = 0;
+
+            # tweak the bond strength to give this joint priority
+            # over ? and :
+            $bs_tweak = 0.25;
+        }
+
+        # Identify and recombine a broken ?/: chain
+        elsif ( $type_ibeg_2 eq '?' ) {
+
+            # Do not recombine different levels
+            my $lev = $levels_to_go[$ibeg_2];
+            return if ( $lev ne $levels_to_go[$ibeg_1] );
+
+            # Do not recombine a '?' if either next line or
+            # previous line does not start with a ':'.  The reasons
+            # are that (1) no alignment of the ? will be possible
+            # and (2) the expression is somewhat complex, so the
+            # '?' is harder to see in the interior of the line.
+            my $follows_colon  = $ibeg_1 >= 0 && $type_ibeg_1 eq ':';
+            my $precedes_colon = $ibeg_3 >= 0 && $types_to_go[$ibeg_3] eq ':';
+            return unless ( $follows_colon || $precedes_colon );
+
+            # we will always combining a ? line following a : line
+            if ( !$follows_colon ) {
+
+                # ...otherwise recombine only if it looks like a
+                # chain.  we will just look at a few nearby lines
+                # to see if this looks like a chain.
+                my $local_count = 0;
+                foreach my $ii ( $ibeg_0, $ibeg_1, $ibeg_3, $ibeg_4 ) {
+                    $local_count++
+                      if $ii >= 0
+                      && $types_to_go[$ii] eq ':'
+                      && $levels_to_go[$ii] == $lev;
+                }
+                return unless ( $local_count > 1 );
+            }
+            $forced_breakpoint_to_go[$iend_1] = 0;
+        }
+
+        # do not recombine lines with leading '.'
+        elsif ( $type_ibeg_2 eq '.' ) {
+            my $i_next_nonblank = min( $inext_to_go[$ibeg_2], $iend_2 );
+            return
+              unless (
 
-                            # Combine something like:
-                            #    next
-                            #      if ( $lang !~ /${l}$/i );
-                            # into:
-                            #    next if ( $lang !~ /${l}$/i );
-                            next
-                              unless (
-                                $this_line_is_semicolon_terminated
+                # ... unless there is just one and we can reduce
+                # this to two lines if we do.  For example, this
+                #
+                #
+                #  $bodyA .=
+                #    '($dummy, $pat) = &get_next_tex_cmd;' . '$args .= $pat;'
+                #
+                #  looks better than this:
+                #  $bodyA .= '($dummy, $pat) = &get_next_tex_cmd;'
+                #    . '$args .= $pat;'
 
-                                #  previous line begins with 'and' or 'or'
-                                && $type_ibeg_1 eq 'k'
-                                && $is_and_or{ $tokens_to_go[$ibeg_1] }
+                ( $n == 2 && $n == $nmax && $type_ibeg_1 ne $type_ibeg_2 )
 
-                              );
-                        }
+                # ... or this would strand a short quote , like this
+                #                . "some long quote"
+                #                . "\n";
 
-                        # handle all other leading keywords
-                        else {
+                || (   $types_to_go[$i_next_nonblank] eq 'Q'
+                    && $i_next_nonblank >= $iend_2 - 1
+                    && $token_lengths_to_go[$i_next_nonblank] <
+                    $rOpts_short_concatenation_item_length )
+              );
+        }
 
-                            # keywords look best at start of lines,
-                            # but combine things like "1 while"
-                            unless ( $is_assignment{$type_iend_1} ) {
-                                next
-                                  if ( ( $type_iend_1 ne 'k' )
-                                    && ( $tokens_to_go[$ibeg_2] ne 'while' ) );
-                            }
-                        }
-                    }
+        # handle leading keyword..
+        elsif ( $type_ibeg_2 eq 'k' ) {
 
-                    # similar treatment of && and || as above for 'and' and
-                    # 'or': NOTE: This block of code is currently bypassed
-                    # because of a previous block but is retained for possible
-                    # future use.
-                    elsif ( $is_amp_amp{$type_ibeg_2} ) {
+            # handle leading "or"
+            if ( $tokens_to_go[$ibeg_2] eq 'or' ) {
+                return
+                  unless (
+                    $this_line_is_semicolon_terminated
+                    && (
+                        $type_ibeg_1 eq '}'
+                        || (
 
-                        # maybe looking at something like:
-                        # unless $TEXTONLY || $item =~ m%</?(hr>|p>|a|img)%i;
+                            # following 'if' or 'unless' or 'or'
+                            $type_ibeg_1 eq 'k'
+                            && $is_if_unless{ $tokens_to_go[$ibeg_1] }
 
-                        next
-                          unless (
-                            $this_line_is_semicolon_terminated
+                            # important: only combine a very simple
+                            # or statement because the step below
+                            # may have combined a trailing 'and'
+                            # with this or, and we do not want to
+                            # then combine everything together
+                            && ( $iend_2 - $ibeg_2 <= 7 )
+                        )
+                    )
+                  );
 
-                            # previous line begins with an 'if' or 'unless'
-                            # keyword
-                            && $type_ibeg_1 eq 'k'
-                            && $is_if_unless{ $tokens_to_go[$ibeg_1] }
+                #X: RT #81854
+                $forced_breakpoint_to_go[$iend_1] = 0
+                  unless ( $old_breakpoint_to_go[$iend_1] );
+            }
 
-                          );
-                    }
+            # handle leading 'and' and 'xor'
+            elsif ($tokens_to_go[$ibeg_2] eq 'and'
+                || $tokens_to_go[$ibeg_2] eq 'xor' )
+            {
 
-                    # handle line with leading = or similar
-                    elsif ( $is_assignment{$type_ibeg_2} ) {
-                        next unless ( $n == 1 || $n == $nmax );
-                        next if ( $old_breakpoint_to_go[$iend_1] );
-                        next
-                          unless (
+                # Decide if we will combine a single terminal 'and'
+                # after an 'if' or 'unless'.
 
-                            # unless we can reduce this to two lines
-                            $nmax == 2
+                #     This looks best with the 'and' on the same
+                #     line as the 'if':
+                #
+                #         $a = 1
+                #           if $seconds and $nu < 2;
+                #
+                #     But this looks better as shown:
+                #
+                #         $a = 1
+                #           if !$this->{Parents}{$_}
+                #           or $this->{Parents}{$_} eq $_;
+                #
+                return
+                  unless (
+                    $this_line_is_semicolon_terminated
+                    && (
 
-                            # or three lines, the last with a leading semicolon
-                            || ( $nmax == 3 && $types_to_go[$ibeg_nmax] eq ';' )
+                        # following 'if' or 'unless' or 'or'
+                        $type_ibeg_1 eq 'k'
+                        && (   $is_if_unless{ $tokens_to_go[$ibeg_1] }
+                            || $tokens_to_go[$ibeg_1] eq 'or' )
+                    )
+                  );
+            }
 
-                            # or the next line ends with a here doc
-                            || $type_iend_2 eq 'h'
+            # handle leading "if" and "unless"
+            elsif ( $is_if_unless{ $tokens_to_go[$ibeg_2] } ) {
 
-                            # or this is a short line ending in ;
-                            || (   $n == $nmax
-                                && $this_line_is_semicolon_terminated )
-                          );
-                        $forced_breakpoint_to_go[$iend_1] = 0;
-                    }
+                # Combine something like:
+                #    next
+                #      if ( $lang !~ /${l}$/i );
+                # into:
+                #    next if ( $lang !~ /${l}$/i );
+                return
+                  unless (
+                    $this_line_is_semicolon_terminated
 
-                    #----------------------------------------------------------
-                    # Recombine Section 4:
-                    # Combine the lines if we arrive here and it is possible
-                    #----------------------------------------------------------
+                    #  previous line begins with 'and' or 'or'
+                    && $type_ibeg_1 eq 'k'
+                    && $is_and_or{ $tokens_to_go[$ibeg_1] }
 
-                    # honor hard breakpoints
-                    next if ( $forced_breakpoint_to_go[$iend_1] > 0 );
+                  );
+            }
 
-                    my $bs = $rbond_strength_to_go->[$iend_1] + $bs_tweak;
+            # handle all other leading keywords
+            else {
 
-                 # Require a few extra spaces before recombining lines if we are
-                 # at an old breakpoint unless this is a simple list or terminal
-                 # line.  The goal is to avoid oscillating between two
-                 # quasi-stable end states.  For example this snippet caused
-                 # problems:
-##    my $this =
-##    bless {
-##        TText => "[" . ( join ',', map { "\"$_\"" } split "\n", $_ ) . "]"
-##      },
-##      $type;
-                    next
-                      if ( $old_breakpoint_to_go[$iend_1]
-                        && !$this_line_is_semicolon_terminated
-                        && $n < $nmax
-                        && $excess + 4 > 0
-                        && $type_iend_2 ne ',' );
+                # keywords look best at start of lines,
+                # but combine things like "1 while"
+                unless ( $is_assignment{$type_iend_1} ) {
+                    return
+                      if ( ( $type_iend_1 ne 'k' )
+                        && ( $tokens_to_go[$ibeg_2] ne 'while' ) );
+                }
+            }
+        }
 
-                    # do not recombine if we would skip in indentation levels
-                    if ( $n < $nmax ) {
-                        my $if_next = $ri_beg->[ $n + 1 ];
-                        next
-                          if (
-                               $levels_to_go[$ibeg_1] < $levels_to_go[$ibeg_2]
-                            && $levels_to_go[$ibeg_2] < $levels_to_go[$if_next]
+        # similar treatment of && and || as above for 'and' and
+        # 'or': NOTE: This block of code is currently bypassed
+        # because of a previous block but is retained for possible
+        # future use.
+        elsif ( $is_amp_amp{$type_ibeg_2} ) {
 
-                            # but an isolated 'if (' is undesirable
-                            && !(
-                                   $n == 1
-                                && $iend_1 - $ibeg_1 <= 2
-                                && $type_ibeg_1 eq 'k'
-                                && $tokens_to_go[$ibeg_1] eq 'if'
-                                && $tokens_to_go[$iend_1] ne '('
-                            )
-                          );
-                    }
+            # maybe looking at something like:
+            # unless $TEXTONLY || $item =~ m%</?(hr>|p>|a|img)%i;
 
-                    # honor no-break's
-                    ## next if ( $bs >= NO_BREAK - 1 );  # removed for b1257
+            return
+              unless (
+                $this_line_is_semicolon_terminated
 
-                    # remember the pair with the greatest bond strength
-                    if ( !$n_best ) {
-                        $n_best  = $n;
-                        $bs_best = $bs;
-                    }
-                    else {
+                # previous line begins with an 'if' or 'unless'
+                # keyword
+                && $type_ibeg_1 eq 'k'
+                && $is_if_unless{ $tokens_to_go[$ibeg_1] }
 
-                        if ( $bs > $bs_best ) {
-                            $n_best  = $n;
-                            $bs_best = $bs;
-                        }
-                    }
-                }
+              );
+        }
 
-                # recombine the pair with the greatest bond strength
-                if ($n_best) {
-                    splice @{$ri_beg}, $n_best,     1;
-                    splice @{$ri_end}, $n_best - 1, 1;
-                    splice @joint,     $n_best,     1;
+        # handle line with leading = or similar
+        elsif ( $is_assignment{$type_ibeg_2} ) {
+            return unless ( $n == 1 || $n == $nmax );
+            return if ( $old_breakpoint_to_go[$iend_1] );
+            return
+              unless (
 
-                    # keep going if we are still making progress
-                    $more_to_do++;
-                }
-            }    # end iteration loop
+                # unless we can reduce this to two lines
+                $nmax == 2
 
-        }    # end loop over sections
+                # or three lines, the last with a leading semicolon
+                || ( $nmax == 3 && $types_to_go[$ibeg_nmax] eq ';' )
 
-      RETURN:
+                # or the next line ends with a here doc
+                || $type_iend_2 eq 'h'
 
-        if (DEBUG_RECOMBINE) {
-            my $nmax_last = @{$ri_end} - 1;
-            print STDERR
-"exiting recombine with $nmax_last lines, starting lines=$nmax_start, iterations=$it_count, max_it=$it_count_max numsec=$num_sections\n";
+                # or this is a short line ending in ;
+                || (   $n == $nmax
+                    && $this_line_is_semicolon_terminated )
+              );
+            $forced_breakpoint_to_go[$iend_1] = 0;
         }
-        return;
-    } ## end sub recombine_breakpoints
+        return ( 1, $bs_tweak );
+    } ## end sub recombine_section_3
+
 } ## end closure recombine_breakpoints
 
 sub insert_final_ternary_breaks {
@@ -17287,8 +18642,12 @@ sub correct_lp_indentation {
                       get_saved_opening_indentation($align_seqno);
                     if ( defined($indent) ) {
 
-                        # FIXME: should use '1' here if no space after opening
-                        # and '2' if want space; hardwired at 1 like -gnu-style
+                        # NOTE: we could use '1' here if no space after
+                        # opening and '2' if want space; it is hardwired at 1
+                        # like -gnu-style. But it is probably best to leave
+                        # this alone because changing it would change
+                        # formatting of much existing code without any
+                        # significant benefit.
                         $actual_pos = get_spaces($indent) + $offset + 1;
                     }
                 }
@@ -17532,6 +18891,8 @@ sub undo_lp_ci {
 # CODE SECTION 10: Code to break long statments
 ###############################################
 
+use constant DEBUG_BREAK_LINES => 0;
+
 sub break_long_lines {
 
     #-----------------------------------------------------------
@@ -17539,22 +18900,13 @@ sub break_long_lines {
     # maximum line length.
     #-----------------------------------------------------------
 
-    # Define an array of indexes for inserting newline characters to
-    # keep the line lengths below the maximum desired length.  There is
-    # an implied break after the last token, so it need not be included.
+    my ( $self, $saw_good_break, $rcolon_list, $rbond_strength_bias ) = @_;
 
-    # Method:
-    # This routine is part of series of routines which adjust line
-    # lengths.  It is only called if a statement is longer than the
-    # maximum line length, or if a preliminary scanning located
-    # desirable break points.   Sub break_lists has already looked at
-    # these tokens and set breakpoints (in array
-    # $forced_breakpoint_to_go[$i]) where it wants breaks (for example
-    # after commas, after opening parens, and before closing parens).
-    # This routine will honor these breakpoints and also add additional
-    # breakpoints as necessary to keep the line length below the maximum
-    # requested.  It bases its decision on where the 'bond strength' is
-    # lowest.
+    # Input parameters:
+    #  $saw_good_break - a flag set by break_lists
+    #  $rcolon_list    - ref to a list of all the ? and : tokens in the batch,
+    #    in order.
+    #  $rbond_strength_bias - small bond strength bias values set by break_lists
 
     # Output: returns references to the arrays:
     #  @i_first
@@ -17562,673 +18914,731 @@ sub break_long_lines {
     # which contain the indexes $i of the first and last tokens on each
     # line.
 
-    # In addition, the array:
-    #   $forced_breakpoint_to_go[$i]
-    # may be updated to be =1 for any index $i after which there must be
-    # a break.  This signals later routines not to undo the breakpoint.
+    # In addition, the array:
+    #   $forced_breakpoint_to_go[$i]
+    # may be updated to be =1 for any index $i after which there must be
+    # a break.  This signals later routines not to undo the breakpoint.
+
+    # Method:
+    # This routine is called if a statement is longer than the maximum line
+    # length, or if a preliminary scanning located desirable break points.
+    # Sub break_lists has already looked at these tokens and set breakpoints
+    # (in array $forced_breakpoint_to_go[$i]) where it wants breaks (for
+    # example after commas, after opening parens, and before closing parens).
+    # This routine will honor these breakpoints and also add additional
+    # breakpoints as necessary to keep the line length below the maximum
+    # requested.  It bases its decision on where the 'bond strength' is
+    # lowest.
+
+    my @i_first        = ();    # the first index to output
+    my @i_last         = ();    # the last index to output
+    my @i_colon_breaks = ();    # needed to decide if we have to break at ?'s
+    if ( $types_to_go[0] eq ':' ) { push @i_colon_breaks, 0 }
+
+    # Get the 'bond strengths' between tokens
+    my $rbond_strength_to_go = $self->set_bond_strengths();
+
+    # Add any comma bias set by break_lists
+    if ( @{$rbond_strength_bias} ) {
+        foreach my $item ( @{$rbond_strength_bias} ) {
+            my ( $ii, $bias ) = @{$item};
+            if ( $ii >= 0 && $ii <= $max_index_to_go ) {
+                $rbond_strength_to_go->[$ii] += $bias;
+            }
+            elsif (DEVEL_MODE) {
+                my $KK  = $K_to_go[0];
+                my $lno = $self->[_rLL_]->[$KK]->[_LINE_INDEX_];
+                Fault(
+"Bad bond strength bias near line $lno: i=$ii must be between 0 and $max_index_to_go\n"
+                );
+            }
+        }
+    }
+
+    my $imin = 0;
+    my $imax = $max_index_to_go;
+    if ( $types_to_go[$imin] eq 'b' ) { $imin++ }
+    if ( $types_to_go[$imax] eq 'b' ) { $imax-- }
+
+    my $i_begin             = $imin;
+    my $last_break_strength = NO_BREAK;
+    my $i_last_break        = -1;
+    my $line_count          = 0;
+
+    # see if any ?/:'s are in order
+    my $colons_in_order = 1;
+    my $last_tok        = EMPTY_STRING;
+    foreach ( @{$rcolon_list} ) {
+        if ( $_ eq $last_tok ) { $colons_in_order = 0; last }
+        $last_tok = $_;
+    }
+
+    # This is a sufficient but not necessary condition for colon chain
+    my $is_colon_chain = ( $colons_in_order && @{$rcolon_list} > 2 );
+
+    #------------------------------------------
+    # BEGINNING of main loop to set breakpoints
+    # Keep iterating until we reach the end
+    #------------------------------------------
+    while ( $i_begin <= $imax ) {
+
+        #------------------------------------------------------------------
+        # Find the best next breakpoint based on token-token bond strengths
+        #------------------------------------------------------------------
+        my ( $i_lowest, $lowest_strength, $leading_alignment_type, $Msg ) =
+          $self->break_lines_inner_loop(
+
+            $i_begin,
+            $i_last_break,
+            $imax,
+            $last_break_strength,
+            $line_count,
+            $rbond_strength_to_go,
+            $saw_good_break,
+
+          );
+
+        # Now make any adjustments required by ternary breakpoint rules
+        if ( @{$rcolon_list} ) {
+
+            my $i_next_nonblank = $inext_to_go[$i_lowest];
+
+            #-------------------------------------------------------
+            # ?/: rule 1 : if a break here will separate a '?' on this
+            # line from its closing ':', then break at the '?' instead.
+            # But do not break a sequential chain of ?/: statements
+            #-------------------------------------------------------
+            if ( !$is_colon_chain ) {
+                foreach my $i ( $i_begin + 1 .. $i_lowest - 1 ) {
+                    next unless ( $tokens_to_go[$i] eq '?' );
+
+                    # do not break if statement is broken by side comment
+                    next
+                      if ( $tokens_to_go[$max_index_to_go] eq '#'
+                        && terminal_type_i( 0, $max_index_to_go ) !~
+                        /^[\;\}]$/ );
+
+                    # no break needed if matching : is also on the line
+                    next
+                      if ( $mate_index_to_go[$i] >= 0
+                        && $mate_index_to_go[$i] <= $i_next_nonblank );
+
+                    $i_lowest = $i;
+                    if ( $want_break_before{'?'} ) { $i_lowest-- }
+                    $i_next_nonblank = $inext_to_go[$i_lowest];
+                    last;
+                }
+            }
+
+            my $next_nonblank_type = $types_to_go[$i_next_nonblank];
+
+            #-------------------------------------------------------------
+            # ?/: rule 2 : if we break at a '?', then break at its ':'
+            #
+            # Note: this rule is also in sub break_lists to handle a break
+            # at the start and end of a line (in case breaks are dictated
+            # by side comments).
+            #-------------------------------------------------------------
+            if ( $next_nonblank_type eq '?' ) {
+                $self->set_closing_breakpoint($i_next_nonblank);
+            }
+            elsif ( $types_to_go[$i_lowest] eq '?' ) {
+                $self->set_closing_breakpoint($i_lowest);
+            }
+
+            #--------------------------------------------------------
+            # ?/: rule 3 : if we break at a ':' then we save
+            # its location for further work below.  We may need to go
+            # back and break at its '?'.
+            #--------------------------------------------------------
+            if ( $next_nonblank_type eq ':' ) {
+                push @i_colon_breaks, $i_next_nonblank;
+            }
+            elsif ( $types_to_go[$i_lowest] eq ':' ) {
+                push @i_colon_breaks, $i_lowest;
+            }
+
+            # here we should set breaks for all '?'/':' pairs which are
+            # separated by this line
+        }
+
+        # guard against infinite loop (should never happen)
+        if ( $i_lowest <= $i_last_break ) {
+            DEVEL_MODE
+              && Fault("i_lowest=$i_lowest <= i_last_break=$i_last_break\n");
+            $i_lowest = $imax;
+        }
+
+        DEBUG_BREAK_LINES
+          && print STDOUT
+"BREAK: best is i = $i_lowest strength = $lowest_strength;\nReason>> $Msg\n";
+
+        $line_count++;
+
+        # save this line segment, after trimming blanks at the ends
+        push( @i_first,
+            ( $types_to_go[$i_begin] eq 'b' ) ? $i_begin + 1 : $i_begin );
+        push( @i_last,
+            ( $types_to_go[$i_lowest] eq 'b' ) ? $i_lowest - 1 : $i_lowest );
+
+        # set a forced breakpoint at a container opening, if necessary, to
+        # signal a break at a closing container.  Excepting '(' for now.
+        if (
+            (
+                   $tokens_to_go[$i_lowest] eq '{'
+                || $tokens_to_go[$i_lowest] eq '['
+            )
+            && !$forced_breakpoint_to_go[$i_lowest]
+          )
+        {
+            $self->set_closing_breakpoint($i_lowest);
+        }
+
+        # get ready to find the next breakpoint
+        $last_break_strength = $lowest_strength;
+        $i_last_break        = $i_lowest;
+        $i_begin             = $i_lowest + 1;
+
+        # skip past a blank
+        if ( ( $i_begin <= $imax ) && ( $types_to_go[$i_begin] eq 'b' ) ) {
+            $i_begin++;
+        }
+    }
+
+    #-------------------------------------------------
+    # END of main loop to set continuation breakpoints
+    #-------------------------------------------------
+
+    #-----------------------------------------------------------
+    # ?/: rule 4 -- if we broke at a ':', then break at
+    # corresponding '?' unless this is a chain of ?: expressions
+    #-----------------------------------------------------------
+    if (@i_colon_breaks) {
+        my $is_chain = ( $colons_in_order && @i_colon_breaks > 1 );
+        if ( !$is_chain ) {
+            $self->do_colon_breaks( \@i_colon_breaks, \@i_first, \@i_last );
+        }
+    }
+
+    return ( \@i_first, \@i_last, $rbond_strength_to_go );
+} ## end sub break_long_lines
 
-    my ( $self, $saw_good_break, $rcolon_list, $rbond_strength_bias ) = @_;
+# small bond strength numbers to help break ties
+use constant TINY_BIAS => 0.0001;
+use constant MAX_BIAS  => 0.001;
 
-    # @{$rcolon_list} is a list of all the ? and : tokens in the batch, in
-    # order.
+sub break_lines_inner_loop {
 
-    use constant DEBUG_BREAK_LINES => 0;
+    #-----------------------------------------------------------------
+    # Find the best next breakpoint in index range ($i_begin .. $imax)
+    # which, if possible, does not exceed the maximum line length.
+    #-----------------------------------------------------------------
 
-    my @i_first        = ();    # the first index to output
-    my @i_last         = ();    # the last index to output
-    my @i_colon_breaks = ();    # needed to decide if we have to break at ?'s
-    if ( $types_to_go[0] eq ':' ) { push @i_colon_breaks, 0 }
+    my (
+        $self,    #
 
-    my $rbond_strength_to_go = $self->set_bond_strengths();
+        $i_begin,
+        $i_last_break,
+        $imax,
+        $last_break_strength,
+        $line_count,
+        $rbond_strength_to_go,
+        $saw_good_break,
 
-    # Add any comma bias set by break_lists
-    if ( @{$rbond_strength_bias} ) {
-        foreach my $item ( @{$rbond_strength_bias} ) {
-            my ( $ii, $bias ) = @{$item};
-            if ( $ii >= 0 && $ii <= $max_index_to_go ) {
-                $rbond_strength_to_go->[$ii] += $bias;
-            }
-            elsif (DEVEL_MODE) {
-                my $KK  = $K_to_go[0];
-                my $lno = $self->[_rLL_]->[$KK]->[_LINE_INDEX_];
-                Fault(
-"Bad bond strength bias near line $lno: i=$ii must be between 0 and $max_index_to_go\n"
-                );
-            }
+    ) = @_;
+
+    # Given:
+    #   $i_begin               = first index of range
+    #   $i_last_break          = index of previous break
+    #   $imax                  = last index of range
+    #   $last_break_strength   = bond strength of last break
+    #   $line_count            = number of output lines so far
+    #   $rbond_strength_to_go  = ref to array of bond strengths
+    #   $saw_good_break        = true if old line had a good breakpoint
+
+    # Returns:
+    #   $i_lowest               = index of best breakpoint
+    #   $lowest_strength        = 'bond strength' at best breakpoint
+    #   $leading_alignment_type = special token type after break
+    #   $Msg                    = string of debug info
+
+    my $Msg                    = EMPTY_STRING;
+    my $strength               = NO_BREAK;
+    my $i_test                 = $i_begin - 1;
+    my $i_lowest               = -1;
+    my $starting_sum           = $summed_lengths_to_go[$i_begin];
+    my $lowest_strength        = NO_BREAK;
+    my $leading_alignment_type = EMPTY_STRING;
+    my $leading_spaces         = leading_spaces_to_go($i_begin);
+    my $maximum_line_length =
+      $maximum_line_length_at_level[ $levels_to_go[$i_begin] ];
+    DEBUG_BREAK_LINES
+      && do {
+        $Msg .= "updating leading spaces to be $leading_spaces at i=$i_begin\n";
+      };
+
+    # Do not separate an isolated bare word from an opening paren.
+    # Alternate Fix #2 for issue b1299.  This waits as long as possible
+    # to make the decision.
+    if ( $types_to_go[$i_begin] eq 'i'
+        && substr( $tokens_to_go[$i_begin], 0, 1 ) =~ /\w/ )
+    {
+        my $i_next_nonblank = $inext_to_go[$i_begin];
+        if ( $tokens_to_go[$i_next_nonblank] eq '(' ) {
+            $rbond_strength_to_go->[$i_begin] = NO_BREAK;
         }
     }
 
-    my $imin = 0;
-    my $imax = $max_index_to_go;
-    if ( $types_to_go[$imin] eq 'b' ) { $imin++ }
-    if ( $types_to_go[$imax] eq 'b' ) { $imax-- }
-    my $i_begin = $imin;    # index for starting next iteration
+    #-------------------------------------------------
+    # Begin loop over the indexes in the _to_go arrays
+    #-------------------------------------------------
+    while ( ++$i_test <= $imax ) {
+        my $type                     = $types_to_go[$i_test];
+        my $token                    = $tokens_to_go[$i_test];
+        my $next_type                = $types_to_go[ $i_test + 1 ];
+        my $next_token               = $tokens_to_go[ $i_test + 1 ];
+        my $i_next_nonblank          = $inext_to_go[$i_test];
+        my $next_nonblank_type       = $types_to_go[$i_next_nonblank];
+        my $next_nonblank_token      = $tokens_to_go[$i_next_nonblank];
+        my $next_nonblank_block_type = $block_type_to_go[$i_next_nonblank];
 
-    my $leading_spaces          = leading_spaces_to_go($imin);
-    my $line_count              = 0;
-    my $last_break_strength     = NO_BREAK;
-    my $i_last_break            = -1;
-    my $max_bias                = 0.001;
-    my $tiny_bias               = 0.0001;
-    my $leading_alignment_token = EMPTY_STRING;
-    my $leading_alignment_type  = EMPTY_STRING;
+        #---------------------------------------------------------------
+        # Section A: Get token-token strength and handle any adjustments
+        #---------------------------------------------------------------
 
-    # see if any ?/:'s are in order
-    my $colons_in_order = 1;
-    my $last_tok        = EMPTY_STRING;
-    foreach ( @{$rcolon_list} ) {
-        if ( $_ eq $last_tok ) { $colons_in_order = 0; last }
-        $last_tok = $_;
-    }
+        # adjustments to the previous bond strength may have been made, and
+        # we must keep the bond strength of a token and its following blank
+        # the same;
+        my $last_strength = $strength;
+        $strength = $rbond_strength_to_go->[$i_test];
+        if ( $type eq 'b' ) { $strength = $last_strength }
 
-    # This is a sufficient but not necessary condition for colon chain
-    my $is_colon_chain = ( $colons_in_order && @{$rcolon_list} > 2 );
+        # reduce strength a bit to break ties at an old comma breakpoint ...
+        if (
 
-    my $Msg = EMPTY_STRING;
+            $old_breakpoint_to_go[$i_test]
 
-    #-------------------------------------------------------
-    # BEGINNING of main loop to set continuation breakpoints
-    # Keep iterating until we reach the end
-    #-------------------------------------------------------
-    while ( $i_begin <= $imax ) {
-        my $lowest_strength        = NO_BREAK;
-        my $starting_sum           = $summed_lengths_to_go[$i_begin];
-        my $i_lowest               = -1;
-        my $i_test                 = -1;
-        my $lowest_next_token      = EMPTY_STRING;
-        my $lowest_next_type       = 'b';
-        my $i_lowest_next_nonblank = -1;
-        my $maximum_line_length =
-          $maximum_line_length_at_level[ $levels_to_go[$i_begin] ];
-
-        # Do not separate an isolated bare word from an opening paren.
-        # Alternate Fix #2 for issue b1299.  This waits as long as possible
-        # to make the decision.
-        if ( $types_to_go[$i_begin] eq 'i'
-            && substr( $tokens_to_go[$i_begin], 0, 1 ) =~ /\w/ )
+            # Patch: limited to just commas to avoid blinking states
+            && $type eq ','
+
+            # which is a 'good' breakpoint, meaning ...
+            # we don't want to break before it
+            && !$want_break_before{$type}
+
+            # and either we want to break before the next token
+            # or the next token is not short (i.e. not a '*', '/' etc.)
+            && $i_next_nonblank <= $imax
+            && (   $want_break_before{$next_nonblank_type}
+                || $token_lengths_to_go[$i_next_nonblank] > 2
+                || $next_nonblank_type eq ','
+                || $is_opening_type{$next_nonblank_type} )
+          )
         {
-            my $i_next_nonblank = $inext_to_go[$i_begin];
-            if ( $tokens_to_go[$i_next_nonblank] eq '(' ) {
-                $rbond_strength_to_go->[$i_begin] = NO_BREAK;
-            }
+            $strength -= TINY_BIAS;
+            DEBUG_BREAK_LINES && do { $Msg .= " :-bias at i=$i_test" };
         }
 
-        #-------------------------------------------------------
-        # BEGINNING of inner loop to find the best next breakpoint
-        #-------------------------------------------------------
-        my $strength = NO_BREAK;
-        $i_test = $i_begin - 1;
-        while ( ++$i_test <= $imax ) {
-            my $type                     = $types_to_go[$i_test];
-            my $token                    = $tokens_to_go[$i_test];
-            my $next_type                = $types_to_go[ $i_test + 1 ];
-            my $next_token               = $tokens_to_go[ $i_test + 1 ];
-            my $i_next_nonblank          = $inext_to_go[$i_test];
-            my $next_nonblank_type       = $types_to_go[$i_next_nonblank];
-            my $next_nonblank_token      = $tokens_to_go[$i_next_nonblank];
-            my $next_nonblank_block_type = $block_type_to_go[$i_next_nonblank];
-
-            # adjustments to the previous bond strength may have been made, and
-            # we must keep the bond strength of a token and its following blank
-            # the same;
-            my $last_strength = $strength;
-            $strength = $rbond_strength_to_go->[$i_test];
-            if ( $type eq 'b' ) { $strength = $last_strength }
-
-            # reduce strength a bit to break ties at an old comma breakpoint ...
-            if (
+        # otherwise increase strength a bit if this token would be at the
+        # maximum line length.  This is necessary to avoid blinking
+        # in the above example when the -iob flag is added.
+        else {
+            my $len =
+              $leading_spaces +
+              $summed_lengths_to_go[ $i_test + 1 ] -
+              $starting_sum;
+            if ( $len >= $maximum_line_length ) {
+                $strength += TINY_BIAS;
+                DEBUG_BREAK_LINES && do { $Msg .= " :+bias at i=$i_test" };
+            }
+        }
 
-                $old_breakpoint_to_go[$i_test]
+        #-------------------------------------
+        # Section B: Handle forced breakpoints
+        #-------------------------------------
+        my $must_break;
 
-                # Patch: limited to just commas to avoid blinking states
-                && $type eq ','
+        # Force an immediate break at certain operators
+        # with lower level than the start of the line,
+        # unless we've already seen a better break.
+        #
+        # Note on an issue with a preceding '?' :
 
-                # which is a 'good' breakpoint, meaning ...
-                # we don't want to break before it
-                && !$want_break_before{$type}
+        # There may be a break at a previous ? if the line is long.  Because
+        # of this we do not want to force a break if there is a previous ? on
+        # this line.  For now the best way to do this is to not break if we
+        # have seen a lower strength point, which is probably a ?.
+        #
+        # Example of unwanted breaks we are avoiding at a '.' following a ?
+        # from pod2html using perltidy -gnu:
+        # )
+        # ? "\n&lt;A NAME=\""
+        # . $value
+        # . "\"&gt;\n$text&lt;/A&gt;\n"
+        # : "\n$type$pod2.html\#" . $value . "\"&gt;$text&lt;\/A&gt;\n";
+        if (
+            ( $strength <= $lowest_strength )
+            && ( $nesting_depth_to_go[$i_begin] >
+                $nesting_depth_to_go[$i_next_nonblank] )
+            && (
+                $next_nonblank_type =~ /^(\.|\&\&|\|\|)$/
+                || (
+                    $next_nonblank_type eq 'k'
 
-                # and either we want to break before the next token
-                # or the next token is not short (i.e. not a '*', '/' etc.)
-                && $i_next_nonblank <= $imax
-                && (   $want_break_before{$next_nonblank_type}
-                    || $token_lengths_to_go[$i_next_nonblank] > 2
-                    || $next_nonblank_type eq ','
-                    || $is_opening_type{$next_nonblank_type} )
-              )
-            {
-                $strength -= $tiny_bias;
-                DEBUG_BREAK_LINES && do { $Msg .= " :-bias at i=$i_test" };
-            }
+                    ##  /^(and|or)$/  # note: includes 'xor' now
+                    && $is_and_or{$next_nonblank_token}
+                )
+            )
+          )
+        {
+            $self->set_forced_breakpoint($i_next_nonblank);
+            DEBUG_BREAK_LINES
+              && do { $Msg .= " :Forced break at i=$i_next_nonblank" };
+        }
 
-            # otherwise increase strength a bit if this token would be at the
-            # maximum line length.  This is necessary to avoid blinking
-            # in the above example when the -iob flag is added.
-            else {
-                my $len =
-                  $leading_spaces +
-                  $summed_lengths_to_go[ $i_test + 1 ] -
-                  $starting_sum;
-                if ( $len >= $maximum_line_length ) {
-                    $strength += $tiny_bias;
-                    DEBUG_BREAK_LINES && do { $Msg .= " :+bias at i=$i_test" };
-                }
-            }
+        if (
 
-            my $must_break = 0;
+            # Try to put a break where requested by break_lists
+            $forced_breakpoint_to_go[$i_test]
 
-            # Force an immediate break at certain operators
-            # with lower level than the start of the line,
-            # unless we've already seen a better break.
+            # break between ) { in a continued line so that the '{' can
+            # be outdented
+            # See similar logic in break_lists which catches instances
+            # where a line is just something like ') {'.  We have to
+            # be careful because the corresponding block keyword might
+            # not be on the first line, such as 'for' here:
             #
-            #------------------------------------
-            # Note on an issue with a preceding ?
-            #------------------------------------
-            # We don't include a ? in the above list, but there may
-            # be a break at a previous ? if the line is long.
-            # Because of this we do not want to force a break if
-            # there is a previous ? on this line.  For now the best way
-            # to do this is to not break if we have seen a lower strength
-            # point, which is probably a ?.
+            # eval {
+            #     for ("a") {
+            #         for $x ( 1, 2 ) { local $_ = "b"; s/(.*)/+$1/ }
+            #     }
+            # };
             #
-            # Example of unwanted breaks we are avoiding at a '.' following a ?
-            # from pod2html using perltidy -gnu:
-            # )
-            # ? "\n&lt;A NAME=\""
-            # . $value
-            # . "\"&gt;\n$text&lt;/A&gt;\n"
-            # : "\n$type$pod2.html\#" . $value . "\"&gt;$text&lt;\/A&gt;\n";
-            if (
-                ( $strength <= $lowest_strength )
-                && ( $nesting_depth_to_go[$i_begin] >
-                    $nesting_depth_to_go[$i_next_nonblank] )
-                && (
-                    $next_nonblank_type =~ /^(\.|\&\&|\|\|)$/
-                    || (
-                        $next_nonblank_type eq 'k'
+            || (
+                   $line_count
+                && ( $token eq ')' )
+                && ( $next_nonblank_type eq '{' )
+                && ($next_nonblank_block_type)
+                && ( $next_nonblank_block_type ne $tokens_to_go[$i_begin] )
+
+                # RT #104427: Dont break before opening sub brace because
+                # sub block breaks handled at higher level, unless
+                # it looks like the preceding list is long and broken
+                && !(
 
-                        ##  /^(and|or)$/  # note: includes 'xor' now
-                        && $is_and_or{$next_nonblank_token}
+                    (
+                           $next_nonblank_block_type =~ /$SUB_PATTERN/
+                        || $next_nonblank_block_type =~ /$ASUB_PATTERN/
                     )
+                    && ( $nesting_depth_to_go[$i_begin] ==
+                        $nesting_depth_to_go[$i_next_nonblank] )
                 )
-              )
-            {
-                $self->set_forced_breakpoint($i_next_nonblank);
+
+                && !$rOpts_opening_brace_always_on_right
+            )
+
+            # There is an implied forced break at a terminal opening brace
+            || ( ( $type eq '{' ) && ( $i_test == $imax ) )
+          )
+        {
+
+            # Forced breakpoints must sometimes be overridden, for example
+            # because of a side comment causing a NO_BREAK.  It is easier
+            # to catch this here than when they are set.
+            if ( $strength < NO_BREAK - 1 ) {
+                $strength   = $lowest_strength - TINY_BIAS;
+                $must_break = 1;
                 DEBUG_BREAK_LINES
-                  && do { $Msg .= " :Forced break at i=$i_next_nonblank" };
+                  && do { $Msg .= " :set must_break at i=$i_next_nonblank" };
             }
+        }
 
-            if (
-
-                # Try to put a break where requested by break_lists
-                $forced_breakpoint_to_go[$i_test]
+        # quit if a break here would put a good terminal token on
+        # the next line and we already have a possible break
+        if (
+               !$must_break
+            && ( $next_nonblank_type eq ';' || $next_nonblank_type eq ',' )
+            && (
+                (
+                    $leading_spaces +
+                    $summed_lengths_to_go[ $i_next_nonblank + 1 ] -
+                    $starting_sum
+                ) > $maximum_line_length
+            )
+          )
+        {
+            if ( $i_lowest >= 0 ) {
+                DEBUG_BREAK_LINES && do {
+                    $Msg .= " :quit at good terminal='$next_nonblank_type'";
+                };
+                last;
+            }
+        }
 
-                # break between ) { in a continued line so that the '{' can
-                # be outdented
-                # See similar logic in break_lists which catches instances
-                # where a line is just something like ') {'.  We have to
-                # be careful because the corresponding block keyword might
-                # not be on the first line, such as 'for' here:
-                #
-                # eval {
-                #     for ("a") {
-                #         for $x ( 1, 2 ) { local $_ = "b"; s/(.*)/+$1/ }
-                #     }
-                # };
-                #
-                || (
-                       $line_count
-                    && ( $token eq ')' )
-                    && ( $next_nonblank_type eq '{' )
-                    && ($next_nonblank_block_type)
-                    && ( $next_nonblank_block_type ne $tokens_to_go[$i_begin] )
-
-                    # RT #104427: Dont break before opening sub brace because
-                    # sub block breaks handled at higher level, unless
-                    # it looks like the preceding list is long and broken
-                    && !(
+        # Avoid a break which would strand a single punctuation
+        # token.  For example, we do not want to strand a leading
+        # '.' which is followed by a long quoted string.
+        # But note that we do want to do this with -extrude (l=1)
+        # so please test any changes to this code on -extrude.
+        if (
+               !$must_break
+            && ( $i_test == $i_begin )
+            && ( $i_test < $imax )
+            && ( $token eq $type )
+            && (
+                (
+                    $leading_spaces +
+                    $summed_lengths_to_go[ $i_test + 1 ] -
+                    $starting_sum
+                ) < $maximum_line_length
+            )
+          )
+        {
+            $i_test = min( $imax, $inext_to_go[$i_test] );
+            DEBUG_BREAK_LINES && do {
+                $Msg .= " :redo at i=$i_test";
+            };
+            redo;
+        }
 
-                        (
-                               $next_nonblank_block_type =~ /$SUB_PATTERN/
-                            || $next_nonblank_block_type =~ /$ASUB_PATTERN/
-                        )
-                        && ( $nesting_depth_to_go[$i_begin] ==
-                            $nesting_depth_to_go[$i_next_nonblank] )
-                    )
+        #------------------------------------------------------------
+        # Section C: Look for the lowest bond strength between tokens
+        #------------------------------------------------------------
+        if ( ( $strength <= $lowest_strength ) && ( $strength < NO_BREAK ) ) {
 
-                    && !$rOpts_opening_brace_always_on_right
-                )
+            # break at previous best break if it would have produced
+            # a leading alignment of certain common tokens, and it
+            # is different from the latest candidate break
+            if ($leading_alignment_type) {
+                DEBUG_BREAK_LINES && do {
+                    $Msg .=
+                      " :last at leading_alignment='$leading_alignment_type'";
+                };
+                last;
+            }
 
-                # There is an implied forced break at a terminal opening brace
-                || ( ( $type eq '{' ) && ( $i_test == $imax ) )
+            # Force at least one breakpoint if old code had good
+            # break It is only called if a breakpoint is required or
+            # desired.  This will probably need some adjustments
+            # over time.  A goal is to try to be sure that, if a new
+            # side comment is introduced into formatted text, then
+            # the same breakpoints will occur.  scbreak.t
+            if (
+                $i_test == $imax            # we are at the end
+                && !$forced_breakpoint_count
+                && $saw_good_break          # old line had good break
+                && $type =~ /^[#;\{]$/      # and this line ends in
+                                            # ';' or side comment
+                && $i_last_break < 0        # and we haven't made a break
+                && $i_lowest >= 0           # and we saw a possible break
+                && $i_lowest < $imax - 1    # (but not just before this ;)
+                && $strength - $lowest_strength < 0.5 * WEAK    # and it's good
               )
             {
 
-                # Forced breakpoints must sometimes be overridden, for example
-                # because of a side comment causing a NO_BREAK.  It is easier
-                # to catch this here than when they are set.
-                if ( $strength < NO_BREAK - 1 ) {
-                    $strength   = $lowest_strength - $tiny_bias;
-                    $must_break = 1;
-                    DEBUG_BREAK_LINES
-                      && do { $Msg .= " :set must_break at i=$i_next_nonblank" };
-                }
+                DEBUG_BREAK_LINES && do {
+                    $Msg .= " :last at good old break\n";
+                };
+                last;
             }
 
-            # quit if a break here would put a good terminal token on
-            # the next line and we already have a possible break
+            # Do not skip past an important break point in a short final
+            # segment.  For example, without this check we would miss the
+            # break at the final / in the following code:
+            #
+            #  $depth_stop =
+            #    ( $tau * $mass_pellet * $q_0 *
+            #        ( 1. - exp( -$t_stop / $tau ) ) -
+            #        4. * $pi * $factor * $k_ice *
+            #        ( $t_melt - $t_ice ) *
+            #        $r_pellet *
+            #        $t_stop ) /
+            #    ( $rho_ice * $Qs * $pi * $r_pellet**2 );
+            #
             if (
-                   !$must_break
-                && ( $next_nonblank_type eq ';' || $next_nonblank_type eq ',' )
-                && (
-                    (
-                        $leading_spaces +
-                        $summed_lengths_to_go[ $i_next_nonblank + 1 ] -
-                        $starting_sum
-                    ) > $maximum_line_length
-                )
+                   $line_count > 2
+                && $i_lowest >= 0    # and we saw a possible break
+                && $i_lowest < $i_test
+                && $i_test > $imax - 2
+                && $nesting_depth_to_go[$i_begin] >
+                $nesting_depth_to_go[$i_lowest]
+                && $lowest_strength < $last_break_strength - .5 * WEAK
               )
             {
-                if ( $i_lowest >= 0 ) {
+                # Make this break for math operators for now
+                my $ir = $inext_to_go[$i_lowest];
+                my $il = $iprev_to_go[$ir];
+                if (   $types_to_go[$il] =~ /^[\/\*\+\-\%]$/
+                    || $types_to_go[$ir] =~ /^[\/\*\+\-\%]$/ )
+                {
                     DEBUG_BREAK_LINES && do {
-                        $Msg .= " :quit at good terminal='$next_nonblank_type'";
+                        $Msg .= " :last-noskip_short";
                     };
                     last;
                 }
             }
 
-            # Avoid a break which would strand a single punctuation
-            # token.  For example, we do not want to strand a leading
-            # '.' which is followed by a long quoted string.
-            # But note that we do want to do this with -extrude (l=1)
-            # so please test any changes to this code on -extrude.
-            if (
-                   !$must_break
-                && ( $i_test == $i_begin )
-                && ( $i_test < $imax )
-                && ( $token eq $type )
-                && (
-                    (
-                        $leading_spaces +
-                        $summed_lengths_to_go[ $i_test + 1 ] -
-                        $starting_sum
-                    ) < $maximum_line_length
-                )
-              )
-            {
-                $i_test = min( $imax, $inext_to_go[$i_test] );
+            # Update the minimum bond strength location
+            $lowest_strength = $strength;
+            $i_lowest        = $i_test;
+            if ($must_break) {
                 DEBUG_BREAK_LINES && do {
-                    $Msg .= " :redo at i=$i_test";
+                    $Msg .= " :last-must_break";
                 };
-                redo;
+                last;
             }
 
-            if ( ( $strength <= $lowest_strength ) && ( $strength < NO_BREAK ) )
+            # set flags to remember if a break here will produce a
+            # leading alignment of certain common tokens
+            if (   $line_count > 0
+                && $i_test < $imax
+                && ( $lowest_strength - $last_break_strength <= MAX_BIAS ) )
             {
-
-                # break at previous best break if it would have produced
-                # a leading alignment of certain common tokens, and it
-                # is different from the latest candidate break
-                if ($leading_alignment_type) {
-                    DEBUG_BREAK_LINES && do {
-                        $Msg .=
-" :last at leading_alignment='$leading_alignment_type'";
-                    };
-                    last;
-                }
-
-                # Force at least one breakpoint if old code had good
-                # break It is only called if a breakpoint is required or
-                # desired.  This will probably need some adjustments
-                # over time.  A goal is to try to be sure that, if a new
-                # side comment is introduced into formatted text, then
-                # the same breakpoints will occur.  scbreak.t
-                if (
-                    $i_test == $imax            # we are at the end
-                    && !$forced_breakpoint_count
-                    && $saw_good_break          # old line had good break
-                    && $type =~ /^[#;\{]$/      # and this line ends in
-                                                # ';' or side comment
-                    && $i_last_break < 0        # and we haven't made a break
-                    && $i_lowest >= 0           # and we saw a possible break
-                    && $i_lowest < $imax - 1    # (but not just before this ;)
-                    && $strength - $lowest_strength < 0.5 * WEAK # and it's good
-                  )
-                {
-
-                    DEBUG_BREAK_LINES && do {
-                        $Msg .= " :last at good old break\n";
-                    };
-                    last;
-                }
-
-                # Do not skip past an important break point in a short final
-                # segment.  For example, without this check we would miss the
-                # break at the final / in the following code:
-                #
-                #  $depth_stop =
-                #    ( $tau * $mass_pellet * $q_0 *
-                #        ( 1. - exp( -$t_stop / $tau ) ) -
-                #        4. * $pi * $factor * $k_ice *
-                #        ( $t_melt - $t_ice ) *
-                #        $r_pellet *
-                #        $t_stop ) /
-                #    ( $rho_ice * $Qs * $pi * $r_pellet**2 );
-                #
+                my $i_last_end = $iprev_to_go[$i_begin];
+                my $tok_beg    = $tokens_to_go[$i_begin];
+                my $type_beg   = $types_to_go[$i_begin];
                 if (
-                       $line_count > 2
-                    && $i_lowest >= 0    # and we saw a possible break
-                    && $i_lowest < $i_test
-                    && $i_test > $imax - 2
-                    && $nesting_depth_to_go[$i_begin] >
-                    $nesting_depth_to_go[$i_lowest]
-                    && $lowest_strength < $last_break_strength - .5 * WEAK
-                  )
-                {
-                    # Make this break for math operators for now
-                    my $ir = $inext_to_go[$i_lowest];
-                    my $il = $iprev_to_go[$ir];
-                    if (   $types_to_go[$il] =~ /^[\/\*\+\-\%]$/
-                        || $types_to_go[$ir] =~ /^[\/\*\+\-\%]$/ )
-                    {
-                        DEBUG_BREAK_LINES && do {
-                            $Msg .= " :last-noskip_short";
-                        };
-                        last;
-                    }
-                }
 
-                # Update the minimum bond strength location
-                $lowest_strength        = $strength;
-                $i_lowest               = $i_test;
-                $lowest_next_token      = $next_nonblank_token;
-                $lowest_next_type       = $next_nonblank_type;
-                $i_lowest_next_nonblank = $i_next_nonblank;
-                if ($must_break) {
-                    DEBUG_BREAK_LINES && do {
-                        $Msg .= " :last-must_break";
-                    };
-                    last;
-                }
+                    # check for leading alignment of certain tokens
+                    (
+                           $tok_beg eq $next_nonblank_token
+                        && $is_chain_operator{$tok_beg}
+                        && (   $type_beg eq 'k'
+                            || $type_beg eq $tok_beg )
+                        && $nesting_depth_to_go[$i_begin] >=
+                        $nesting_depth_to_go[$i_next_nonblank]
+                    )
 
-                # set flags to remember if a break here will produce a
-                # leading alignment of certain common tokens
-                if (   $line_count > 0
-                    && $i_test < $imax
-                    && ( $lowest_strength - $last_break_strength <= $max_bias )
+                    || (   $tokens_to_go[$i_last_end] eq $token
+                        && $is_chain_operator{$token}
+                        && ( $type eq 'k' || $type eq $token )
+                        && $nesting_depth_to_go[$i_last_end] >=
+                        $nesting_depth_to_go[$i_test] )
                   )
                 {
-                    my $i_last_end = $iprev_to_go[$i_begin];
-                    my $tok_beg    = $tokens_to_go[$i_begin];
-                    my $type_beg   = $types_to_go[$i_begin];
-                    if (
-
-                        # check for leading alignment of certain tokens
-                        (
-                               $tok_beg eq $next_nonblank_token
-                            && $is_chain_operator{$tok_beg}
-                            && (   $type_beg eq 'k'
-                                || $type_beg eq $tok_beg )
-                            && $nesting_depth_to_go[$i_begin] >=
-                            $nesting_depth_to_go[$i_next_nonblank]
-                        )
-
-                        || (   $tokens_to_go[$i_last_end] eq $token
-                            && $is_chain_operator{$token}
-                            && ( $type eq 'k' || $type eq $token )
-                            && $nesting_depth_to_go[$i_last_end] >=
-                            $nesting_depth_to_go[$i_test] )
-                      )
-                    {
-                        $leading_alignment_token = $next_nonblank_token;
-                        $leading_alignment_type  = $next_nonblank_type;
-                    }
+                    $leading_alignment_type = $next_nonblank_type;
                 }
             }
+        }
 
-            my $too_long = ( $i_test >= $imax );
-            if ( !$too_long ) {
-                my $next_length =
-                  $leading_spaces +
-                  $summed_lengths_to_go[ $i_test + 2 ] -
-                  $starting_sum;
-                $too_long = $next_length > $maximum_line_length;
+        #-----------------------------------------------------------
+        # Section D: See if the maximum line length will be exceeded
+        #-----------------------------------------------------------
+        my $too_long = ( $i_test >= $imax );
+        if ( !$too_long ) {
+            my $next_length =
+              $leading_spaces +
+              $summed_lengths_to_go[ $i_test + 2 ] -
+              $starting_sum;
+            $too_long = $next_length > $maximum_line_length;
 
-                # To prevent blinkers we will avoid leaving a token exactly at
-                # the line length limit unless it is the last token or one of
-                # several "good" types.
-                #
-                # The following code was a blinker with -pbp before this
-                # modification:
+            # To prevent blinkers we will avoid leaving a token exactly at
+            # the line length limit unless it is the last token or one of
+            # several "good" types.
+            #
+            # The following code was a blinker with -pbp before this
+            # modification:
 ##                    $last_nonblank_token eq '('
 ##                        && $is_indirect_object_taker{ $paren_type
 ##                            [$paren_depth] }
-                # The issue causing the problem is that if the
-                # term [$paren_depth] gets broken across a line then
-                # the whitespace routine doesn't see both opening and closing
-                # brackets and will format like '[ $paren_depth ]'.  This
-                # leads to an oscillation in length depending if we break
-                # before the closing bracket or not.
-                if (  !$too_long
-                    && $i_test + 1 < $imax
-                    && $next_nonblank_type ne ','
-                    && !$is_closing_type{$next_nonblank_type} )
-                {
-                    $too_long = $next_length >= $maximum_line_length;
-                    DEBUG_BREAK_LINES && do {
-                        $Msg .= " :too_long=$too_long" if ($too_long);
-                    }
-                }
-            }
-
-            DEBUG_BREAK_LINES && do {
-                my $ltok = $token;
-                my $rtok =
-                  $next_nonblank_token ? $next_nonblank_token : EMPTY_STRING;
-                my $i_testp2 = $i_test + 2;
-                if ( $i_testp2 > $max_index_to_go + 1 ) {
-                    $i_testp2 = $max_index_to_go + 1;
-                }
-                if ( length($ltok) > 6 ) { $ltok = substr( $ltok, 0, 8 ) }
-                if ( length($rtok) > 6 ) { $rtok = substr( $rtok, 0, 8 ) }
-                print STDOUT
-"BREAK: i=$i_test imax=$imax $types_to_go[$i_test] $next_nonblank_type sp=($leading_spaces) lnext= $summed_lengths_to_go[$i_testp2] 2long=$too_long str=$strength    $ltok $rtok\n";
-            };
-
-            # allow one extra terminal token after exceeding line length
-            # if it would strand this token.
-            if (   $rOpts_fuzzy_line_length
-                && $too_long
-                && $i_lowest == $i_test
-                && $token_lengths_to_go[$i_test] > 1
-                && ( $next_nonblank_type eq ';' || $next_nonblank_type eq ',' )
-              )
-            {
-                $too_long = 0;
-                DEBUG_BREAK_LINES && do {
-                    $Msg .= " :do_not_strand next='$next_nonblank_type'";
-                };
-            }
-
-            # we are done if...
-            if (
-
-                # ... no more space and we have a break
-                $too_long && $i_lowest >= 0
-
-                # ... or no more tokens
-                || $i_test == $imax
-              )
+            # The issue causing the problem is that if the
+            # term [$paren_depth] gets broken across a line then
+            # the whitespace routine doesn't see both opening and closing
+            # brackets and will format like '[ $paren_depth ]'.  This
+            # leads to an oscillation in length depending if we break
+            # before the closing bracket or not.
+            if (  !$too_long
+                && $i_test + 1 < $imax
+                && $next_nonblank_type ne ','
+                && !$is_closing_type{$next_nonblank_type} )
             {
+                $too_long = $next_length >= $maximum_line_length;
                 DEBUG_BREAK_LINES && do {
-                    $Msg .=
-" :Done-too_long=$too_long or i_lowest=$i_lowest or $i_test==imax";
-                };
-                last;
+                    $Msg .= " :too_long=$too_long" if ($too_long);
+                }
             }
         }
 
-        #-------------------------------------------------------
-        # END of inner loop to find the best next breakpoint
-        # Now decide exactly where to put the breakpoint
-        #-------------------------------------------------------
-
-        # it's always ok to break at imax if no other break was found
-        if ( $i_lowest < 0 ) { $i_lowest = $imax }
-
-        # semi-final index calculation
-        my $i_next_nonblank     = $inext_to_go[$i_lowest];
-        my $next_nonblank_type  = $types_to_go[$i_next_nonblank];
-        my $next_nonblank_token = $tokens_to_go[$i_next_nonblank];
-
-        #-------------------------------------------------------
-        # ?/: rule 1 : if a break here will separate a '?' on this
-        # line from its closing ':', then break at the '?' instead.
-        #-------------------------------------------------------
-        foreach my $i ( $i_begin + 1 .. $i_lowest - 1 ) {
-            next unless ( $tokens_to_go[$i] eq '?' );
-
-            # do not break if probable sequence of ?/: statements
-            next if ($is_colon_chain);
-
-            # do not break if statement is broken by side comment
-            next
-              if ( $tokens_to_go[$max_index_to_go] eq '#'
-                && terminal_type_i( 0, $max_index_to_go ) !~ /^[\;\}]$/ );
-
-            # no break needed if matching : is also on the line
-            next
-              if ( $mate_index_to_go[$i] >= 0
-                && $mate_index_to_go[$i] <= $i_next_nonblank );
-
-            $i_lowest = $i;
-            if ( $want_break_before{'?'} ) { $i_lowest-- }
-            last;
-        }
-
-        #-------------------------------------------------------
-        # END of inner loop to find the best next breakpoint:
-        # Break the line after the token with index i=$i_lowest
-        #-------------------------------------------------------
-
-        # final index calculation
-        $i_next_nonblank     = $inext_to_go[$i_lowest];
-        $next_nonblank_type  = $types_to_go[$i_next_nonblank];
-        $next_nonblank_token = $tokens_to_go[$i_next_nonblank];
-
-        DEBUG_BREAK_LINES
-          && print STDOUT
-"BREAK: best is i = $i_lowest strength = $lowest_strength;\nReason>> $Msg\n";
-        $Msg = EMPTY_STRING;
-
-        #-------------------------------------------------------
-        # ?/: rule 2 : if we break at a '?', then break at its ':'
-        #
-        # Note: this rule is also in sub break_lists to handle a break
-        # at the start and end of a line (in case breaks are dictated
-        # by side comments).
-        #-------------------------------------------------------
-        if ( $next_nonblank_type eq '?' ) {
-            $self->set_closing_breakpoint($i_next_nonblank);
-        }
-        elsif ( $types_to_go[$i_lowest] eq '?' ) {
-            $self->set_closing_breakpoint($i_lowest);
-        }
-
-        #-------------------------------------------------------
-        # ?/: rule 3 : if we break at a ':' then we save
-        # its location for further work below.  We may need to go
-        # back and break at its '?'.
-        #-------------------------------------------------------
-        if ( $next_nonblank_type eq ':' ) {
-            push @i_colon_breaks, $i_next_nonblank;
-        }
-        elsif ( $types_to_go[$i_lowest] eq ':' ) {
-            push @i_colon_breaks, $i_lowest;
-        }
-
-        # here we should set breaks for all '?'/':' pairs which are
-        # separated by this line
-
-        $line_count++;
-
-        # save this line segment, after trimming blanks at the ends
-        push( @i_first,
-            ( $types_to_go[$i_begin] eq 'b' ) ? $i_begin + 1 : $i_begin );
-        push( @i_last,
-            ( $types_to_go[$i_lowest] eq 'b' ) ? $i_lowest - 1 : $i_lowest );
+        DEBUG_BREAK_LINES && do {
+            my $ltok = $token;
+            my $rtok =
+              $next_nonblank_token ? $next_nonblank_token : EMPTY_STRING;
+            my $i_testp2 = $i_test + 2;
+            if ( $i_testp2 > $max_index_to_go + 1 ) {
+                $i_testp2 = $max_index_to_go + 1;
+            }
+            if ( length($ltok) > 6 ) { $ltok = substr( $ltok, 0, 8 ) }
+            if ( length($rtok) > 6 ) { $rtok = substr( $rtok, 0, 8 ) }
+            print STDOUT
+"BREAK: i=$i_test imax=$imax $types_to_go[$i_test] $next_nonblank_type sp=($leading_spaces) lnext= $summed_lengths_to_go[$i_testp2] 2long=$too_long str=$strength    $ltok $rtok\n";
+        };
 
-        # set a forced breakpoint at a container opening, if necessary, to
-        # signal a break at a closing container.  Excepting '(' for now.
-        if (
-            (
-                   $tokens_to_go[$i_lowest] eq '{'
-                || $tokens_to_go[$i_lowest] eq '['
-            )
-            && !$forced_breakpoint_to_go[$i_lowest]
-          )
+        # allow one extra terminal token after exceeding line length
+        # if it would strand this token.
+        if (   $rOpts_fuzzy_line_length
+            && $too_long
+            && $i_lowest == $i_test
+            && $token_lengths_to_go[$i_test] > 1
+            && ( $next_nonblank_type eq ';' || $next_nonblank_type eq ',' ) )
         {
-            $self->set_closing_breakpoint($i_lowest);
+            $too_long = 0;
+            DEBUG_BREAK_LINES && do {
+                $Msg .= " :do_not_strand next='$next_nonblank_type'";
+            };
         }
 
-        # get ready to go again
-        $i_begin                 = $i_lowest + 1;
-        $last_break_strength     = $lowest_strength;
-        $i_last_break            = $i_lowest;
-        $leading_alignment_token = EMPTY_STRING;
-        $leading_alignment_type  = EMPTY_STRING;
-        $lowest_next_token       = EMPTY_STRING;
-        $lowest_next_type        = 'b';
+        # Stop if line will be too long and we have a solution
+        if (
 
-        if ( ( $i_begin <= $imax ) && ( $types_to_go[$i_begin] eq 'b' ) ) {
-            $i_begin++;
-        }
+            # ... no more space and we have a break
+            $too_long && $i_lowest >= 0
 
-        # update indentation size
-        if ( $i_begin <= $imax ) {
-            $leading_spaces = leading_spaces_to_go($i_begin);
-            DEBUG_BREAK_LINES
-              && print STDOUT
-              "updating leading spaces to be $leading_spaces at i=$i_begin\n";
+            # ... or no more tokens
+            || $i_test == $imax
+          )
+        {
+            DEBUG_BREAK_LINES && do {
+                $Msg .=
+" :Done-too_long=$too_long or i_lowest=$i_lowest or $i_test==imax";
+            };
+            last;
         }
     }
 
-    #-------------------------------------------------------
-    # END of main loop to set continuation breakpoints
-    # Now go back and make any necessary corrections
-    #-------------------------------------------------------
+    #-----------------------------------------------
+    # End loop over the indexes in the _to_go arrays
+    #-----------------------------------------------
 
-    #-------------------------------------------------------
-    # ?/: rule 4 -- if we broke at a ':', then break at
-    # corresponding '?' unless this is a chain of ?: expressions
-    #-------------------------------------------------------
-    if (@i_colon_breaks) {
+    # Be sure we return an index in the range ($ibegin .. $imax).
+    # We will break at imax if no other break was found.
+    if ( $i_lowest < 0 ) { $i_lowest = $imax }
 
-        # using a simple method for deciding if we are in a ?/: chain --
-        # this is a chain if it has multiple ?/: pairs all in order;
-        # otherwise not.
-        # Note that if line starts in a ':' we count that above as a break
-        my $is_chain = ( $colons_in_order && @i_colon_breaks > 1 );
+    return ( $i_lowest, $lowest_strength, $leading_alignment_type, $Msg );
+} ## end sub break_lines_inner_loop
 
-        unless ($is_chain) {
-            my @insert_list = ();
-            foreach (@i_colon_breaks) {
-                my $i_question = $mate_index_to_go[$_];
-                if ( $i_question >= 0 ) {
-                    if ( $want_break_before{'?'} ) {
-                        $i_question = $iprev_to_go[$i_question];
-                    }
+sub do_colon_breaks {
+    my ( $self, $ri_colon_breaks, $ri_first, $ri_last ) = @_;
 
-                    if ( $i_question >= 0 ) {
-                        push @insert_list, $i_question;
-                    }
-                }
-                $self->insert_additional_breaks( \@insert_list, \@i_first,
-                    \@i_last );
+    # using a simple method for deciding if we are in a ?/: chain --
+    # this is a chain if it has multiple ?/: pairs all in order;
+    # otherwise not.
+    # Note that if line starts in a ':' we count that above as a break
+
+    my @insert_list = ();
+    foreach ( @{$ri_colon_breaks} ) {
+        my $i_question = $mate_index_to_go[$_];
+        if ( $i_question >= 0 ) {
+            if ( $want_break_before{'?'} ) {
+                $i_question = $iprev_to_go[$i_question];
+            }
+
+            if ( $i_question >= 0 ) {
+                push @insert_list, $i_question;
             }
         }
+        $self->insert_additional_breaks( \@insert_list, $ri_first, $ri_last );
     }
-    return ( \@i_first, \@i_last, $rbond_strength_to_go );
-} ## end sub break_long_lines
+    return;
+}
 
 ###########################################
 # CODE SECTION 11: Code to break long lists
@@ -18242,29 +19652,54 @@ sub break_long_lines {
     use constant DEBUG_BREAK_LISTS => 0;
 
     my (
-        $block_type,                $current_depth,
-        $depth,                     $i,
-        $i_last_nonblank_token,     $last_nonblank_token,
-        $last_nonblank_type,        $last_nonblank_block_type,
-        $last_old_breakpoint_count, $minimum_depth,
-        $next_nonblank_block_type,  $next_nonblank_token,
-        $next_nonblank_type,        $old_breakpoint_count,
-        $starting_breakpoint_count, $starting_depth,
-        $token,                     $type,
+
+        $block_type,
+        $current_depth,
+        $depth,
+        $i,
+        $i_last_colon,
+        $i_line_end,
+        $i_line_start,
+        $i_last_nonblank_token,
+        $last_nonblank_block_type,
+        $last_nonblank_token,
+        $last_nonblank_type,
+        $last_old_breakpoint_count,
+        $minimum_depth,
+        $next_nonblank_block_type,
+        $next_nonblank_token,
+        $next_nonblank_type,
+        $old_breakpoint_count,
+        $starting_breakpoint_count,
+        $starting_depth,
+        $token,
+        $type,
         $type_sequence,
+
     );
 
     my (
-        @breakpoint_stack,              @breakpoint_undo_stack,
-        @comma_index,                   @container_type,
-        @identifier_count_stack,        @index_before_arrow,
-        @interrupted_list,              @item_count_stack,
-        @last_comma_index,              @last_dot_index,
-        @last_nonblank_type,            @old_breakpoint_count_stack,
-        @opening_structure_index_stack, @rfor_semicolon_list,
-        @has_old_logical_breakpoints,   @rand_or_list,
-        @i_equals,                      @override_cab3,
+
+        @breakpoint_stack,
+        @breakpoint_undo_stack,
+        @comma_index,
+        @container_type,
+        @identifier_count_stack,
+        @index_before_arrow,
+        @interrupted_list,
+        @item_count_stack,
+        @last_comma_index,
+        @last_dot_index,
+        @last_nonblank_type,
+        @old_breakpoint_count_stack,
+        @opening_structure_index_stack,
+        @rfor_semicolon_list,
+        @has_old_logical_breakpoints,
+        @rand_or_list,
+        @i_equals,
+        @override_cab3,
         @type_sequence_stack,
+
     );
 
     # these arrays must retain values between calls
@@ -18272,7 +19707,6 @@ sub break_long_lines {
 
     my $length_tol;
     my $lp_tol_boost;
-    my $list_stress_level;
 
     sub initialize_break_lists {
         @dont_align         = ();
@@ -18339,7 +19773,8 @@ sub break_long_lines {
 
         # Define a level where list formatting becomes highly stressed and
         # needs to be simplified. Introduced for case b1262.
-        $list_stress_level = min( $stress_level_alpha, $stress_level_beta + 2 );
+        # $list_stress_level = min($stress_level_alpha, $stress_level_beta + 2);
+        # This is now '$high_stress_level'.
 
         return;
     } ## end sub initialize_break_lists
@@ -18400,16 +19835,17 @@ sub break_long_lines {
         my $bp_count           = 0;
         my $do_not_break_apart = 0;
 
-        # Do not break a list unless there are some non-line-ending commas.
-        # This avoids getting different results with only non-essential commas,
-        # and fixes b1192.
-        my $seqno = $type_sequence_stack[$dd];
-        my $real_comma_count =
-          $seqno ? $self->[_rtype_count_by_seqno_]->{$seqno}->{','} : 1;
-
         # anything to do?
         if ( $item_count_stack[$dd] ) {
 
+            # Do not break a list unless there are some non-line-ending commas.
+            # This avoids getting different results with only non-essential
+            # commas, and fixes b1192.
+            my $seqno = $type_sequence_stack[$dd];
+
+            my $real_comma_count =
+              $seqno ? $self->[_rtype_count_by_seqno_]->{$seqno}->{','} : 1;
+
             # handle commas not in containers...
             if ( $dont_align[$dd] ) {
                 $self->do_uncontained_comma_breaks( $dd, $rbond_strength_bias );
@@ -18424,7 +19860,7 @@ sub break_long_lines {
                 # look like a function call)
                 my $must_break_open = $last_nonblank_type[$dd] !~ /^[kwiU]$/;
 
-                $self->set_comma_breakpoints_do(
+                $self->set_comma_breakpoints_final(
                     {
                         depth            => $dd,
                         i_opening_paren  => $opening_structure_index_stack[$dd],
@@ -18609,6 +20045,7 @@ EOM
         %quick_filter = %is_assignment;
         @q            = qw# => . ; < > ~ #;
         push @q, ',';
+        push @q, 'f';    # added for ';' for issue c154
         @quick_filter{@q} = (1) x scalar(@q);
     }
 
@@ -18663,13 +20100,15 @@ EOM
 
         my ( $self, $is_long_line, $rbond_strength_bias ) = @_;
 
-        #----------------------------------------------------------------------
-        # This routine is called once per batch, if the batch is a list, to set
-        # line breaks so that hierarchical structure can be displayed and so
-        # that list items can be vertically aligned.  The output of this
+        #--------------------------------------------------------------------
+        # This routine is called once per batch, if the batch is a list, to
+        # set line breaks so that hierarchical structure can be displayed and
+        # so that list items can be vertically aligned.  The output of this
         # routine is stored in the array @forced_breakpoint_to_go, which is
-        # used by sub 'break_long_lines' to set final breakpoints.
-        #----------------------------------------------------------------------
+        # used by sub 'break_long_lines' to set final breakpoints.  This is
+        # probably the most complex routine in perltidy, so I have
+        # broken it into pieces and over-commented it.
+        #--------------------------------------------------------------------
 
         my $rLL                  = $self->[_rLL_];
         my $ris_list_by_seqno    = $self->[_ris_list_by_seqno_];
@@ -18682,6 +20121,9 @@ EOM
         $block_type                = SPACE;
         $current_depth             = $starting_depth;
         $i                         = -1;
+        $i_last_colon              = -1;
+        $i_line_end                = -1;
+        $i_line_start              = -1;
         $last_nonblank_token       = ';';
         $last_nonblank_type        = ';';
         $last_nonblank_block_type  = SPACE;
@@ -18699,14 +20141,12 @@ EOM
         my $comma_follows_last_closing_token;
 
         $self->check_for_new_minimum_depth( $current_depth,
-            $parent_seqno_to_go[0] );
+            $parent_seqno_to_go[0] )
+          if ( $current_depth < $minimum_depth );
 
         my $want_previous_breakpoint = -1;
 
         my $saw_good_breakpoint;
-        my $i_line_end   = -1;
-        my $i_line_start = -1;
-        my $i_last_colon = -1;
 
         #----------------------------------------
         # Main loop over all tokens in this batch
@@ -18717,18 +20157,21 @@ EOM
                 $last_nonblank_type       = $type;
                 $last_nonblank_token      = $token;
                 $last_nonblank_block_type = $block_type;
-            } ## end if ( $type ne 'b' )
+            }
             $type          = $types_to_go[$i];
             $block_type    = $block_type_to_go[$i];
             $token         = $tokens_to_go[$i];
             $type_sequence = $type_sequence_to_go[$i];
-            my $next_type       = $types_to_go[ $i + 1 ];
-            my $next_token      = $tokens_to_go[ $i + 1 ];
-            my $i_next_nonblank = ( ( $next_type eq 'b' ) ? $i + 2 : $i + 1 );
+
+            my $i_next_nonblank = $inext_to_go[$i];
             $next_nonblank_type       = $types_to_go[$i_next_nonblank];
             $next_nonblank_token      = $tokens_to_go[$i_next_nonblank];
             $next_nonblank_block_type = $block_type_to_go[$i_next_nonblank];
 
+            #-------------------------------------------
+            # Loop Section A: Look for special breakpoints...
+            #-------------------------------------------
+
             # set break if flag was set
             if ( $want_previous_breakpoint >= 0 ) {
                 $self->set_forced_breakpoint($want_previous_breakpoint);
@@ -18737,63 +20180,26 @@ EOM
 
             $last_old_breakpoint_count = $old_breakpoint_count;
 
-            # Fixed for case b1097 to not consider old breaks at highly
-            # stressed locations, such as types 'L' and 'R'.  It might be
-            # useful to generalize this concept in the future by looking at
-            # actual bond strengths.
-            if (   $old_breakpoint_to_go[$i]
-                && $type ne 'L'
-                && $next_nonblank_type ne 'R' )
-            {
-                $i_line_end   = $i;
-                $i_line_start = $i_next_nonblank;
-
-                $old_breakpoint_count++;
-
-                # Break before certain keywords if user broke there and
-                # this is a 'safe' break point. The idea is to retain
-                # any preferred breaks for sequential list operations,
-                # like a schwartzian transform.
-                if ($rOpts_break_at_old_keyword_breakpoints) {
-                    if (
-                           $next_nonblank_type eq 'k'
-                        && $is_keyword_returning_list{$next_nonblank_token}
-                        && (   $type =~ /^[=\)\]\}Riw]$/
-                            || $type eq 'k'
-                            && $is_keyword_returning_list{$token} )
-                      )
-                    {
-
-                        # we actually have to set this break next time through
-                        # the loop because if we are at a closing token (such
-                        # as '}') which forms a one-line block, this break might
-                        # get undone.
-
-                        # And do not do this at an equals if the user wants
-                        # breaks before an equals (blinker cases b434 b903)
-                        unless ( $type eq '=' && $want_break_before{$type} ) {
-                            $want_previous_breakpoint = $i;
-                        }
-                    } ## end if ( $next_nonblank_type...)
-                } ## end if ($rOpts_break_at_old_keyword_breakpoints)
+            # Check for a good old breakpoint ..
+            if (
+                $old_breakpoint_to_go[$i]
 
-                # Break before attributes if user broke there
-                if ($rOpts_break_at_old_attribute_breakpoints) {
-                    if ( $next_nonblank_type eq 'A' ) {
-                        $want_previous_breakpoint = $i;
-                    }
-                }
+                # Note: ignore old breaks at types 'L' and 'R' to fix case
+                # b1097. These breaks only occur under high stress.
+                && $type ne 'L'
+                && $next_nonblank_type ne 'R'
 
-                # remember an = break as possible good break point
-                if ( $is_assignment{$type} ) {
-                    $i_old_assignment_break = $i;
-                }
-                elsif ( $is_assignment{$next_nonblank_type} ) {
-                    $i_old_assignment_break = $i_next_nonblank;
-                }
-            } ## end if ( $old_breakpoint_to_go...)
+                # ... and ignore other high stress level breaks, fixes b1395
+                && $levels_to_go[$i] < $high_stress_level
+              )
+            {
+                ( $want_previous_breakpoint, $i_old_assignment_break ) =
+                  $self->check_old_breakpoints( $i_next_nonblank,
+                    $want_previous_breakpoint, $i_old_assignment_break );
+            }
 
             next if ( $type eq 'b' );
+
             $depth = $nesting_depth_to_go[ $i + 1 ];
 
             $total_depth_variation += abs( $depth - $depth_last );
               )
             {
                 $self->set_forced_breakpoint( $i - 1 );
-            } ## end if ( $type eq 'k' && $i...)
+            }
+
+            # remember locations of '||'  and '&&' for possible breaks if we
+            # decide this is a long logical expression.
+            if ( $type eq '||' ) {
+                push @{ $rand_or_list[$depth][2] }, $i;
+                ++$has_old_logical_breakpoints[$depth]
+                  if ( ( $i == $i_line_start || $i == $i_line_end )
+                    && $rOpts_break_at_old_logical_breakpoints );
+            }
+            elsif ( $type eq '&&' ) {
+                push @{ $rand_or_list[$depth][3] }, $i;
+                ++$has_old_logical_breakpoints[$depth]
+                  if ( ( $i == $i_line_start || $i == $i_line_end )
+                    && $rOpts_break_at_old_logical_breakpoints );
+            }
+            elsif ( $type eq 'f' ) {
+                push @{ $rfor_semicolon_list[$depth] }, $i;
+            }
+            elsif ( $type eq 'k' ) {
+                if ( $token eq 'and' ) {
+                    push @{ $rand_or_list[$depth][1] }, $i;
+                    ++$has_old_logical_breakpoints[$depth]
+                      if ( ( $i == $i_line_start || $i == $i_line_end )
+                        && $rOpts_break_at_old_logical_breakpoints );
+                }
+
+                # break immediately at 'or's which are probably not in a logical
+                # block -- but we will break in logical breaks below so that
+                # they do not add to the forced_breakpoint_count
+                elsif ( $token eq 'or' ) {
+                    push @{ $rand_or_list[$depth][0] }, $i;
+                    ++$has_old_logical_breakpoints[$depth]
+                      if ( ( $i == $i_line_start || $i == $i_line_end )
+                        && $rOpts_break_at_old_logical_breakpoints );
+                    if ( $is_logical_container{ $container_type[$depth] } ) {
+                    }
+                    else {
+                        if ($is_long_line) { $self->set_forced_breakpoint($i) }
+                        elsif ( ( $i == $i_line_start || $i == $i_line_end )
+                            && $rOpts_break_at_old_logical_breakpoints )
+                        {
+                            $saw_good_breakpoint = 1;
+                        }
+                    }
+                }
+                elsif ( $token eq 'if' || $token eq 'unless' ) {
+                    push @{ $rand_or_list[$depth][4] }, $i;
+                    if ( ( $i == $i_line_start || $i == $i_line_end )
+                        && $rOpts_break_at_old_logical_breakpoints )
+                    {
+                        $self->set_forced_breakpoint($i);
+                    }
+                }
+            }
+            elsif ( $is_assignment{$type} ) {
+                $i_equals[$depth] = $i;
+            }
+
+            #-----------------------------------------
+            # Loop Section B: Handle a sequenced token
+            #-----------------------------------------
+            if ($type_sequence) {
+                $self->break_lists_type_sequence;
+            }
+
+            #------------------------------------------
+            # Loop Section C: Handle Increasing Depth..
+            #------------------------------------------
+
+            # hardened against bad input syntax: depth jump must be 1 and type
+            # must be opening..fixes c102
+            if ( $depth == $current_depth + 1 && $is_opening_type{$type} ) {
+                $self->break_lists_increasing_depth();
+            }
+
+            #------------------------------------------
+            # Loop Section D: Handle Decreasing Depth..
+            #------------------------------------------
+
+            # hardened against bad input syntax: depth jump must be 1 and type
+            # must be closing .. fixes c102
+            elsif ( $depth == $current_depth - 1 && $is_closing_type{$type} ) {
+
+                $self->break_lists_decreasing_depth();
+
+                $comma_follows_last_closing_token =
+                  $next_nonblank_type eq ',' || $next_nonblank_type eq '=>';
+
+            }
+
+            #----------------------------------
+            # Loop Section E: Handle this token
+            #----------------------------------
+
+            $current_depth = $depth;
+
+            # most token types can skip the rest of this loop
+            next unless ( $quick_filter{$type} );
+
+            # handle comma-arrow
+            if ( $type eq '=>' ) {
+                next if ( $last_nonblank_type eq '=>' );
+                next if $rOpts_break_at_old_comma_breakpoints;
+                next
+                  if ( $rOpts_comma_arrow_breakpoints == 3
+                    && !$override_cab3[$depth] );
+                $want_comma_break[$depth]   = 1;
+                $index_before_arrow[$depth] = $i_last_nonblank_token;
+                next;
+            }
+
+            elsif ( $type eq '.' ) {
+                $last_dot_index[$depth] = $i;
+            }
+
+            # Turn off comma alignment if we are sure that this is not a list
+            # environment.  To be safe, we will do this if we see certain
+            # non-list tokens, such as ';', '=', and also the environment is
+            # not a list.
+            ##      $type =~ /^[\;\<\>\~f]$/ || $is_assignment{$type}
+            elsif ( $is_non_list_type{$type}
+                && !$self->is_in_list_by_i($i) )
+            {
+                $dont_align[$depth]         = 1;
+                $want_comma_break[$depth]   = 0;
+                $index_before_arrow[$depth] = -1;
+
+                # no special comma breaks in C-style 'for' terms (c154)
+                if ( $type eq 'f' ) { $last_comma_index[$depth] = undef }
+            }
+
+            # now just handle any commas
+            next if ( $type ne ',' );
+            $self->study_comma($comma_follows_last_closing_token);
+
+        } ## end while ( ++$i <= $max_index_to_go)
+
+        #-------------------------------------------
+        # END of loop over all tokens in this batch
+        # Now set breaks for any unfinished lists ..
+        #-------------------------------------------
+
+        foreach my $dd ( reverse( $minimum_depth .. $current_depth ) ) {
+
+            $interrupted_list[$dd]   = 1;
+            $has_broken_sublist[$dd] = 1 if ( $dd < $current_depth );
+            $self->set_comma_breakpoints( $dd, $rbond_strength_bias )
+              if ( $item_count_stack[$dd] );
+            $self->set_logical_breakpoints($dd)
+              if ( $has_old_logical_breakpoints[$dd] );
+            $self->set_for_semicolon_breakpoints($dd);
+
+            # break open container...
+            my $i_opening = $opening_structure_index_stack[$dd];
+            if ( defined($i_opening) && $i_opening >= 0 ) {
+                $self->set_forced_breakpoint($i_opening)
+                  unless (
+                    is_unbreakable_container($dd)
+
+                    # Avoid a break which would place an isolated ' or "
+                    # on a line
+                    || (   $type eq 'Q'
+                        && $i_opening >= $max_index_to_go - 2
+                        && ( $token eq "'" || $token eq '"' ) )
+                  );
+            }
+        } ## end for ( my $dd = $current_depth...)
+
+        #----------------------------------------
+        # Return the flag '$saw_good_breakpoint'.
+        #----------------------------------------
+        # This indicates if the input file had some good breakpoints.  This
+        # flag will be used to force a break in a line shorter than the
+        # allowed line length.
+        if ( $has_old_logical_breakpoints[$current_depth] ) {
+            $saw_good_breakpoint = 1;
+        }
+
+        # A complex line with one break at an = has a good breakpoint.
+        # This is not complex ($total_depth_variation=0):
+        # $res1
+        #   = 10;
+        #
+        # This is complex ($total_depth_variation=6):
+        # $res2 =
+        #  (is_boundp("a", 'self-insert') && is_boundp("b", 'self-insert'));
+
+        # The check ($i_old_.. < $max_index_to_go) was added to fix b1333
+        elsif ($i_old_assignment_break
+            && $total_depth_variation > 4
+            && $old_breakpoint_count == 1
+            && $i_old_assignment_break < $max_index_to_go )
+        {
+            $saw_good_breakpoint = 1;
+        }
+
+        return $saw_good_breakpoint;
+    } ## end sub break_lists
 
-            # remember locations of '||'  and '&&' for possible breaks if we
-            # decide this is a long logical expression.
-            if ( $type eq '||' ) {
-                push @{ $rand_or_list[$depth][2] }, $i;
-                ++$has_old_logical_breakpoints[$depth]
-                  if ( ( $i == $i_line_start || $i == $i_line_end )
-                    && $rOpts_break_at_old_logical_breakpoints );
-            } ## end elsif ( $type eq '||' )
-            elsif ( $type eq '&&' ) {
-                push @{ $rand_or_list[$depth][3] }, $i;
-                ++$has_old_logical_breakpoints[$depth]
-                  if ( ( $i == $i_line_start || $i == $i_line_end )
-                    && $rOpts_break_at_old_logical_breakpoints );
-            } ## end elsif ( $type eq '&&' )
-            elsif ( $type eq 'f' ) {
-                push @{ $rfor_semicolon_list[$depth] }, $i;
+    sub study_comma {
+
+        # study and store info for a list comma
+
+        my ( $self, $comma_follows_last_closing_token ) = @_;
+
+        $last_dot_index[$depth]   = undef;
+        $last_comma_index[$depth] = $i;
+
+        # break here if this comma follows a '=>'
+        # but not if there is a side comment after the comma
+        if ( $want_comma_break[$depth] ) {
+
+            if ( $next_nonblank_type =~ /^[\)\}\]R]$/ ) {
+                if ($rOpts_comma_arrow_breakpoints) {
+                    $want_comma_break[$depth] = 0;
+                    return;
+                }
             }
-            elsif ( $type eq 'k' ) {
-                if ( $token eq 'and' ) {
-                    push @{ $rand_or_list[$depth][1] }, $i;
-                    ++$has_old_logical_breakpoints[$depth]
-                      if ( ( $i == $i_line_start || $i == $i_line_end )
-                        && $rOpts_break_at_old_logical_breakpoints );
-                } ## end if ( $token eq 'and' )
 
-                # break immediately at 'or's which are probably not in a logical
-                # block -- but we will break in logical breaks below so that
-                # they do not add to the forced_breakpoint_count
-                elsif ( $token eq 'or' ) {
-                    push @{ $rand_or_list[$depth][0] }, $i;
-                    ++$has_old_logical_breakpoints[$depth]
-                      if ( ( $i == $i_line_start || $i == $i_line_end )
-                        && $rOpts_break_at_old_logical_breakpoints );
-                    if ( $is_logical_container{ $container_type[$depth] } ) {
-                    }
-                    else {
-                        if ($is_long_line) { $self->set_forced_breakpoint($i) }
-                        elsif ( ( $i == $i_line_start || $i == $i_line_end )
-                            && $rOpts_break_at_old_logical_breakpoints )
-                        {
-                            $saw_good_breakpoint = 1;
-                        }
-                    } ## end else [ if ( $is_logical_container...)]
-                } ## end elsif ( $token eq 'or' )
-                elsif ( $token eq 'if' || $token eq 'unless' ) {
-                    push @{ $rand_or_list[$depth][4] }, $i;
-                    if ( ( $i == $i_line_start || $i == $i_line_end )
-                        && $rOpts_break_at_old_logical_breakpoints )
+            $self->set_forced_breakpoint($i)
+              unless ( $next_nonblank_type eq '#' );
+
+            # break before the previous token if it looks safe
+            # Example of something that we will not try to break before:
+            #   DBI::SQL_SMALLINT() => $ado_consts->{adSmallInt},
+            # Also we don't want to break at a binary operator (like +):
+            # $c->createOval(
+            #    $x + $R, $y +
+            #    $R => $x - $R,
+            #    $y - $R, -fill   => 'black',
+            # );
+            my $ibreak = $index_before_arrow[$depth] - 1;
+            if (   $ibreak > 0
+                && $tokens_to_go[ $ibreak + 1 ] !~ /^[\)\}\]]$/ )
+            {
+                if ( $tokens_to_go[$ibreak] eq '-' ) { $ibreak-- }
+                if ( $types_to_go[$ibreak] eq 'b' )  { $ibreak-- }
+                if ( $types_to_go[$ibreak] =~ /^[,wiZCUG\(\{\[]$/ ) {
+
+                    # don't break before a comma, as in the following:
+                    # ( LONGER_THAN,=> 1,
+                    #    EIGHTY_CHARACTERS,=> 2,
+                    #    CAUSES_FORMATTING,=> 3,
+                    #    LIKE_THIS,=> 4,
+                    # );
+                    # This example is for -tso but should be general rule
+                    if (   $tokens_to_go[ $ibreak + 1 ] ne '->'
+                        && $tokens_to_go[ $ibreak + 1 ] ne ',' )
                     {
-                        $self->set_forced_breakpoint($i);
+                        $self->set_forced_breakpoint($ibreak);
                     }
-                } ## end elsif ( $token eq 'if' ||...)
-            } ## end elsif ( $type eq 'k' )
-            elsif ( $is_assignment{$type} ) {
-                $i_equals[$depth] = $i;
+                }
             }
 
-            if ($type_sequence) {
+            $want_comma_break[$depth]   = 0;
+            $index_before_arrow[$depth] = -1;
 
-                # handle any postponed closing breakpoints
-                if ( $is_closing_sequence_token{$token} ) {
-                    if ( $type eq ':' ) {
-                        $i_last_colon = $i;
+            # handle list which mixes '=>'s and ','s:
+            # treat any list items so far as an interrupted list
+            $interrupted_list[$depth] = 1;
+            return;
+        }
 
-                        # retain break at a ':' line break
-                        if (   ( $i == $i_line_start || $i == $i_line_end )
-                            && $rOpts_break_at_old_ternary_breakpoints
-                            && $levels_to_go[$i] < $list_stress_level )
-                        {
+        # Break after all commas above starting depth...
+        # But only if the last closing token was followed by a comma,
+        #   to avoid breaking a list operator (issue c119)
+        if (   $depth < $starting_depth
+            && $comma_follows_last_closing_token
+            && !$dont_align[$depth] )
+        {
+            $self->set_forced_breakpoint($i)
+              unless ( $next_nonblank_type eq '#' );
+            return;
+        }
 
-                            $self->set_forced_breakpoint($i);
+        # add this comma to the list..
+        my $item_count = $item_count_stack[$depth];
+        if ( $item_count == 0 ) {
 
-                            # Break at a previous '=', but only if it is before
-                            # the mating '?'. Mate_index test fixes b1287.
-                            my $ieq = $i_equals[$depth];
-                            if ( $ieq > 0 && $ieq < $mate_index_to_go[$i] ) {
-                                $self->set_forced_breakpoint(
-                                    $i_equals[$depth] );
-                                $i_equals[$depth] = -1;
-                            }
-                        } ## end if ( ( $i == $i_line_start...))
-                    } ## end if ( $type eq ':' )
-                    if ( has_postponed_breakpoint($type_sequence) ) {
-                        my $inc = ( $type eq ':' ) ? 0 : 1;
-                        if ( $i >= $inc ) {
-                            $self->set_forced_breakpoint( $i - $inc );
-                        }
-                    }
-                } ## end if ( $is_closing_sequence_token{$token} )
+            # but do not form a list with no opening structure
+            # for example:
 
-                # set breaks at ?/: if they will get separated (and are
-                # not a ?/: chain), or if the '?' is at the end of the
-                # line
-                elsif ( $token eq '?' ) {
-                    my $i_colon = $mate_index_to_go[$i];
-                    if (
-                        $i_colon <= 0  # the ':' is not in this batch
-                        || $i == 0     # this '?' is the first token of the line
-                        || $i ==
-                        $max_index_to_go    # or this '?' is the last token
-                      )
-                    {
+            #            open INFILE_COPY, ">$input_file_copy"
+            #              or die ("very long message");
+            if ( ( $opening_structure_index_stack[$depth] < 0 )
+                && $self->is_in_block_by_i($i) )
+            {
+                $dont_align[$depth] = 1;
+            }
+        }
 
-                        # don't break if # this has a side comment, and
-                        # don't break at a '?' if preceded by ':' on
-                        # this line of previous ?/: pair on this line.
-                        # This is an attempt to preserve a chain of ?/:
-                        # expressions (elsif2.t).
-                        if (
-                            (
-                                   $i_last_colon < 0
-                                || $parent_seqno_to_go[$i_last_colon] !=
-                                $parent_seqno_to_go[$i]
-                            )
-                            && $tokens_to_go[$max_index_to_go] ne '#'
-                          )
-                        {
-                            $self->set_forced_breakpoint($i);
-                        }
-                        $self->set_closing_breakpoint($i);
-                    } ## end if ( $i_colon <= 0  ||...)
-                } ## end elsif ( $token eq '?' )
-
-                elsif ( $is_opening_token{$token} ) {
-
-                    # do requested -lp breaks at the OPENING token for BROKEN
-                    # blocks.  NOTE: this can be done for both -lp and -xlp,
-                    # but only -xlp can really take advantage of this.  So this
-                    # is currently restricted to -xlp to avoid excess changes to
-                    # existing -lp formatting.
-                    if (   $rOpts_extended_line_up_parentheses
-                        && $mate_index_to_go[$i] < 0 )
-                    {
-                        my $lp_object =
-                          $self->[_rlp_object_by_seqno_]->{$type_sequence};
-                        if ($lp_object) {
-                            my $K_begin_line = $lp_object->get_K_begin_line();
-                            my $i_begin_line = $K_begin_line - $K_to_go[0];
-                            $self->set_forced_lp_break( $i_begin_line, $i );
-                        }
-                    }
-                }
+        $comma_index[$depth][$item_count] = $i;
+        ++$item_count_stack[$depth];
+        if ( $last_nonblank_type =~ /^[iR\]]$/ ) {
+            $identifier_count_stack[$depth]++;
+        }
+        return;
+    } ## end sub study_comma
 
-            } ## end if ($type_sequence)
+    sub check_old_breakpoints {
 
-#print "LISTX sees: i=$i type=$type  tok=$token  block=$block_type depth=$depth\n";
+        # Check for a good old breakpoint
 
-            #------------------------------------------------------------
-            # Handle Increasing Depth..
-            #
-            # prepare for a new list when depth increases
-            # token $i is a '(','{', or '['
-            #------------------------------------------------------------
-            # hardened against bad input syntax: depth jump must be 1 and type
-            # must be opening..fixes c102
-            if ( $depth == $current_depth + 1 && $is_opening_type{$type} ) {
+        my ( $self, $i_next_nonblank, $want_previous_breakpoint,
+            $i_old_assignment_break )
+          = @_;
 
-                #----------------------------------------------------------
-                # BEGIN initialize depth arrays
-                # ... use the same order as sub check_for_new_minimum_depth
-                #----------------------------------------------------------
-                $type_sequence_stack[$depth] = $type_sequence;
-                $override_cab3[$depth] =
-                     $rOpts_comma_arrow_breakpoints == 3
-                  && $type_sequence
-                  && $self->[_roverride_cab3_]->{$type_sequence};
-
-                $breakpoint_stack[$depth] = $forced_breakpoint_count;
-                $container_type[$depth] =
-
-                  #      k => && || ? : .
-                  $is_container_label_type{$last_nonblank_type}
-                  ? $last_nonblank_token
-                  : EMPTY_STRING;
-                $identifier_count_stack[$depth]        = 0;
-                $index_before_arrow[$depth]            = -1;
-                $interrupted_list[$depth]              = 0;
-                $item_count_stack[$depth]              = 0;
-                $last_nonblank_type[$depth]            = $last_nonblank_type;
-                $opening_structure_index_stack[$depth] = $i;
-
-                $breakpoint_undo_stack[$depth] = $forced_breakpoint_undo_count;
-                $comma_index[$depth]           = undef;
-                $last_comma_index[$depth]      = undef;
-                $last_dot_index[$depth]        = undef;
-                $old_breakpoint_count_stack[$depth]  = $old_breakpoint_count;
-                $has_old_logical_breakpoints[$depth] = 0;
-                $rand_or_list[$depth]                = [];
-                $rfor_semicolon_list[$depth]         = [];
-                $i_equals[$depth]                    = -1;
-
-                # if line ends here then signal closing token to break
-                if ( $next_nonblank_type eq 'b' || $next_nonblank_type eq '#' )
-                {
-                    $self->set_closing_breakpoint($i);
-                }
+        $i_line_end   = $i;
+        $i_line_start = $i_next_nonblank;
+
+        $old_breakpoint_count++;
+
+        # Break before certain keywords if user broke there and
+        # this is a 'safe' break point. The idea is to retain
+        # any preferred breaks for sequential list operations,
+        # like a schwartzian transform.
+        if ($rOpts_break_at_old_keyword_breakpoints) {
+            if (
+                   $next_nonblank_type eq 'k'
+                && $is_keyword_returning_list{$next_nonblank_token}
+                && (   $type =~ /^[=\)\]\}Riw]$/
+                    || $type eq 'k' && $is_keyword_returning_list{$token} )
+              )
+            {
 
-                # Not all lists of values should be vertically aligned..
-                $dont_align[$depth] =
+                # we actually have to set this break next time through
+                # the loop because if we are at a closing token (such
+                # as '}') which forms a one-line block, this break might
+                # get undone.
 
-                  # code BLOCKS are handled at a higher level
-                  ( $block_type ne EMPTY_STRING )
+                # But do not do this at an '=' if:
+                # - the user wants breaks before an equals (b434 b903)
+                # - or -naws is set (can be unstable, see b1354)
+                my $skip = $type eq '='
+                  && ( $want_break_before{$type}
+                    || !$rOpts_add_whitespace );
 
-                  # certain paren lists
-                  || ( $type eq '(' ) && (
+                $want_previous_breakpoint = $i
+                  unless ($skip);
 
-                    # it does not usually look good to align a list of
-                    # identifiers in a parameter list, as in:
-                    #    my($var1, $var2, ...)
-                    # (This test should probably be refined, for now I'm just
-                    # testing for any keyword)
-                    ( $last_nonblank_type eq 'k' )
+            }
+        }
 
-                    # a trailing '(' usually indicates a non-list
-                    || ( $next_nonblank_type eq '(' )
-                  );
-                $has_broken_sublist[$depth] = 0;
-                $want_comma_break[$depth]   = 0;
+        # Break before attributes if user broke there
+        if ($rOpts_break_at_old_attribute_breakpoints) {
+            if ( $next_nonblank_type eq 'A' ) {
+                $want_previous_breakpoint = $i;
+            }
+        }
 
-                #-------------------------------------
-                # END initialize depth arrays
-                #-------------------------------------
+        # remember an = break as possible good break point
+        if ( $is_assignment{$type} ) {
+            $i_old_assignment_break = $i;
+        }
+        elsif ( $is_assignment{$next_nonblank_type} ) {
+            $i_old_assignment_break = $i_next_nonblank;
+        }
+        return ( $want_previous_breakpoint, $i_old_assignment_break );
+    } ## end sub check_old_breakpoints
 
-                # patch to outdent opening brace of long if/for/..
-                # statements (like this one).  See similar coding in
-                # set_continuation breaks.  We have also catch it here for
-                # short line fragments which otherwise will not go through
-                # break_long_lines.
-                if (
-                    $block_type
+    sub break_lists_type_sequence {
 
-                    # if we have the ')' but not its '(' in this batch..
-                    && ( $last_nonblank_token eq ')' )
-                    && $mate_index_to_go[$i_last_nonblank_token] < 0
+        my ($self) = @_;
 
-                    # and user wants brace to left
-                    && !$rOpts_opening_brace_always_on_right
+        # handle any postponed closing breakpoints
+        if ( $is_closing_sequence_token{$token} ) {
+            if ( $type eq ':' ) {
+                $i_last_colon = $i;
 
-                    && ( $type eq '{' )     # should be true
-                    && ( $token eq '{' )    # should be true
-                  )
+                # retain break at a ':' line break
+                if (   ( $i == $i_line_start || $i == $i_line_end )
+                    && $rOpts_break_at_old_ternary_breakpoints
+                    && $levels_to_go[$i] < $high_stress_level )
                 {
-                    $self->set_forced_breakpoint( $i - 1 );
-                } ## end if ( $block_type && ( ...))
-            } ## end if ( $depth > $current_depth)
 
-            #------------------------------------------------------------
-            # Handle Decreasing Depth..
-            #
-            # finish off any old list when depth decreases
-            # token $i is a ')','}', or ']'
-            #------------------------------------------------------------
-            # hardened against bad input syntax: depth jump must be 1 and type
-            # must be closing .. fixes c102
-            elsif ( $depth == $current_depth - 1 && $is_closing_type{$type} ) {
+                    $self->set_forced_breakpoint($i);
 
-                $self->check_for_new_minimum_depth( $depth,
-                    $parent_seqno_to_go[$i] );
+                    # Break at a previous '=', but only if it is before
+                    # the mating '?'. Mate_index test fixes b1287.
+                    my $ieq = $i_equals[$depth];
+                    if ( $ieq > 0 && $ieq < $mate_index_to_go[$i] ) {
+                        $self->set_forced_breakpoint( $i_equals[$depth] );
+                        $i_equals[$depth] = -1;
+                    }
+                }
+            }
+            if ( has_postponed_breakpoint($type_sequence) ) {
+                my $inc = ( $type eq ':' ) ? 0 : 1;
+                if ( $i >= $inc ) {
+                    $self->set_forced_breakpoint( $i - $inc );
+                }
+            }
+        }
 
-                $comma_follows_last_closing_token =
-                  $next_nonblank_type eq ',' || $next_nonblank_type eq '=>';
+        # set breaks at ?/: if they will get separated (and are
+        # not a ?/: chain), or if the '?' is at the end of the
+        # line
+        elsif ( $token eq '?' ) {
+            my $i_colon = $mate_index_to_go[$i];
+            if (
+                $i_colon <= 0    # the ':' is not in this batch
+                || $i == 0       # this '?' is the first token of the line
+                || $i == $max_index_to_go    # or this '?' is the last token
+              )
+            {
 
-                # force all outer logical containers to break after we see on
-                # old breakpoint
-                $has_old_logical_breakpoints[$depth] ||=
-                  $has_old_logical_breakpoints[$current_depth];
-
-                # Patch to break between ') {' if the paren list is broken.
-                # There is similar logic in break_long_lines for
-                # non-broken lists.
-                if (   $token eq ')'
-                    && $next_nonblank_block_type
-                    && $interrupted_list[$current_depth]
-                    && $next_nonblank_type eq '{'
-                    && !$rOpts_opening_brace_always_on_right )
+                # don't break if # this has a side comment, and
+                # don't break at a '?' if preceded by ':' on
+                # this line of previous ?/: pair on this line.
+                # This is an attempt to preserve a chain of ?/:
+                # expressions (elsif2.t).
+                if (
+                    (
+                           $i_last_colon < 0
+                        || $parent_seqno_to_go[$i_last_colon] !=
+                        $parent_seqno_to_go[$i]
+                    )
+                    && $tokens_to_go[$max_index_to_go] ne '#'
+                  )
                 {
                     $self->set_forced_breakpoint($i);
-                } ## end if ( $token eq ')' && ...
+                }
+                $self->set_closing_breakpoint($i);
+            }
+        }
 
-#print "LISTY sees: i=$i type=$type  tok=$token  block=$block_type depth=$depth next=$next_nonblank_type next_block=$next_nonblank_block_type inter=$interrupted_list[$current_depth]\n";
+        elsif ( $is_opening_token{$token} ) {
 
-                # set breaks at commas if necessary
-                my ( $bp_count, $do_not_break_apart ) =
-                  $self->set_comma_breakpoints( $current_depth,
-                    $rbond_strength_bias );
+            # do requested -lp breaks at the OPENING token for BROKEN
+            # blocks.  NOTE: this can be done for both -lp and -xlp,
+            # but only -xlp can really take advantage of this.  So this
+            # is currently restricted to -xlp to avoid excess changes to
+            # existing -lp formatting.
+            if (   $rOpts_extended_line_up_parentheses
+                && $mate_index_to_go[$i] < 0 )
+            {
+                my $lp_object =
+                  $self->[_rlp_object_by_seqno_]->{$type_sequence};
+                if ($lp_object) {
+                    my $K_begin_line = $lp_object->get_K_begin_line();
+                    my $i_begin_line = $K_begin_line - $K_to_go[0];
+                    $self->set_forced_lp_break( $i_begin_line, $i );
+                }
+            }
+        }
+        return;
+    } ## end sub break_lists_type_sequence
 
-                my $i_opening = $opening_structure_index_stack[$current_depth];
-                my $saw_opening_structure = ( $i_opening >= 0 );
-                my $lp_object;
-                if ( $rOpts_line_up_parentheses && $saw_opening_structure ) {
-                    $lp_object = $self->[_rlp_object_by_seqno_]
-                      ->{ $type_sequence_to_go[$i_opening] };
-                }
-
-                # this term is long if we had to break at interior commas..
-                my $is_long_term = $bp_count > 0;
-
-                # If this is a short container with one or more comma arrows,
-                # then we will mark it as a long term to open it if requested.
-                # $rOpts_comma_arrow_breakpoints =
-                #    0 - open only if comma precedes closing brace
-                #    1 - stable: except for one line blocks
-                #    2 - try to form 1 line blocks
-                #    3 - ignore =>
-                #    4 - always open up if vt=0
-                #    5 - stable: even for one line blocks if vt=0
-
-                # PATCH: Modify the -cab flag if we are not processing a list:
-                # We only want the -cab flag to apply to list containers, so
-                # for non-lists we use the default and stable -cab=5 value.
-                # Fixes case b939a.
-                my $cab_flag = $rOpts_comma_arrow_breakpoints;
-                if ( $type_sequence && !$ris_list_by_seqno->{$type_sequence} ) {
-                    $cab_flag = 5;
-                }
-
-                # Ignore old breakpoints when under stress.
-                # Fixes b1203 b1204 as well as b1197-b1200.
-                # But not if -lp: fixes b1264, b1265.  NOTE: rechecked with
-                # b1264 to see if this check is still required at all, and
-                # these still require a check, but at higher level beta+3
-                # instead of beta:  b1193 b780
-                if (   $saw_opening_structure
-                    && !$lp_object
-                    && $levels_to_go[$i_opening] >= $list_stress_level )
-                {
-                    $cab_flag = 2;
+    sub break_lists_increasing_depth {
 
-                    # Do not break hash braces under stress (fixes b1238)
-                    $do_not_break_apart ||= $types_to_go[$i_opening] eq 'L';
+        my ($self) = @_;
 
-                    # This option fixes b1235, b1237, b1240 with old and new
-                    # -lp, but formatting is nicer with next option.
-                    ## $is_long_term ||=
-                    ##  $levels_to_go[$i_opening] > $stress_level_beta + 1;
+        #--------------------------------------------
+        # prepare for a new list when depth increases
+        # token $i is a '(','{', or '['
+        #--------------------------------------------
 
-                    # This option fixes b1240 but not b1235, b1237 with new -lp,
-                    # but this gives better formatting than the previous option.
-                    $do_not_break_apart ||=
-                      $levels_to_go[$i_opening] > $stress_level_beta;
-                }
+        #----------------------------------------------------------
+        # BEGIN initialize depth arrays
+        # ... use the same order as sub check_for_new_minimum_depth
+        #----------------------------------------------------------
+        $type_sequence_stack[$depth] = $type_sequence;
+        $override_cab3[$depth] =
+             $rOpts_comma_arrow_breakpoints == 3
+          && $type_sequence
+          && $self->[_roverride_cab3_]->{$type_sequence};
+
+        $breakpoint_stack[$depth] = $forced_breakpoint_count;
+        $container_type[$depth] =
+
+          #      k => && || ? : .
+          $is_container_label_type{$last_nonblank_type}
+          ? $last_nonblank_token
+          : EMPTY_STRING;
+        $identifier_count_stack[$depth]        = 0;
+        $index_before_arrow[$depth]            = -1;
+        $interrupted_list[$depth]              = 0;
+        $item_count_stack[$depth]              = 0;
+        $last_nonblank_type[$depth]            = $last_nonblank_type;
+        $opening_structure_index_stack[$depth] = $i;
+
+        $breakpoint_undo_stack[$depth]       = $forced_breakpoint_undo_count;
+        $comma_index[$depth]                 = undef;
+        $last_comma_index[$depth]            = undef;
+        $last_dot_index[$depth]              = undef;
+        $old_breakpoint_count_stack[$depth]  = $old_breakpoint_count;
+        $has_old_logical_breakpoints[$depth] = 0;
+        $rand_or_list[$depth]                = [];
+        $rfor_semicolon_list[$depth]         = [];
+        $i_equals[$depth]                    = -1;
+
+        # if line ends here then signal closing token to break
+        if ( $next_nonblank_type eq 'b' || $next_nonblank_type eq '#' ) {
+            $self->set_closing_breakpoint($i);
+        }
+
+        # Not all lists of values should be vertically aligned..
+        $dont_align[$depth] =
+
+          # code BLOCKS are handled at a higher level
+          ( $block_type ne EMPTY_STRING )
+
+          # certain paren lists
+          || ( $type eq '(' ) && (
+
+            # it does not usually look good to align a list of
+            # identifiers in a parameter list, as in:
+            #    my($var1, $var2, ...)
+            # (This test should probably be refined, for now I'm just
+            # testing for any keyword)
+            ( $last_nonblank_type eq 'k' )
+
+            # a trailing '(' usually indicates a non-list
+            || ( $next_nonblank_type eq '(' )
+          );
+        $has_broken_sublist[$depth] = 0;
+        $want_comma_break[$depth]   = 0;
 
-                if (  !$is_long_term
-                    && $saw_opening_structure
-                    && $is_opening_token{ $tokens_to_go[$i_opening] }
-                    && $index_before_arrow[ $depth + 1 ] > 0
-                    && !$opening_vertical_tightness{ $tokens_to_go[$i_opening] }
-                  )
-                {
-                    $is_long_term =
-                         $cab_flag == 4
-                      || $cab_flag == 0 && $last_nonblank_token eq ','
-                      || $cab_flag == 5 && $old_breakpoint_to_go[$i_opening];
-                } ## end if ( !$is_long_term &&...)
+        #----------------------------
+        # END initialize depth arrays
+        #----------------------------
 
-                # mark term as long if the length between opening and closing
-                # parens exceeds allowed line length
-                if ( !$is_long_term && $saw_opening_structure ) {
+        # patch to outdent opening brace of long if/for/..
+        # statements (like this one).  See similar coding in
+        # set_continuation breaks.  We have also catch it here for
+        # short line fragments which otherwise will not go through
+        # break_long_lines.
+        if (
+            $block_type
 
-                    my $i_opening_minus =
-                      $self->find_token_starting_list($i_opening);
+            # if we have the ')' but not its '(' in this batch..
+            && ( $last_nonblank_token eq ')' )
+            && $mate_index_to_go[$i_last_nonblank_token] < 0
 
-                    my $excess =
-                      $self->excess_line_length( $i_opening_minus, $i );
-
-                    # Use standard spaces for indentation of lists in -lp mode
-                    # if it gives a longer line length. This helps to avoid an
-                    # instability due to forming and breaking one-line blocks.
-                    # This fixes case b1314.
-                    my $indentation = $leading_spaces_to_go[$i_opening_minus];
-                    if ( ref($indentation)
-                        && $ris_broken_container->{$type_sequence} )
-                    {
-                        my $lp_spaces  = $indentation->get_spaces();
-                        my $std_spaces = $indentation->get_standard_spaces();
-                        my $diff       = $std_spaces - $lp_spaces;
-                        if ( $diff > 0 ) { $excess += $diff }
-                    }
+            # and user wants brace to left
+            && !$rOpts_opening_brace_always_on_right
 
-                    my $tol = $length_tol;
+            && ( $type eq '{' )     # should be true
+            && ( $token eq '{' )    # should be true
+          )
+        {
+            $self->set_forced_breakpoint( $i - 1 );
+        }
 
-                    # boost tol for an -lp container
-                    if (
-                           $lp_tol_boost
-                        && $lp_object
-                        && ( $rOpts_extended_continuation_indentation
-                            || !$ris_list_by_seqno->{$type_sequence} )
-                      )
-                    {
-                        $tol += $lp_tol_boost;
-                    }
+        return;
+    } ## end sub break_lists_increasing_depth
+
+    sub break_lists_decreasing_depth {
+
+        my ( $self, $rbond_strength_bias ) = @_;
+
+        # We have arrived at a closing container token in sub break_lists:
+        # the token at index $i is one of these: ')','}', ']'
+        # A number of important breakpoints for this container can now be set
+        # based on the information that we have collected. This includes:
+        # - breaks at commas to format tables
+        # - breaks at certain logical operators and other good breakpoints
+        # - breaks at opening and closing containers if needed by selected
+        #   formatting styles
+        # These breaks are made by calling sub 'set_forced_breakpoint'
+
+        $self->check_for_new_minimum_depth( $depth, $parent_seqno_to_go[$i] )
+          if ( $depth < $minimum_depth );
+
+        # force all outer logical containers to break after we see on
+        # old breakpoint
+        $has_old_logical_breakpoints[$depth] ||=
+          $has_old_logical_breakpoints[$current_depth];
+
+        # Patch to break between ') {' if the paren list is broken.
+        # There is similar logic in break_long_lines for
+        # non-broken lists.
+        if (   $token eq ')'
+            && $next_nonblank_block_type
+            && $interrupted_list[$current_depth]
+            && $next_nonblank_type eq '{'
+            && !$rOpts_opening_brace_always_on_right )
+        {
+            $self->set_forced_breakpoint($i);
+        }
 
-                    # Patch to avoid blinking with -bbxi=2 and -cab=2
-                    # in which variations in -ci cause unstable formatting
-                    # in edge cases. We just always add one ci level so that
-                    # the formatting is independent of the -BBX results.
-                    # Fixes cases b1137 b1149 b1150 b1155 b1158 b1159 b1160
-                    # b1161 b1166 b1167 b1168
-                    if (  !$ci_levels_to_go[$i_opening]
-                        && $rbreak_before_container_by_seqno->{$type_sequence} )
-                    {
-                        $tol += $rOpts->{'continuation-indentation'};
-                    }
+#print "LISTY sees: i=$i type=$type  tok=$token  block=$block_type depth=$depth next=$next_nonblank_type next_block=$next_nonblank_block_type inter=$interrupted_list[$current_depth]\n";
 
-                    $is_long_term = $excess + $tol > 0;
+        #-----------------------------------------------------------------
+        # Set breaks at commas to display a table of values if appropriate
+        #-----------------------------------------------------------------
+        my ( $bp_count, $do_not_break_apart ) = ( 0, 0 );
+        ( $bp_count, $do_not_break_apart ) =
+          $self->set_comma_breakpoints( $current_depth, $rbond_strength_bias )
+          if ( $item_count_stack[$current_depth] );
+
+        #-----------------------------------------------------------
+        # Now set flags needed to decide if we should break open the
+        # container ... This is a long rambling section which has
+        # grown over time to handle all situations.
+        #-----------------------------------------------------------
+        my $i_opening = $opening_structure_index_stack[$current_depth];
+        my $saw_opening_structure = ( $i_opening >= 0 );
+        my $lp_object;
+        if ( $rOpts_line_up_parentheses && $saw_opening_structure ) {
+            $lp_object = $self->[_rlp_object_by_seqno_]
+              ->{ $type_sequence_to_go[$i_opening] };
+        }
+
+        # this term is long if we had to break at interior commas..
+        my $is_long_term = $bp_count > 0;
+
+        # If this is a short container with one or more comma arrows,
+        # then we will mark it as a long term to open it if requested.
+        # $rOpts_comma_arrow_breakpoints =
+        #    0 - open only if comma precedes closing brace
+        #    1 - stable: except for one line blocks
+        #    2 - try to form 1 line blocks
+        #    3 - ignore =>
+        #    4 - always open up if vt=0
+        #    5 - stable: even for one line blocks if vt=0
+
+        # PATCH: Modify the -cab flag if we are not processing a list:
+        # We only want the -cab flag to apply to list containers, so
+        # for non-lists we use the default and stable -cab=5 value.
+        # Fixes case b939a.
+        my $cab_flag = $rOpts_comma_arrow_breakpoints;
+        if ( $type_sequence && !$self->[_ris_list_by_seqno_]->{$type_sequence} )
+        {
+            $cab_flag = 5;
+        }
+
+        # Ignore old breakpoints when under stress.
+        # Fixes b1203 b1204 as well as b1197-b1200.
+        # But not if -lp: fixes b1264, b1265.  NOTE: rechecked with
+        # b1264 to see if this check is still required at all, and
+        # these still require a check, but at higher level beta+3
+        # instead of beta:  b1193 b780
+        if (   $saw_opening_structure
+            && !$lp_object
+            && $levels_to_go[$i_opening] >= $high_stress_level )
+        {
+            $cab_flag = 2;
 
-                } ## end if ( !$is_long_term &&...)
+            # Do not break hash braces under stress (fixes b1238)
+            $do_not_break_apart ||= $types_to_go[$i_opening] eq 'L';
 
-                # We've set breaks after all comma-arrows.  Now we have to
-                # undo them if this can be a one-line block
-                # (the only breakpoints set will be due to comma-arrows)
+            # This option fixes b1235, b1237, b1240 with old and new
+            # -lp, but formatting is nicer with next option.
+            ## $is_long_term ||=
+            ##  $levels_to_go[$i_opening] > $stress_level_beta + 1;
 
-                if (
+            # This option fixes b1240 but not b1235, b1237 with new -lp,
+            # but this gives better formatting than the previous option.
+            # TODO: see if stress_level_alha should also be considered
+            $do_not_break_apart ||=
+              $levels_to_go[$i_opening] > $stress_level_beta;
+        }
 
-                    # user doesn't require breaking after all comma-arrows
-                    ( $cab_flag != 0 ) && ( $cab_flag != 4 )
+        if (  !$is_long_term
+            && $saw_opening_structure
+            && $is_opening_token{ $tokens_to_go[$i_opening] }
+            && $index_before_arrow[ $depth + 1 ] > 0
+            && !$opening_vertical_tightness{ $tokens_to_go[$i_opening] } )
+        {
+            $is_long_term =
+                 $cab_flag == 4
+              || $cab_flag == 0 && $last_nonblank_token eq ','
+              || $cab_flag == 5 && $old_breakpoint_to_go[$i_opening];
+        }
 
-                    # and if the opening structure is in this batch
-                    && $saw_opening_structure
+        # mark term as long if the length between opening and closing
+        # parens exceeds allowed line length
+        if ( !$is_long_term && $saw_opening_structure ) {
 
-                    # and either on the same old line
-                    && (
-                        $old_breakpoint_count_stack[$current_depth] ==
-                        $last_old_breakpoint_count
+            my $i_opening_minus = $self->find_token_starting_list($i_opening);
 
-                        # or user wants to form long blocks with arrows
-                        || $cab_flag == 2
+            my $excess = $self->excess_line_length( $i_opening_minus, $i );
 
-                        # if -cab=3 is overridden then use -cab=2 behavior
-                        || $cab_flag == 3 && $override_cab3[$current_depth]
-                    )
+            # Use standard spaces for indentation of lists in -lp mode
+            # if it gives a longer line length. This helps to avoid an
+            # instability due to forming and breaking one-line blocks.
+            # This fixes case b1314.
+            my $indentation = $leading_spaces_to_go[$i_opening_minus];
+            if ( ref($indentation)
+                && $self->[_ris_broken_container_]->{$type_sequence} )
+            {
+                my $lp_spaces  = $indentation->get_spaces();
+                my $std_spaces = $indentation->get_standard_spaces();
+                my $diff       = $std_spaces - $lp_spaces;
+                if ( $diff > 0 ) { $excess += $diff }
+            }
 
-                    # and we made breakpoints between the opening and closing
-                    && ( $breakpoint_undo_stack[$current_depth] <
-                        $forced_breakpoint_undo_count )
+            my $tol = $length_tol;
 
-                    # and this block is short enough to fit on one line
-                    # Note: use < because need 1 more space for possible comma
-                    && !$is_long_term
+            # boost tol for an -lp container
+            if (
+                   $lp_tol_boost
+                && $lp_object
+                && ( $rOpts_extended_continuation_indentation
+                    || !$self->[_ris_list_by_seqno_]->{$type_sequence} )
+              )
+            {
+                $tol += $lp_tol_boost;
+            }
 
-                  )
-                {
-                    $self->undo_forced_breakpoint_stack(
-                        $breakpoint_undo_stack[$current_depth] );
-                } ## end if ( ( $rOpts_comma_arrow_breakpoints...))
-
-                # now see if we have any comma breakpoints left
-                my $has_comma_breakpoints =
-                  ( $breakpoint_stack[$current_depth] !=
-                      $forced_breakpoint_count );
-
-                # update broken-sublist flag of the outer container
-                $has_broken_sublist[$depth] =
-                     $has_broken_sublist[$depth]
-                  || $has_broken_sublist[$current_depth]
-                  || $is_long_term
-                  || $has_comma_breakpoints;
-
-# Having come to the closing ')', '}', or ']', now we have to decide if we
-# should 'open up' the structure by placing breaks at the opening and
-# closing containers.  This is a tricky decision.  Here are some of the
-# basic considerations:
-#
-# -If this is a BLOCK container, then any breakpoints will have already
-# been set (and according to user preferences), so we need do nothing here.
-#
-# -If we have a comma-separated list for which we can align the list items,
-# then we need to do so because otherwise the vertical aligner cannot
-# currently do the alignment.
-#
-# -If this container does itself contain a container which has been broken
-# open, then it should be broken open to properly show the structure.
-#
-# -If there is nothing to align, and no other reason to break apart,
-# then do not do it.
-#
-# We will not break open the parens of a long but 'simple' logical expression.
-# For example:
-#
-# This is an example of a simple logical expression and its formatting:
-#
-#     if ( $bigwasteofspace1 && $bigwasteofspace2
-#         || $bigwasteofspace3 && $bigwasteofspace4 )
-#
-# Most people would prefer this than the 'spacey' version:
-#
-#     if (
-#         $bigwasteofspace1 && $bigwasteofspace2
-#         || $bigwasteofspace3 && $bigwasteofspace4
-#     )
-#
-# To illustrate the rules for breaking logical expressions, consider:
-#
-#             FULLY DENSE:
-#             if ( $opt_excl
-#                 and ( exists $ids_excl_uc{$id_uc}
-#                     or grep $id_uc =~ /$_/, @ids_excl_uc ))
-#
-# This is on the verge of being difficult to read.  The current default is to
-# open it up like this:
-#
-#             DEFAULT:
-#             if (
-#                 $opt_excl
-#                 and ( exists $ids_excl_uc{$id_uc}
-#                     or grep $id_uc =~ /$_/, @ids_excl_uc )
-#               )
-#
-# This is a compromise which tries to avoid being too dense and to spacey.
-# A more spaced version would be:
-#
-#             SPACEY:
-#             if (
-#                 $opt_excl
-#                 and (
-#                     exists $ids_excl_uc{$id_uc}
-#                     or grep $id_uc =~ /$_/, @ids_excl_uc
-#                 )
-#               )
-#
-# Some people might prefer the spacey version -- an option could be added.  The
-# innermost expression contains a long block '( exists $ids_...  ')'.
-#
-# Here is how the logic goes: We will force a break at the 'or' that the
-# innermost expression contains, but we will not break apart its opening and
-# closing containers because (1) it contains no multi-line sub-containers itself,
-# and (2) there is no alignment to be gained by breaking it open like this
-#
-#             and (
-#                 exists $ids_excl_uc{$id_uc}
-#                 or grep $id_uc =~ /$_/, @ids_excl_uc
-#             )
-#
-# (although this looks perfectly ok and might be good for long expressions).  The
-# outer 'if' container, though, contains a broken sub-container, so it will be
-# broken open to avoid too much density.  Also, since it contains no 'or's, there
-# will be a forced break at its 'and'.
-
-                # Open-up if parens if requested. We do this by pretending we
-                # did not see the opening structure, since in that case parens
-                # always get opened up.
-                if (   $saw_opening_structure
-                    && $rOpts_break_open_compact_parens )
-                {
+            # Patch to avoid blinking with -bbxi=2 and -cab=2
+            # in which variations in -ci cause unstable formatting
+            # in edge cases. We just always add one ci level so that
+            # the formatting is independent of the -BBX results.
+            # Fixes cases b1137 b1149 b1150 b1155 b1158 b1159 b1160
+            # b1161 b1166 b1167 b1168
+            if (  !$ci_levels_to_go[$i_opening]
+                && $self->[_rbreak_before_container_by_seqno_]->{$type_sequence}
+              )
+            {
+                $tol += $rOpts_continuation_indentation;
+            }
 
-                    # This parameter is a one-character flag, as follows:
-                    #  '0' matches no parens  -> break open NOT OK
-                    #  '1' matches all parens -> break open OK
-                    #  Other values are same as used by the weld-exclusion-list
-                    my $flag = $rOpts_break_open_compact_parens;
-                    if (   $flag eq '*'
-                        || $flag eq '1' )
-                    {
-                        $saw_opening_structure = 0;
-                    }
-                    else {
-                        my $KK = $K_to_go[$i_opening];
-                        $saw_opening_structure =
-                          !$self->match_paren_flag( $KK, $flag );
-                    }
-                }
+            $is_long_term = $excess + $tol > 0;
 
-                # set some flags telling something about this container..
-                my $is_simple_logical_expression = 0;
-                if (   $item_count_stack[$current_depth] == 0
-                    && $saw_opening_structure
-                    && $tokens_to_go[$i_opening] eq '('
-                    && $is_logical_container{ $container_type[$current_depth] }
-                  )
-                {
+        }
 
-                    # This seems to be a simple logical expression with
-                    # no existing breakpoints.  Set a flag to prevent
-                    # opening it up.
-                    if ( !$has_comma_breakpoints ) {
-                        $is_simple_logical_expression = 1;
-                    }
+        # We've set breaks after all comma-arrows.  Now we have to
+        # undo them if this can be a one-line block
+        # (the only breakpoints set will be due to comma-arrows)
 
-                    # This seems to be a simple logical expression with
-                    # breakpoints (broken sublists, for example).  Break
-                    # at all 'or's and '||'s.
-                    else {
-                        $self->set_logical_breakpoints($current_depth);
-                    }
-                } ## end if ( $item_count_stack...)
+        if (
 
-                if ( $is_long_term
-                    && @{ $rfor_semicolon_list[$current_depth] } )
-                {
-                    $self->set_for_semicolon_breakpoints($current_depth);
+            # user doesn't require breaking after all comma-arrows
+            ( $cab_flag != 0 ) && ( $cab_flag != 4 )
 
-                    # open up a long 'for' or 'foreach' container to allow
-                    # leading term alignment unless -lp is used.
-                    $has_comma_breakpoints = 1 unless ($lp_object);
-                } ## end if ( $is_long_term && ...)
+            # and if the opening structure is in this batch
+            && $saw_opening_structure
 
-                if (
+            # and either on the same old line
+            && (
+                $old_breakpoint_count_stack[$current_depth] ==
+                $last_old_breakpoint_count
 
-                    # breaks for code BLOCKS are handled at a higher level
-                    !$block_type
+                # or user wants to form long blocks with arrows
+                || $cab_flag == 2
 
-                    # we do not need to break at the top level of an 'if'
-                    # type expression
-                    && !$is_simple_logical_expression
+                # if -cab=3 is overridden then use -cab=2 behavior
+                || $cab_flag == 3 && $override_cab3[$current_depth]
+            )
 
-                    ## modification to keep ': (' containers vertically tight;
-                    ## but probably better to let user set -vt=1 to avoid
-                    ## inconsistency with other paren types
-                    ## && ($container_type[$current_depth] ne ':')
+            # and we made breakpoints between the opening and closing
+            && ( $breakpoint_undo_stack[$current_depth] <
+                $forced_breakpoint_undo_count )
 
-                    # otherwise, we require one of these reasons for breaking:
-                    && (
+            # and this block is short enough to fit on one line
+            # Note: use < because need 1 more space for possible comma
+            && !$is_long_term
 
-                        # - this term has forced line breaks
-                        $has_comma_breakpoints
+          )
+        {
+            $self->undo_forced_breakpoint_stack(
+                $breakpoint_undo_stack[$current_depth] );
+        }
 
-                       # - the opening container is separated from this batch
-                       #   for some reason (comment, blank line, code block)
-                       # - this is a non-paren container spanning multiple lines
-                        || !$saw_opening_structure
+        # now see if we have any comma breakpoints left
+        my $has_comma_breakpoints =
+          ( $breakpoint_stack[$current_depth] != $forced_breakpoint_count );
 
-                        # - this is a long block contained in another breakable
-                        #   container
-                        || $is_long_term && !$self->is_in_block_by_i($i_opening)
-                    )
-                  )
-                {
+        # update broken-sublist flag of the outer container
+        $has_broken_sublist[$depth] =
+             $has_broken_sublist[$depth]
+          || $has_broken_sublist[$current_depth]
+          || $is_long_term
+          || $has_comma_breakpoints;
 
-                    # do special -lp breaks at the CLOSING token for INTACT
-                    # blocks (because we might not do them if the block does
-                    # not break open)
-                    if ($lp_object) {
-                        my $K_begin_line = $lp_object->get_K_begin_line();
-                        my $i_begin_line = $K_begin_line - $K_to_go[0];
-                        $self->set_forced_lp_break( $i_begin_line, $i_opening );
-                    }
+        # Having come to the closing ')', '}', or ']', now we have to decide
+        # if we should 'open up' the structure by placing breaks at the
+        # opening and closing containers.  This is a tricky decision.  Here
+        # are some of the basic considerations:
+        #
+        # -If this is a BLOCK container, then any breakpoints will have
+        # already been set (and according to user preferences), so we need do
+        # nothing here.
+        #
+        # -If we have a comma-separated list for which we can align the list
+        # items, then we need to do so because otherwise the vertical aligner
+        # cannot currently do the alignment.
+        #
+        # -If this container does itself contain a container which has been
+        # broken open, then it should be broken open to properly show the
+        # structure.
+        #
+        # -If there is nothing to align, and no other reason to break apart,
+        # then do not do it.
+        #
+        # We will not break open the parens of a long but 'simple' logical
+        # expression.  For example:
+        #
+        # This is an example of a simple logical expression and its formatting:
+        #
+        #     if ( $bigwasteofspace1 && $bigwasteofspace2
+        #         || $bigwasteofspace3 && $bigwasteofspace4 )
+        #
+        # Most people would prefer this than the 'spacey' version:
+        #
+        #     if (
+        #         $bigwasteofspace1 && $bigwasteofspace2
+        #         || $bigwasteofspace3 && $bigwasteofspace4
+        #     )
+        #
+        # To illustrate the rules for breaking logical expressions, consider:
+        #
+        #             FULLY DENSE:
+        #             if ( $opt_excl
+        #                 and ( exists $ids_excl_uc{$id_uc}
+        #                     or grep $id_uc =~ /$_/, @ids_excl_uc ))
+        #
+        # This is on the verge of being difficult to read.  The current
+        # default is to open it up like this:
+        #
+        #             DEFAULT:
+        #             if (
+        #                 $opt_excl
+        #                 and ( exists $ids_excl_uc{$id_uc}
+        #                     or grep $id_uc =~ /$_/, @ids_excl_uc )
+        #               )
+        #
+        # This is a compromise which tries to avoid being too dense and to
+        # spacey.  A more spaced version would be:
+        #
+        #             SPACEY:
+        #             if (
+        #                 $opt_excl
+        #                 and (
+        #                     exists $ids_excl_uc{$id_uc}
+        #                     or grep $id_uc =~ /$_/, @ids_excl_uc
+        #                 )
+        #               )
+        #
+        # Some people might prefer the spacey version -- an option could be
+        # added.  The innermost expression contains a long block '( exists
+        # $ids_...  ')'.
+        #
+        # Here is how the logic goes: We will force a break at the 'or' that
+        # the innermost expression contains, but we will not break apart its
+        # opening and closing containers because (1) it contains no
+        # multi-line sub-containers itself, and (2) there is no alignment to
+        # be gained by breaking it open like this
+        #
+        #             and (
+        #                 exists $ids_excl_uc{$id_uc}
+        #                 or grep $id_uc =~ /$_/, @ids_excl_uc
+        #             )
+        #
+        # (although this looks perfectly ok and might be good for long
+        # expressions).  The outer 'if' container, though, contains a broken
+        # sub-container, so it will be broken open to avoid too much density.
+        # Also, since it contains no 'or's, there will be a forced break at
+        # its 'and'.
+
+        # Handle the experimental flag --break-open-compact-parens
+        # NOTE: This flag is not currently used and may eventually be removed.
+        # If this flag is set, we will implement it by
+        # pretending we did not see the opening structure, since in that case
+        # parens always get opened up.
+        if (   $saw_opening_structure
+            && $rOpts_break_open_compact_parens )
+        {
 
-                    # break after opening structure.
-                    # note: break before closing structure will be automatic
-                    if ( $minimum_depth <= $current_depth ) {
+            # This parameter is a one-character flag, as follows:
+            #  '0' matches no parens  -> break open NOT OK
+            #  '1' matches all parens -> break open OK
+            #  Other values are same as used by the weld-exclusion-list
+            my $flag = $rOpts_break_open_compact_parens;
+            if (   $flag eq '*'
+                || $flag eq '1' )
+            {
+                $saw_opening_structure = 0;
+            }
+            else {
 
-                        if ( $i_opening >= 0 ) {
-                            $self->set_forced_breakpoint($i_opening)
-                              unless ( $do_not_break_apart
-                                || is_unbreakable_container($current_depth) );
-                        }
+                # NOTE: $seqno will be equal to closure var $type_sequence here
+                my $seqno = $type_sequence_to_go[$i_opening];
+                $saw_opening_structure =
+                  !$self->match_paren_control_flag( $seqno, $flag );
+            }
+        }
 
-                        # break at ',' of lower depth level before opening token
-                        if ( $last_comma_index[$depth] ) {
-                            $self->set_forced_breakpoint(
-                                $last_comma_index[$depth] );
-                        }
+        # Set some more flags telling something about this container..
+        my $is_simple_logical_expression;
+        if (   $item_count_stack[$current_depth] == 0
+            && $saw_opening_structure
+            && $tokens_to_go[$i_opening] eq '('
+            && $is_logical_container{ $container_type[$current_depth] } )
+        {
 
-                        # break at '.' of lower depth level before opening token
-                        if ( $last_dot_index[$depth] ) {
-                            $self->set_forced_breakpoint(
-                                $last_dot_index[$depth] );
-                        }
+            # This seems to be a simple logical expression with
+            # no existing breakpoints.  Set a flag to prevent
+            # opening it up.
+            if ( !$has_comma_breakpoints ) {
+                $is_simple_logical_expression = 1;
+            }
 
-                        # break before opening structure if preceded by another
-                        # closing structure and a comma.  This is normally
-                        # done by the previous closing brace, but not
-                        # if it was a one-line block.
-                        if ( $i_opening > 2 ) {
-                            my $i_prev =
-                              ( $types_to_go[ $i_opening - 1 ] eq 'b' )
-                              ? $i_opening - 2
-                              : $i_opening - 1;
-
-                            if (
-                                $types_to_go[$i_prev] eq ','
-                                && (   $types_to_go[ $i_prev - 1 ] eq ')'
-                                    || $types_to_go[ $i_prev - 1 ] eq '}' )
-                              )
-                            {
-                                $self->set_forced_breakpoint($i_prev);
-                            }
+            #---------------------------------------------------
+            # This seems to be a simple logical expression with
+            # breakpoints (broken sublists, for example).  Break
+            # at all 'or's and '||'s.
+            #---------------------------------------------------
+            else {
+                $self->set_logical_breakpoints($current_depth);
+            }
+        }
 
-                            # also break before something like ':('  or '?('
-                            # if appropriate.
-                            elsif (
-                                $types_to_go[$i_prev] =~ /^([k\:\?]|&&|\|\|)$/ )
-                            {
-                                my $token_prev = $tokens_to_go[$i_prev];
-                                if ( $want_break_before{$token_prev} ) {
-                                    $self->set_forced_breakpoint($i_prev);
-                                }
-                            } ## end elsif ( $types_to_go[$i_prev...])
-                        } ## end if ( $i_opening > 2 )
-                    } ## end if ( $minimum_depth <=...)
-
-                    # break after comma following closing structure
-                    if ( $next_type eq ',' ) {
-                        $self->set_forced_breakpoint( $i + 1 );
-                    }
+        # break long terms at any C-style for semicolons (c154)
+        if ( $is_long_term
+            && @{ $rfor_semicolon_list[$current_depth] } )
+        {
+            $self->set_for_semicolon_breakpoints($current_depth);
 
-                    # break before an '=' following closing structure
-                    if (
-                        $is_assignment{$next_nonblank_type}
-                        && ( $breakpoint_stack[$current_depth] !=
-                            $forced_breakpoint_count )
-                      )
-                    {
-                        $self->set_forced_breakpoint($i);
-                    } ## end if ( $is_assignment{$next_nonblank_type...})
-
-                    # break at any comma before the opening structure Added
-                    # for -lp, but seems to be good in general.  It isn't
-                    # obvious how far back to look; the '5' below seems to
-                    # work well and will catch the comma in something like
-                    #  push @list, myfunc( $param, $param, ..
-
-                    my $icomma = $last_comma_index[$depth];
-                    if ( defined($icomma) && ( $i_opening - $icomma ) < 5 ) {
-                        unless ( $forced_breakpoint_to_go[$icomma] ) {
-                            $self->set_forced_breakpoint($icomma);
-                        }
-                    }
-                } ## end logic to open up a container
+            # and open up a long 'for' or 'foreach' container to allow
+            # leading term alignment unless -lp is used.
+            $has_comma_breakpoints = 1 unless ($lp_object);
+        }
 
-                # Break open a logical container open if it was already open
-                elsif ($is_simple_logical_expression
-                    && $has_old_logical_breakpoints[$current_depth] )
-                {
-                    $self->set_logical_breakpoints($current_depth);
-                }
+        #----------------------------------------------------------------
+        # FINALLY: Break open container according to the flags which have
+        # been set.
+        #----------------------------------------------------------------
+        if (
 
-                # Handle long container which does not get opened up
-                elsif ($is_long_term) {
+            # breaks for code BLOCKS are handled at a higher level
+            !$block_type
 
-                    # must set fake breakpoint to alert outer containers that
-                    # they are complex
-                    set_fake_breakpoint();
-                } ## end elsif ($is_long_term)
+            # we do not need to break at the top level of an 'if'
+            # type expression
+            && !$is_simple_logical_expression
 
-            } ## end elsif ( $depth < $current_depth)
+            ## modification to keep ': (' containers vertically tight;
+            ## but probably better to let user set -vt=1 to avoid
+            ## inconsistency with other paren types
+            ## && ($container_type[$current_depth] ne ':')
 
-            #------------------------------------------------------------
-            # Handle this token
-            #------------------------------------------------------------
+            # otherwise, we require one of these reasons for breaking:
+            && (
 
-            $current_depth = $depth;
+                # - this term has forced line breaks
+                $has_comma_breakpoints
 
-            # most token types can skip the rest of this loop
-            next unless ( $quick_filter{$type} );
+                # - the opening container is separated from this batch
+                #   for some reason (comment, blank line, code block)
+                # - this is a non-paren container spanning multiple lines
+                || !$saw_opening_structure
 
-            # handle comma-arrow
-            if ( $type eq '=>' ) {
-                next if ( $last_nonblank_type eq '=>' );
-                next if $rOpts_break_at_old_comma_breakpoints;
-                next
-                  if ( $rOpts_comma_arrow_breakpoints == 3
-                    && !$override_cab3[$depth] );
-                $want_comma_break[$depth]   = 1;
-                $index_before_arrow[$depth] = $i_last_nonblank_token;
-                next;
-            } ## end if ( $type eq '=>' )
+                # - this is a long block contained in another breakable
+                #   container
+                || $is_long_term && !$self->is_in_block_by_i($i_opening)
+            )
+          )
+        {
 
-            elsif ( $type eq '.' ) {
-                $last_dot_index[$depth] = $i;
+            # do special -lp breaks at the CLOSING token for INTACT
+            # blocks (because we might not do them if the block does
+            # not break open)
+            if ($lp_object) {
+                my $K_begin_line = $lp_object->get_K_begin_line();
+                my $i_begin_line = $K_begin_line - $K_to_go[0];
+                $self->set_forced_lp_break( $i_begin_line, $i_opening );
             }
 
-            # Turn off alignment if we are sure that this is not a list
-            # environment.  To be safe, we will do this if we see certain
-            # non-list tokens, such as ';', and also the environment is
-            # not a list.  Note that '=' could be in any of the = operators
-            # (lextest.t). We can't just use the reported environment
-            # because it can be incorrect in some cases.
-            elsif ( ( $type =~ /^[\;\<\>\~]$/ || $is_assignment{$type} )
-                && !$self->is_in_list_by_i($i) )
-            {
-                $dont_align[$depth]         = 1;
-                $want_comma_break[$depth]   = 0;
-                $index_before_arrow[$depth] = -1;
-            } ## end elsif ( ( $type =~ /^[\;\<\>\~]$/...))
-
-            # now just handle any commas
-            next unless ( $type eq ',' );
-
-            $last_dot_index[$depth]   = undef;
-            $last_comma_index[$depth] = $i;
-
-            # break here if this comma follows a '=>'
-            # but not if there is a side comment after the comma
-            if ( $want_comma_break[$depth] ) {
+            # break after opening structure.
+            # note: break before closing structure will be automatic
+            if ( $minimum_depth <= $current_depth ) {
 
-                if ( $next_nonblank_type =~ /^[\)\}\]R]$/ ) {
-                    if ($rOpts_comma_arrow_breakpoints) {
-                        $want_comma_break[$depth] = 0;
-                        next;
-                    }
+                if ( $i_opening >= 0 ) {
+                    $self->set_forced_breakpoint($i_opening)
+                      unless ( $do_not_break_apart
+                        || is_unbreakable_container($current_depth) );
                 }
 
-                $self->set_forced_breakpoint($i)
-                  unless ( $next_nonblank_type eq '#' );
-
-                # break before the previous token if it looks safe
-                # Example of something that we will not try to break before:
-                #   DBI::SQL_SMALLINT() => $ado_consts->{adSmallInt},
-                # Also we don't want to break at a binary operator (like +):
-                # $c->createOval(
-                #    $x + $R, $y +
-                #    $R => $x - $R,
-                #    $y - $R, -fill   => 'black',
-                # );
-                my $ibreak = $index_before_arrow[$depth] - 1;
-                if (   $ibreak > 0
-                    && $tokens_to_go[ $ibreak + 1 ] !~ /^[\)\}\]]$/ )
-                {
-                    if ( $tokens_to_go[$ibreak] eq '-' ) { $ibreak-- }
-                    if ( $types_to_go[$ibreak] eq 'b' )  { $ibreak-- }
-                    if ( $types_to_go[$ibreak] =~ /^[,wiZCUG\(\{\[]$/ ) {
-
-                        # don't break pointer calls, such as the following:
-                        #  File::Spec->curdir  => 1,
-                        # (This is tokenized as adjacent 'w' tokens)
-                        ##if ( $tokens_to_go[ $ibreak + 1 ] !~ /^->/ ) {
-
-                        # And don't break before a comma, as in the following:
-                        # ( LONGER_THAN,=> 1,
-                        #    EIGHTY_CHARACTERS,=> 2,
-                        #    CAUSES_FORMATTING,=> 3,
-                        #    LIKE_THIS,=> 4,
-                        # );
-                        # This example is for -tso but should be general rule
-                        if (   $tokens_to_go[ $ibreak + 1 ] ne '->'
-                            && $tokens_to_go[ $ibreak + 1 ] ne ',' )
-                        {
-                            $self->set_forced_breakpoint($ibreak);
-                        }
-                    } ## end if ( $types_to_go[$ibreak...])
-                } ## end if ( $ibreak > 0 && $tokens_to_go...)
-
-                $want_comma_break[$depth]   = 0;
-                $index_before_arrow[$depth] = -1;
+                # break at ',' of lower depth level before opening token
+                if ( $last_comma_index[$depth] ) {
+                    $self->set_forced_breakpoint( $last_comma_index[$depth] );
+                }
 
-                # handle list which mixes '=>'s and ','s:
-                # treat any list items so far as an interrupted list
-                $interrupted_list[$depth] = 1;
-                next;
-            } ## end if ( $want_comma_break...)
-
-            # Break after all commas above starting depth...
-            # But only if the last closing token was followed by a comma,
-            #   to avoid breaking a list operator (issue c119)
-            if (   $depth < $starting_depth
-                && $comma_follows_last_closing_token
-                && !$dont_align[$depth] )
-            {
-                $self->set_forced_breakpoint($i)
-                  unless ( $next_nonblank_type eq '#' );
-                next;
-            }
+                # break at '.' of lower depth level before opening token
+                if ( $last_dot_index[$depth] ) {
+                    $self->set_forced_breakpoint( $last_dot_index[$depth] );
+                }
 
-            # add this comma to the list..
-            my $item_count = $item_count_stack[$depth];
-            if ( $item_count == 0 ) {
+                # break before opening structure if preceded by another
+                # closing structure and a comma.  This is normally
+                # done by the previous closing brace, but not
+                # if it was a one-line block.
+                if ( $i_opening > 2 ) {
+                    my $i_prev =
+                      ( $types_to_go[ $i_opening - 1 ] eq 'b' )
+                      ? $i_opening - 2
+                      : $i_opening - 1;
 
-                # but do not form a list with no opening structure
-                # for example:
+                    my $type_prev  = $types_to_go[$i_prev];
+                    my $token_prev = $tokens_to_go[$i_prev];
+                    if (
+                        $type_prev eq ','
+                        && (   $types_to_go[ $i_prev - 1 ] eq ')'
+                            || $types_to_go[ $i_prev - 1 ] eq '}' )
+                      )
+                    {
+                        $self->set_forced_breakpoint($i_prev);
+                    }
 
-                #            open INFILE_COPY, ">$input_file_copy"
-                #              or die ("very long message");
-                if ( ( $opening_structure_index_stack[$depth] < 0 )
-                    && $self->is_in_block_by_i($i) )
-                {
-                    $dont_align[$depth] = 1;
+                    # also break before something like ':('  or '?('
+                    # if appropriate.
+                    elsif ($type_prev =~ /^([k\:\?]|&&|\|\|)$/
+                        && $want_break_before{$token_prev} )
+                    {
+                        $self->set_forced_breakpoint($i_prev);
+                    }
                 }
-            } ## end if ( $item_count == 0 )
-
-            $comma_index[$depth][$item_count] = $i;
-            ++$item_count_stack[$depth];
-            if ( $last_nonblank_type =~ /^[iR\]]$/ ) {
-                $identifier_count_stack[$depth]++;
             }
-        } ## end while ( ++$i <= $max_index_to_go)
-
-        #-------------------------------------------
-        # end of loop over all tokens in this batch
-        #-------------------------------------------
 
-        # set breaks for any unfinished lists ..
-        foreach my $dd ( reverse( $minimum_depth .. $current_depth ) ) {
+            # break after comma following closing structure
+            if ( $types_to_go[ $i + 1 ] eq ',' ) {
+                $self->set_forced_breakpoint( $i + 1 );
+            }
 
-            $interrupted_list[$dd]   = 1;
-            $has_broken_sublist[$dd] = 1 if ( $dd < $current_depth );
-            $self->set_comma_breakpoints( $dd, $rbond_strength_bias );
-            $self->set_logical_breakpoints($dd)
-              if ( $has_old_logical_breakpoints[$dd] );
-            $self->set_for_semicolon_breakpoints($dd);
+            # break before an '=' following closing structure
+            if (
+                $is_assignment{$next_nonblank_type}
+                && ( $breakpoint_stack[$current_depth] !=
+                    $forced_breakpoint_count )
+              )
+            {
+                $self->set_forced_breakpoint($i);
+            }
 
-            # break open container...
-            my $i_opening = $opening_structure_index_stack[$dd];
-            if ( defined($i_opening) && $i_opening >= 0 ) {
-                $self->set_forced_breakpoint($i_opening)
-                  unless (
-                    is_unbreakable_container($dd)
+            # break at any comma before the opening structure Added
+            # for -lp, but seems to be good in general.  It isn't
+            # obvious how far back to look; the '5' below seems to
+            # work well and will catch the comma in something like
+            #  push @list, myfunc( $param, $param, ..
 
-                    # Avoid a break which would place an isolated ' or "
-                    # on a line
-                    || (   $type eq 'Q'
-                        && $i_opening >= $max_index_to_go - 2
-                        && ( $token eq "'" || $token eq '"' ) )
-                  );
+            my $icomma = $last_comma_index[$depth];
+            if ( defined($icomma) && ( $i_opening - $icomma ) < 5 ) {
+                unless ( $forced_breakpoint_to_go[$icomma] ) {
+                    $self->set_forced_breakpoint($icomma);
+                }
             }
-        } ## end for ( my $dd = $current_depth...)
+        }
 
-        # Return a flag indicating if the input file had some good breakpoints.
-        # This flag will be used to force a break in a line shorter than the
-        # allowed line length.
-        if ( $has_old_logical_breakpoints[$current_depth] ) {
-            $saw_good_breakpoint = 1;
+        #-----------------------------------------------------------
+        # Break open a logical container open if it was already open
+        #-----------------------------------------------------------
+        elsif ($is_simple_logical_expression
+            && $has_old_logical_breakpoints[$current_depth] )
+        {
+            $self->set_logical_breakpoints($current_depth);
         }
 
-        # A complex line with one break at an = has a good breakpoint.
-        # This is not complex ($total_depth_variation=0):
-        # $res1
-        #   = 10;
-        #
-        # This is complex ($total_depth_variation=6):
-        # $res2 =
-        #  (is_boundp("a", 'self-insert') && is_boundp("b", 'self-insert'));
+        # Handle long container which does not get opened up
+        elsif ($is_long_term) {
 
-        # The check ($i_old_.. < $max_index_to_go) was added to fix b1333
-        elsif ($i_old_assignment_break
-            && $total_depth_variation > 4
-            && $old_breakpoint_count == 1
-            && $i_old_assignment_break < $max_index_to_go )
-        {
-            $saw_good_breakpoint = 1;
-        } ## end elsif ( $i_old_assignment_break...)
+            # must set fake breakpoint to alert outer containers that
+            # they are complex
+            set_fake_breakpoint();
+        }
 
-        return $saw_good_breakpoint;
-    } ## end sub break_lists
+        return;
+    } ## end sub break_lists_decreasing_depth
 } ## end closure break_lists
 
 my %is_kwiZ;
@@ -19809,7 +21340,9 @@ sub find_token_starting_list {
     # This will be the return index
     my $i_opening_minus = $i_opening_paren;
 
-    goto RETURN if ( $i_opening_minus <= 0 );
+    if ( $i_opening_minus <= 0 ) {
+        return $i_opening_minus;
+    }
 
     my $im1 = $i_opening_paren - 1;
     my ( $iprev_nb, $type_prev_nb ) = ( $im1, $types_to_go[$im1] );
@@ -19850,8 +21383,6 @@ sub find_token_starting_list {
         if ( $types_to_go[$i_opening_minus] eq 'b' ) { $i_opening_minus++ }
     }
 
-  RETURN:
-
     DEBUG_FIND_START && print <<EOM;
 FIND_START: i=$i_opening_paren tok=$tokens_to_go[$i_opening_paren] => im=$i_opening_minus tok=$tokens_to_go[$i_opening_minus]
 EOM
@@ -19859,7 +21390,7 @@ EOM
     return $i_opening_minus;
 } ## end sub find_token_starting_list
 
-{    ## begin closure set_comma_breakpoints_do
+{    ## begin closure set_comma_breakpoints_final
 
     my %is_keyword_with_special_leading_term;
 
 
     use constant DEBUG_SPARSE => 0;
 
-    sub set_comma_breakpoints_do {
+    sub comma_broken_sublist_rule {
+
+        my (
+
+            $self,    #
+
+            $item_count,
+            $interrupted,
+            $i_first_comma,
+            $i_true_last_comma,
+            $ri_term_end,
+            $ri_term_begin,
+            $ri_term_comma,
+            $ritem_lengths,
+
+        ) = @_;
+
+        # Break at every comma except for a comma between two
+        # simple, small terms.  This prevents long vertical
+        # columns of, say, just 0's.
+        my $small_length = 10;    # 2 + actual maximum length wanted
+
+        # We'll insert a break in long runs of small terms to
+        # allow alignment in uniform tables.
+        my $skipped_count = 0;
+        my $columns       = table_columns_available($i_first_comma);
+        my $fields        = int( $columns / $small_length );
+        if (   $rOpts_maximum_fields_per_table
+            && $fields > $rOpts_maximum_fields_per_table )
+        {
+            $fields = $rOpts_maximum_fields_per_table;
+        }
+        my $max_skipped_count = $fields - 1;
+
+        my $is_simple_last_term = 0;
+        my $is_simple_next_term = 0;
+        foreach my $j ( 0 .. $item_count ) {
+            $is_simple_last_term = $is_simple_next_term;
+            $is_simple_next_term = 0;
+            if (   $j < $item_count
+                && $ri_term_end->[$j] == $ri_term_begin->[$j]
+                && $ritem_lengths->[$j] <= $small_length )
+            {
+                $is_simple_next_term = 1;
+            }
+            next if $j == 0;
+            if (   $is_simple_last_term
+                && $is_simple_next_term
+                && $skipped_count < $max_skipped_count )
+            {
+                $skipped_count++;
+            }
+            else {
+                $skipped_count = 0;
+                my $i_tc = $ri_term_comma->[ $j - 1 ];
+                last unless defined $i_tc;
+                $self->set_forced_breakpoint($i_tc);
+            }
+        }
+
+        # always break at the last comma if this list is
+        # interrupted; we wouldn't want to leave a terminal '{', for
+        # example.
+        if ($interrupted) {
+            $self->set_forced_breakpoint($i_true_last_comma);
+        }
+        return;
+    }
+
+    sub set_emergency_comma_breakpoints {
+
+        my (
+
+            $self,    #
+
+            $number_of_fields_best,
+            $rinput_hash,
+            $comma_count,
+            $i_first_comma,
+
+        ) = @_;
+
+        # The number of fields worked out to be negative, so we
+        # have to make an emergency fix.
+
+        my $rcomma_index        = $rinput_hash->{rcomma_index};
+        my $next_nonblank_type  = $rinput_hash->{next_nonblank_type};
+        my $rdo_not_break_apart = $rinput_hash->{rdo_not_break_apart};
+        my $must_break_open     = $rinput_hash->{must_break_open};
+
+        # are we an item contained in an outer list?
+        my $in_hierarchical_list = $next_nonblank_type =~ /^[\}\,]$/;
+
+        # In many cases, it may be best to not force a break if there is just
+        # one comma, because the standard continuation break logic will do a
+        # better job without it.
+
+        # In the common case that all but one of the terms can fit
+        # on a single line, it may look better not to break open the
+        # containing parens.  Consider, for example
+
+        #     $color =
+        #       join ( '/',
+        #         sort { $color_value{$::a} <=> $color_value{$::b}; }
+        #         keys %colors );
+
+        # which will look like this with the container broken:
+
+        #   $color = join (
+        #       '/',
+        #       sort { $color_value{$::a} <=> $color_value{$::b}; } keys %colors
+        #   );
+
+        # Here is an example of this rule for a long last term:
+
+        #   log_message( 0, 256, 128,
+        #       "Number of routes in adj-RIB-in to be considered: $peercount" );
+
+        # And here is an example with a long first term:
+
+        # $s = sprintf(
+        # "%2d wallclock secs (%$f usr %$f sys + %$f cusr %$f csys = %$f CPU)",
+        #     $r, $pu, $ps, $cu, $cs, $tt
+        #   )
+        #   if $style eq 'all';
+
+        my $i_last_comma = $rcomma_index->[ $comma_count - 1 ];
+
+        my $long_last_term = $self->excess_line_length( 0, $i_last_comma ) <= 0;
+        my $long_first_term =
+          $self->excess_line_length( $i_first_comma + 1, $max_index_to_go ) <=
+          0;
+
+        # break at every comma ...
+        if (
+
+            # if requested by user or is best looking
+            $number_of_fields_best == 1
 
-        # Given a list with some commas, set breakpoints at some of the
-        # commas, if necessary, to make it easy to read.
+            # or if this is a sublist of a larger list
+            || $in_hierarchical_list
+
+            # or if multiple commas and we don't have a long first or last
+            # term
+            || ( $comma_count > 1
+                && !( $long_last_term || $long_first_term ) )
+          )
+        {
+            foreach ( 0 .. $comma_count - 1 ) {
+                $self->set_forced_breakpoint( $rcomma_index->[$_] );
+            }
+        }
+        elsif ($long_last_term) {
+
+            $self->set_forced_breakpoint($i_last_comma);
+            ${$rdo_not_break_apart} = 1 unless $must_break_open;
+        }
+        elsif ($long_first_term) {
+
+            $self->set_forced_breakpoint($i_first_comma);
+        }
+        else {
+
+            # let breaks be defined by default bond strength logic
+        }
+        return;
+    }
+
+    sub set_comma_breakpoints_final {
+
+        # Given a list of comma-separated items, set breakpoints at some of
+        # the commas, if necessary, to make it easy to read.
 
         my ( $self, $rinput_hash ) = @_;
 
@@ -19906,16 +21605,20 @@ EOM
         }
         my $is_lp_formatting = ref( $leading_spaces_to_go[$i_first_comma] );
 
-        #---------------------------------------------------------------
-        # find lengths of all items in the list to calculate page layout
-        #---------------------------------------------------------------
+        #-----------------------------------------------------------
+        # Section A: Find lengths of all items in the list needed to
+        # calculate page layout
+        #-----------------------------------------------------------
         my $comma_count = $item_count;
-        my @item_lengths;
-        my @i_term_begin;
-        my @i_term_end;
-        my @i_term_comma;
+
+        my $ritem_lengths = [];
+        my $ri_term_begin = [];
+        my $ri_term_end   = [];
+        my $ri_term_comma = [];
+
+        my $rmax_length = [ 0, 0 ];
+
         my $i_prev_plus;
-        my @max_length = ( 0, 0 );
         my $first_term_length;
         my $i      = $i_opening_paren;
         my $is_odd = 1;
@@ -19931,22 +21634,22 @@ EOM
               ( $types_to_go[$i_prev_plus] eq 'b' )
               ? $i_prev_plus + 1
               : $i_prev_plus;
-            push @i_term_begin, $i_term_begin;
-            push @i_term_end,   $i_term_end;
-            push @i_term_comma, $i;
+            push @{$ri_term_begin}, $i_term_begin;
+            push @{$ri_term_end},   $i_term_end;
+            push @{$ri_term_comma}, $i;
 
             # note: currently adding 2 to all lengths (for comma and space)
             my $length =
               2 + token_sequence_length( $i_term_begin, $i_term_end );
-            push @item_lengths, $length;
+            push @{$ritem_lengths}, $length;
 
             if ( $j == 0 ) {
                 $first_term_length = $length;
             }
             else {
 
-                if ( $length > $max_length[$is_odd] ) {
-                    $max_length[$is_odd] = $length;
+                if ( $length > $rmax_length->[$is_odd] ) {
+                    $rmax_length->[$is_odd] = $length;
                 }
             }
         }
@@ -19970,15 +21673,15 @@ EOM
 
             # add 2 to length because other lengths include a comma and a blank
             $last_item_length += 2;
-            push @item_lengths, $last_item_length;
-            push @i_term_begin, $i_b + 1;
-            push @i_term_end,   $i_e;
-            push @i_term_comma, undef;
+            push @{$ritem_lengths}, $last_item_length;
+            push @{$ri_term_begin}, $i_b + 1;
+            push @{$ri_term_end},   $i_e;
+            push @{$ri_term_comma}, undef;
 
             my $i_odd = $item_count % 2;
 
-            if ( $last_item_length > $max_length[$i_odd] ) {
-                $max_length[$i_odd] = $last_item_length;
+            if ( $last_item_length > $rmax_length->[$i_odd] ) {
+                $rmax_length->[$i_odd] = $last_item_length;
             }
 
             $item_count++;
@@ -19989,67 +21692,32 @@ EOM
             }
         }
 
-        #---------------------------------------------------------------
         # End of length calculations
-        #---------------------------------------------------------------
 
-        #---------------------------------------------------------------
-        # Compound List Rule 1:
+        #-----------------------------------------
+        # Section B: Handle some special cases ...
+        #-----------------------------------------
+
+        #-------------------------------------------------------------
+        # Special Case B1: Compound List Rule 1:
         # Break at (almost) every comma for a list containing a broken
         # sublist.  This has higher priority than the Interrupted List
         # Rule.
-        #---------------------------------------------------------------
+        #-------------------------------------------------------------
         if ($has_broken_sublist) {
 
-            # Break at every comma except for a comma between two
-            # simple, small terms.  This prevents long vertical
-            # columns of, say, just 0's.
-            my $small_length = 10;    # 2 + actual maximum length wanted
-
-            # We'll insert a break in long runs of small terms to
-            # allow alignment in uniform tables.
-            my $skipped_count = 0;
-            my $columns       = table_columns_available($i_first_comma);
-            my $fields        = int( $columns / $small_length );
-            if (   $rOpts_maximum_fields_per_table
-                && $fields > $rOpts_maximum_fields_per_table )
-            {
-                $fields = $rOpts_maximum_fields_per_table;
-            }
-            my $max_skipped_count = $fields - 1;
-
-            my $is_simple_last_term = 0;
-            my $is_simple_next_term = 0;
-            foreach my $j ( 0 .. $item_count ) {
-                $is_simple_last_term = $is_simple_next_term;
-                $is_simple_next_term = 0;
-                if (   $j < $item_count
-                    && $i_term_end[$j] == $i_term_begin[$j]
-                    && $item_lengths[$j] <= $small_length )
-                {
-                    $is_simple_next_term = 1;
-                }
-                next if $j == 0;
-                if (   $is_simple_last_term
-                    && $is_simple_next_term
-                    && $skipped_count < $max_skipped_count )
-                {
-                    $skipped_count++;
-                }
-                else {
-                    $skipped_count = 0;
-                    my $i_tc = $i_term_comma[ $j - 1 ];
-                    last unless defined $i_tc;
-                    $self->set_forced_breakpoint($i_tc);
-                }
-            }
+            $self->comma_broken_sublist_rule(
 
-            # always break at the last comma if this list is
-            # interrupted; we wouldn't want to leave a terminal '{', for
-            # example.
-            if ($interrupted) {
-                $self->set_forced_breakpoint($i_true_last_comma);
-            }
+                $item_count,
+                $interrupted,
+                $i_first_comma,
+                $i_true_last_comma,
+                $ri_term_end,
+                $ri_term_begin,
+                $ri_term_comma,
+                $ritem_lengths,
+
+            );
             return;
         }
 
@@ -20058,11 +21726,11 @@ EOM
 #i_first = $i_first_comma  i_last=$i_last_comma max=$max_index_to_go\n";
 #print "depth=$depth has_broken=$has_broken_sublist[$depth] is_multi=$is_multiline opening_paren=($i_opening_paren) \n";
 
-        #---------------------------------------------------------------
-        # Interrupted List Rule:
+        #--------------------------------------------------------------
+        # Special Case B2: Interrupted List Rule:
         # A list is forced to use old breakpoints if it was interrupted
         # by side comments or blank lines, or requested by user.
-        #---------------------------------------------------------------
+        #--------------------------------------------------------------
         if (   $rOpts_break_at_old_comma_breakpoints
             || $interrupted
             || $i_opening_paren < 0 )
@@ -20071,19 +21739,16 @@ EOM
             return;
         }
 
-        #---------------------------------------------------------------
-        # Looks like a list of items.  We have to look at it and size it up.
-        #---------------------------------------------------------------
-
         my $opening_token       = $tokens_to_go[$i_opening_paren];
         my $opening_is_in_block = $self->is_in_block_by_i($i_opening_paren);
 
-        #-------------------------------------------------------------------
-        # Return if this will fit on one line
-        #-------------------------------------------------------------------
+        #-----------------------------------------------------------------
+        # Special Case B3: If it fits on one line, return and let the line
+        # break logic decide if and where to break.
+        #-----------------------------------------------------------------
 
-        # The -bbxi=2 parameters can add an extra hidden level of indentation;
-        # this needs a tolerance to avoid instability.  Fixes b1259, 1260.
+        # The -bbxi=2 parameters can add an extra hidden level of indentation
+        # so they need a tolerance to avoid instability.  Fixes b1259, 1260.
         my $tol = 0;
         if (   $break_before_container_types{$opening_token}
             && $container_indentation_options{$opening_token}
@@ -20098,15 +21763,22 @@ EOM
         }
 
         my $i_opening_minus = $self->find_token_starting_list($i_opening_paren);
-        return
-          unless $self->excess_line_length( $i_opening_minus, $i_closing_paren )
-          + $tol > 0;
+        my $excess =
+          $self->excess_line_length( $i_opening_minus, $i_closing_paren );
+        return if ( $excess + $tol <= 0 );
+
+        #---------------------------------------
+        # Section C: Handle a multiline list ...
+        #---------------------------------------
+
+        #---------------------------------------------------------------
+        # Section C1: Determine '$number_of_fields' = the best number of
+        # fields to use if this is to be formatted as a table.
+        #---------------------------------------------------------------
 
-        #-------------------------------------------------------------------
         # Now we know that this block spans multiple lines; we have to set
         # at least one breakpoint -- real or fake -- as a signal to break
         # open any outer containers.
-        #-------------------------------------------------------------------
         set_fake_breakpoint();
 
         # be sure we do not extend beyond the current list length
@@ -20123,8 +21795,8 @@ EOM
               $maximum_line_length_at_level[ $levels_to_go[$i_opening_minus] ]
               - total_line_length( $i_opening_minus, $i_opening_paren );
             $need_lp_break_open =
-                 ( $max_length[0] > $columns_if_unbroken )
-              || ( $max_length[1] > $columns_if_unbroken )
+                 ( $rmax_length->[0] > $columns_if_unbroken )
+              || ( $rmax_length->[1] > $columns_if_unbroken )
               || ( $first_term_length > $columns_if_unbroken );
         }
 
@@ -20133,8 +21805,8 @@ EOM
         # list items might be a hash list.  But if we can be sure that
         # it is not a hash, then we can allow an odd number for more
         # flexibility.
-        my $odd_or_even = 2;    # 1 = odd field count ok, 2 = want even count
-
+        # 1 = odd field count ok, 2 = want even count
+        my $odd_or_even = 2;
         if (   $identifier_count >= $item_count - 1
             || $is_assignment{$next_nonblank_type}
             || ( $list_type && $list_type ne '=>' && $list_type !~ /^[\:\?]$/ )
@@ -20146,12 +21818,12 @@ EOM
         # do we have a long first term which should be
         # left on a line by itself?
         my $use_separate_first_term = (
-            $odd_or_even == 1           # only if we can use 1 field/line
-              && $item_count > 3        # need several items
+            $odd_or_even == 1              # only if we can use 1 field/line
+              && $item_count > 3           # need several items
               && $first_term_length >
-              2 * $max_length[0] - 2    # need long first term
+              2 * $rmax_length->[0] - 2    # need long first term
               && $first_term_length >
-              2 * $max_length[1] - 2    # need long first term
+              2 * $rmax_length->[1] - 2    # need long first term
         );
 
         # or do we know from the type of list that the first term should
@@ -20187,23 +21859,25 @@ EOM
             $i_first_comma   = $rcomma_index->[1];
             $item_count--;
             return if $comma_count == 1;
-            shift @item_lengths;
-            shift @i_term_begin;
-            shift @i_term_end;
-            shift @i_term_comma;
+            shift @{$ritem_lengths};
+            shift @{$ri_term_begin};
+            shift @{$ri_term_end};
+            shift @{$ri_term_comma};
         }
 
         # if not, update the metrics to include the first term
         else {
-            if ( $first_term_length > $max_length[0] ) {
-                $max_length[0] = $first_term_length;
+            if ( $first_term_length > $rmax_length->[0] ) {
+                $rmax_length->[0] = $first_term_length;
             }
         }
 
         # Field width parameters
-        my $pair_width = ( $max_length[0] + $max_length[1] );
+        my $pair_width = ( $rmax_length->[0] + $rmax_length->[1] );
         my $max_width =
-          ( $max_length[0] > $max_length[1] ) ? $max_length[0] : $max_length[1];
+          ( $rmax_length->[0] > $rmax_length->[1] )
+          ? $rmax_length->[0]
+          : $rmax_length->[1];
 
         # Number of free columns across the page width for laying out tables
         my $columns = table_columns_available($i_first_comma);
@@ -20215,10 +21889,9 @@ EOM
         # paren, but in some cases we might not.
         if (   $rOpts_variable_maximum_line_length
             && $tokens_to_go[$i_opening_paren] eq '('
-            && @i_term_begin )
-          ##&& !$old_breakpoint_to_go[$i_opening_paren] )  ## in b1210 patch
+            && @{$ri_term_begin} )
         {
-            my $ib   = $i_term_begin[0];
+            my $ib   = $ri_term_begin->[0];
             my $type = $types_to_go[$ib];
 
             # So far, the only known instance of this problem is when
@@ -20239,19 +21912,19 @@ EOM
             }
         }
 
-        # Estimated maximum number of fields which fit this space
-        # This will be our first guess
+        # Estimated maximum number of fields which fit this space.
+        # This will be our first guess:
         my $number_of_fields_max =
           maximum_number_of_fields( $columns, $odd_or_even, $max_width,
             $pair_width );
         my $number_of_fields = $number_of_fields_max;
 
-        # Find the best-looking number of fields
-        # and make this our second guess if possible
+        # Find the best-looking number of fields.
+        # This will be our second guess, if possible.
         my ( $number_of_fields_best, $ri_ragged_break_list,
             $new_identifier_count )
-          = $self->study_list_complexity( \@i_term_begin, \@i_term_end,
-            \@item_lengths, $max_width );
+          = $self->study_list_complexity( $ri_term_begin, $ri_term_end,
+            $ritem_lengths, $max_width );
 
         if (   $number_of_fields_best != 0
             && $number_of_fields_best < $number_of_fields_max )
@@ -20259,10 +21932,8 @@ EOM
             $number_of_fields = $number_of_fields_best;
         }
 
-        # ----------------------------------------------------------------------
-        # If we are crowded and the -lp option is being used, try to
-        # undo some indentation
-        # ----------------------------------------------------------------------
+        # If we are crowded and the -lp option is being used, try
+        # to undo some indentation
         if (
             $is_lp_formatting
             && (
@@ -20272,46 +21943,19 @@ EOM
             )
           )
         {
-            my $available_spaces =
-              $self->get_available_spaces_to_go($i_first_comma);
-            if ( $available_spaces > 0 ) {
-
-                my $spaces_wanted = $max_width - $columns;    # for 1 field
-
-                if ( $number_of_fields_best == 0 ) {
-                    $number_of_fields_best =
-                      get_maximum_fields_wanted( \@item_lengths );
-                }
-
-                if ( $number_of_fields_best != 1 ) {
-                    my $spaces_wanted_2 =
-                      1 + $pair_width - $columns;    # for 2 fields
-                    if ( $available_spaces > $spaces_wanted_2 ) {
-                        $spaces_wanted = $spaces_wanted_2;
-                    }
-                }
+            ( $number_of_fields, $number_of_fields_best, $columns ) =
+              $self->lp_table_fix(
+
+                $columns,
+                $i_first_comma,
+                $max_width,
+                $number_of_fields,
+                $number_of_fields_best,
+                $odd_or_even,
+                $pair_width,
+                $ritem_lengths,
 
-                if ( $spaces_wanted > 0 ) {
-                    my $deleted_spaces =
-                      $self->reduce_lp_indentation( $i_first_comma,
-                        $spaces_wanted );
-
-                    # redo the math
-                    if ( $deleted_spaces > 0 ) {
-                        $columns = table_columns_available($i_first_comma);
-                        $number_of_fields_max =
-                          maximum_number_of_fields( $columns, $odd_or_even,
-                            $max_width, $pair_width );
-                        $number_of_fields = $number_of_fields_max;
-
-                        if (   $number_of_fields_best == 1
-                            && $number_of_fields >= 1 )
-                        {
-                            $number_of_fields = $number_of_fields_best;
-                        }
-                    }
-                }
-            }
+              );
         }
 
         # try for one column if two won't work
@@ -20337,94 +21981,30 @@ EOM
         # are we an item contained in an outer list?
         my $in_hierarchical_list = $next_nonblank_type =~ /^[\}\,]$/;
 
+        #-----------------------------------------------------------------
+        # Section C2: Stop here if we did not compute a positive number of
+        # fields. In this case we just have to bail out.
+        #-----------------------------------------------------------------
         if ( $number_of_fields <= 0 ) {
 
-#         #---------------------------------------------------------------
-#         # We're in trouble.  We can't find a single field width that works.
-#         # There is no simple answer here; we may have a single long list
-#         # item, or many.
-#         #---------------------------------------------------------------
-#
-#         In many cases, it may be best to not force a break if there is just one
-#         comma, because the standard continuation break logic will do a better
-#         job without it.
-#
-#         In the common case that all but one of the terms can fit
-#         on a single line, it may look better not to break open the
-#         containing parens.  Consider, for example
-#
-#             $color =
-#               join ( '/',
-#                 sort { $color_value{$::a} <=> $color_value{$::b}; }
-#                 keys %colors );
-#
-#         which will look like this with the container broken:
-#
-#             $color = join (
-#                 '/',
-#                 sort { $color_value{$::a} <=> $color_value{$::b}; } keys %colors
-#             );
-#
-#         Here is an example of this rule for a long last term:
-#
-#             log_message( 0, 256, 128,
-#                 "Number of routes in adj-RIB-in to be considered: $peercount" );
-#
-#         And here is an example with a long first term:
-#
-#         $s = sprintf(
-# "%2d wallclock secs (%$f usr %$f sys + %$f cusr %$f csys = %$f CPU)",
-#             $r, $pu, $ps, $cu, $cs, $tt
-#           )
-#           if $style eq 'all';
-
-            $i_last_comma = $rcomma_index->[ $comma_count - 1 ];
-
-            my $long_last_term =
-              $self->excess_line_length( 0, $i_last_comma ) <= 0;
-            my $long_first_term =
-              $self->excess_line_length( $i_first_comma + 1, $max_index_to_go )
-              <= 0;
-
-            # break at every comma ...
-            if (
-
-                # if requested by user or is best looking
-                $number_of_fields_best == 1
-
-                # or if this is a sublist of a larger list
-                || $in_hierarchical_list
-
-                # or if multiple commas and we don't have a long first or last
-                # term
-                || ( $comma_count > 1
-                    && !( $long_last_term || $long_first_term ) )
-              )
-            {
-                foreach ( 0 .. $comma_count - 1 ) {
-                    $self->set_forced_breakpoint( $rcomma_index->[$_] );
-                }
-            }
-            elsif ($long_last_term) {
-
-                $self->set_forced_breakpoint($i_last_comma);
-                ${$rdo_not_break_apart} = 1 unless $must_break_open;
-            }
-            elsif ($long_first_term) {
+            $self->set_emergency_comma_breakpoints(
 
-                $self->set_forced_breakpoint($i_first_comma);
-            }
-            else {
+                $number_of_fields_best,
+                $rinput_hash,
+                $comma_count,
+                $i_first_comma,
 
-                # let breaks be defined by default bond strength logic
-            }
+            );
             return;
         }
 
-        # --------------------------------------------------------
-        # We have a tentative field count that seems to work.
+        #------------------------------------------------------------------
+        # Section C3: We have a tentative field count that seems to work.
+        # Now we must look more closely to determine if a table layout will
+        # actually look okay.
+        #------------------------------------------------------------------
+
         # How many lines will this require?
-        # --------------------------------------------------------
         my $formatted_lines = $item_count / ($number_of_fields);
         if ( $formatted_lines != int $formatted_lines ) {
             $formatted_lines = 1 + int $formatted_lines;
@@ -20490,26 +22070,27 @@ EOM
                     $two_line_word_wrap_ok = 1;
                 }
                 else {
-                    my $KK = $K_to_go[$i_opening_paren];
+                    my $seqno = $type_sequence_to_go[$i_opening_paren];
                     $two_line_word_wrap_ok =
-                      !$self->match_paren_flag( $KK, $flag );
+                      !$self->match_paren_control_flag( $seqno, $flag );
                 }
             }
         }
 
-        # Begin check for shortcut methods, which avoid treating a list
-        # as a table for relatively small parenthesized lists.  These
+        #-------------------------------------------------------------------
+        # Section C4: Check for shortcut methods, which avoid treating
+        # a list as a table for relatively small parenthesized lists.  These
         # are usually easier to read if not formatted as tables.
+        #-------------------------------------------------------------------
         if (
             $packed_lines <= 2           # probably can fit in 2 lines
             && $item_count < 9           # doesn't have too many items
             && $opening_is_in_block      # not a sub-container
             && $two_line_word_wrap_ok    # ok to wrap this paren list
-            ##&& $opening_token eq '('    # is paren list
           )
         {
 
-            # Shortcut method 1: for -lp and just one comma:
+            # Section C4A: Shortcut method 1: for -lp and just one comma:
             # This is a no-brainer, just break at the comma.
             if (
                 $is_lp_formatting      # -lp
@@ -20524,8 +22105,8 @@ EOM
 
             }
 
-            # method 2 is for most small ragged lists which might look
-            # best if not displayed as a table.
+            # Section C4B: Shortcut method 2 is for most small ragged lists
+            # which might look best if not displayed as a table.
             if (
                 ( $number_of_fields == 2 && $item_count == 3 )
                 || (
@@ -20535,7 +22116,7 @@ EOM
               )
             {
 
-                my $break_count = $self->set_ragged_breakpoints( \@i_term_comma,
+                my $break_count = $self->set_ragged_breakpoints( $ri_term_comma,
                     $ri_ragged_break_list );
                 ++$break_count if ($use_separate_first_term);
 
@@ -20563,15 +22144,15 @@ EOM
 
         };
 
-        #---------------------------------------------------------------
-        # Compound List Rule 2:
+        #------------------------------------------------------------------
+        # Section C5: Compound List Rule 2:
         # If this list is too long for one line, and it is an item of a
         # larger list, then we must format it, regardless of sparsity
         # (ian.t).  One reason that we have to do this is to trigger
         # Compound List Rule 1, above, which causes breaks at all commas of
         # all outer lists.  In this way, the structure will be properly
         # displayed.
-        #---------------------------------------------------------------
+        #------------------------------------------------------------------
 
         # Decide if this list is too long for one line unless broken
         my $total_columns = table_columns_available($i_opening_paren);
@@ -20587,7 +22168,7 @@ EOM
                 $i_effective_last_comma + 1 ) > 0;
         }
 
-        # FIXME: For an item after a '=>', try to include the length of the
+        # TODO: For an item after a '=>', try to include the length of the
         # thing before the '=>'.  This is crude and should be improved by
         # actually looking back token by token.
         if ( !$too_long && $i_opening_paren > 0 && $list_type eq '=>' ) {
@@ -20607,23 +22188,21 @@ EOM
 
 #print "LISTX: next=$next_nonblank_type  avail cols=$columns packed=$packed_columns must format = $must_break_open_container too-long=$too_long  opening=$opening_token list_type=$list_type formatted_lines=$formatted_lines  packed=$packed_lines max_sparsity= $max_allowed_sparsity sparsity=$sparsity \n";
 
-        #---------------------------------------------------------------
-        # The main decision:
-        # Now decide if we will align the data into aligned columns.  Do not
-        # attempt to align columns if this is a tiny table or it would be
-        # too spaced.  It seems that the more packed lines we have, the
-        # sparser the list that can be allowed and still look ok.
-        #---------------------------------------------------------------
+        #--------------------------------------------------------------------
+        # Section C6: A table will work here. But do not attempt to align
+        # columns if this is a tiny table or it would be too spaced.  It
+        # seems that the more packed lines we have, the sparser the list that
+        # can be allowed and still look ok.
+        #--------------------------------------------------------------------
 
         if (   ( $formatted_lines < 3 && $packed_lines < $formatted_lines )
             || ( $formatted_lines < 2 )
             || ( $unused_columns > $max_allowed_sparsity * $formatted_columns )
           )
         {
-
-            #---------------------------------------------------------------
-            # too sparse: would look ugly if aligned in a table;
-            #---------------------------------------------------------------
+            #----------------------------------------------------------------
+            # Section C6A: too sparse: would not look good aligned in a table
+            #----------------------------------------------------------------
 
             # use old breakpoints if this is a 'big' list
             if ( $packed_lines > 2 && $item_count > 10 ) {
@@ -20634,7 +22213,7 @@ EOM
             # let the continuation logic handle it if 2 lines
             else {
 
-                my $break_count = $self->set_ragged_breakpoints( \@i_term_comma,
+                my $break_count = $self->set_ragged_breakpoints( $ri_term_comma,
                     $ri_ragged_break_list );
                 ++$break_count if ($use_separate_first_term);
 
@@ -20650,9 +22229,82 @@ EOM
             return;
         }
 
-        #---------------------------------------------------------------
-        # go ahead and format as a table
-        #---------------------------------------------------------------
+        #--------------------------------------------
+        # Section C6B: Go ahead and format as a table
+        #--------------------------------------------
+        $self->write_formatted_table( $number_of_fields, $comma_count,
+            $rcomma_index, $use_separate_first_term );
+
+        return;
+    } ## end sub set_comma_breakpoints_final
+
+    sub lp_table_fix {
+
+        # try to undo some -lp indentation to improve table formatting
+
+        my (
+
+            $self,    #
+
+            $columns,
+            $i_first_comma,
+            $max_width,
+            $number_of_fields,
+            $number_of_fields_best,
+            $odd_or_even,
+            $pair_width,
+            $ritem_lengths,
+
+        ) = @_;
+
+        my $available_spaces =
+          $self->get_available_spaces_to_go($i_first_comma);
+        if ( $available_spaces > 0 ) {
+
+            my $spaces_wanted = $max_width - $columns;    # for 1 field
+
+            if ( $number_of_fields_best == 0 ) {
+                $number_of_fields_best =
+                  get_maximum_fields_wanted($ritem_lengths);
+            }
+
+            if ( $number_of_fields_best != 1 ) {
+                my $spaces_wanted_2 = 1 + $pair_width - $columns; # for 2 fields
+                if ( $available_spaces > $spaces_wanted_2 ) {
+                    $spaces_wanted = $spaces_wanted_2;
+                }
+            }
+
+            if ( $spaces_wanted > 0 ) {
+                my $deleted_spaces =
+                  $self->reduce_lp_indentation( $i_first_comma,
+                    $spaces_wanted );
+
+                # redo the math
+                if ( $deleted_spaces > 0 ) {
+                    $columns = table_columns_available($i_first_comma);
+                    $number_of_fields =
+                      maximum_number_of_fields( $columns, $odd_or_even,
+                        $max_width, $pair_width );
+
+                    if (   $number_of_fields_best == 1
+                        && $number_of_fields >= 1 )
+                    {
+                        $number_of_fields = $number_of_fields_best;
+                    }
+                }
+            }
+        }
+        return ( $number_of_fields, $number_of_fields_best, $columns );
+    } ## end sub lp_table_fix
+
+    sub write_formatted_table {
+
+        # Write a table of comma separated items with fixed number of fields
+        my ( $self, $number_of_fields, $comma_count, $rcomma_index,
+            $use_separate_first_term )
+          = @_;
+
         write_logfile_entry(
             "List: auto formatting with $number_of_fields fields/row\n");
 
@@ -20666,8 +22318,8 @@ EOM
             $j += $number_of_fields;
         }
         return;
-    } ## end sub set_comma_breakpoints_do
-} ## end closure set_comma_breakpoints_do
+    }
+} ## end closure set_comma_breakpoints_final
 
 sub study_list_complexity {
 
@@ -20750,7 +22402,7 @@ sub study_list_complexity {
                 && $i_last_last_break != $i - 2 )
             {
 
-                ## FIXME: don't strand a small term
+                ## TODO: don't strand a small term
                 pop @i_ragged_break_list;
                 push @i_ragged_break_list, $i - 2;
                 push @i_ragged_break_list, $i - 1;
@@ -20915,7 +22567,24 @@ sub copy_old_breakpoints {
     my ( $self, $i_first_comma, $i_last_comma ) = @_;
     for my $i ( $i_first_comma .. $i_last_comma ) {
         if ( $old_breakpoint_to_go[$i] ) {
-            $self->set_forced_breakpoint($i);
+
+            # If the comma style is under certain controls, and if this is a
+            # comma breakpoint with the comma is at the beginning of the next
+            # line, then we must pass that index instead. This will allow sub
+            # set_forced_breakpoints to check and follow the user settings. This
+            # produces a uniform style and can prevent instability (b1422).
+            #
+            # The flag '$controlled_comma_style' will be set if the user
+            # entered any of -wbb=',' -wba=',' -kbb=',' -kba=','.  It is not
+            # needed or set for the -boc flag.
+            my $ibreak = $i;
+            if ( $types_to_go[$ibreak] ne ',' && $controlled_comma_style ) {
+                my $index = $inext_to_go[$ibreak];
+                if ( $index > $ibreak && $types_to_go[$index] eq ',' ) {
+                    $ibreak = $index;
+                }
+            }
+            $self->set_forced_breakpoint($ibreak);
         }
     }
     return;
@@ -20936,11 +22605,12 @@ sub set_nobreaks {
 
     # shouldn't happen; non-critical error
     else {
-        0 && do {
+        if (DEVEL_MODE) {
             my ( $a, $b, $c ) = caller();
-            print STDOUT
-              "NOBREAK ERROR: from $a $c with i=$i j=$j max=$max_index_to_go\n";
-        };
+            Fault(<<EOM);
+NOBREAK ERROR: from $a $c with i=$i j=$j max=$max_index_to_go
+EOM
+        }
     }
     return;
 } ## end sub set_nobreaks
@@ -21052,9 +22722,6 @@ sub get_available_spaces_to_go {
     # an -lp indentation level.  This survives between batches.
     my $lp_position_predictor;
 
-    # A level at which the lp format becomes too highly stressed to continue
-    my $lp_cutoff_level;
-
     BEGIN {
 
         # Index names for the -lp stack variables.
@@ -21077,10 +22744,9 @@ sub get_available_spaces_to_go {
 
         $lp_position_predictor = 0;
         $max_lp_stack          = 0;
-        $lp_cutoff_level = min( $stress_level_alpha, $stress_level_beta + 2 );
 
         # we can turn off -lp if all levels will be at or above the cutoff
-        if ( $lp_cutoff_level <= 1 ) {
+        if ( $high_stress_level <= 1 ) {
             $rOpts_line_up_parentheses          = 0;
             $rOpts_extended_line_up_parentheses = 0;
         }
@@ -21113,40 +22779,57 @@ sub get_available_spaces_to_go {
         @hash_test3{@q} = (1) x scalar(@q);
     }
 
+    # shared variables, re-initialized for each batch
+    my $rlp_object_list;
+    my $max_lp_object_list;
+    my %lp_comma_count;
+    my %lp_arrow_count;
+    my $space_count;
+    my $current_level;
+    my $current_ci_level;
+    my $ii_begin_line;
+    my $in_lp_mode;
+    my $stack_changed;
+    my $K_last_nonblank;
+    my $last_nonblank_token;
+    my $last_nonblank_type;
+    my $last_last_nonblank_type;
+
     sub set_lp_indentation {
 
+        my ($self) = @_;
+
         #------------------------------------------------------------------
         # Define the leading whitespace for all tokens in the current batch
         # when the -lp formatting is selected.
         #------------------------------------------------------------------
 
-        my ($self) = @_;
-
         return unless ($rOpts_line_up_parentheses);
         return unless ( defined($max_index_to_go) && $max_index_to_go >= 0 );
 
         # List of -lp indentation objects created in this batch
-        my $rlp_object_list    = [];
-        my $max_lp_object_list = UNDEFINED_INDEX;
-
-        my %last_lp_equals;
-        my %lp_comma_count;
-        my %lp_arrow_count;
-        my $ii_begin_line = 0;
-
-        my $rLL                       = $self->[_rLL_];
-        my $Klimit                    = $self->[_Klimit_];
-        my $rbreak_container          = $self->[_rbreak_container_];
-        my $rshort_nested             = $self->[_rshort_nested_];
-        my $ris_excluded_lp_container = $self->[_ris_excluded_lp_container_];
-        my $rblock_type_of_seqno      = $self->[_rblock_type_of_seqno_];
-        my $starting_in_quote   = $self->[_this_batch_]->[_starting_in_quote_];
-        my $K_closing_container = $self->[_K_closing_container_];
-        my $rlp_object_by_seqno = $self->[_rlp_object_by_seqno_];
-        my $radjusted_levels    = $self->[_radjusted_levels_];
-        my $rbreak_before_container_by_seqno =
-          $self->[_rbreak_before_container_by_seqno_];
-        my $rcollapsed_length_by_seqno = $self->[_rcollapsed_length_by_seqno_];
+        $rlp_object_list    = [];
+        $max_lp_object_list = -1;
+
+        %lp_comma_count          = ();
+        %lp_arrow_count          = ();
+        $space_count             = undef;
+        $current_level           = undef;
+        $current_ci_level        = undef;
+        $ii_begin_line           = 0;
+        $in_lp_mode              = 0;
+        $stack_changed           = 1;
+        $K_last_nonblank         = undef;
+        $last_nonblank_token     = EMPTY_STRING;
+        $last_nonblank_type      = EMPTY_STRING;
+        $last_last_nonblank_type = EMPTY_STRING;
+
+        my %last_lp_equals = ();
+
+        my $rLL               = $self->[_rLL_];
+        my $Klimit            = $self->[_Klimit_];
+        my $starting_in_quote = $self->[_this_batch_]->[_starting_in_quote_];
+        my $radjusted_levels  = $self->[_radjusted_levels_];
 
         my $nws  = @{$radjusted_levels};
         my $imin = 0;
@@ -21159,7 +22842,6 @@ sub get_available_spaces_to_go {
             $imin += 1;
         }
 
-        my $K_last_nonblank;
         my $Kpnb = $K_to_go[0] - 1;
         if ( $Kpnb > 0 && $rLL->[$Kpnb]->[_TYPE_] eq 'b' ) {
             $Kpnb -= 1;
@@ -21168,38 +22850,35 @@ sub get_available_spaces_to_go {
             $K_last_nonblank = $Kpnb;
         }
 
-        my $last_nonblank_token     = EMPTY_STRING;
-        my $last_nonblank_type      = EMPTY_STRING;
-        my $last_last_nonblank_type = EMPTY_STRING;
-
         if ( defined($K_last_nonblank) ) {
             $last_nonblank_token = $rLL->[$K_last_nonblank]->[_TOKEN_];
             $last_nonblank_type  = $rLL->[$K_last_nonblank]->[_TYPE_];
         }
 
-        my ( $space_count, $current_level, $current_ci_level, $in_lp_mode );
-        my $stack_changed = 1;
-
         #-----------------------------------
         # Loop over all tokens in this batch
         #-----------------------------------
         foreach my $ii ( $imin .. $max_index_to_go ) {
 
-            my $KK              = $K_to_go[$ii];
-            my $type            = $types_to_go[$ii];
-            my $token           = $tokens_to_go[$ii];
-            my $level           = $levels_to_go[$ii];
-            my $ci_level        = $ci_levels_to_go[$ii];
-            my $total_depth     = $nesting_depth_to_go[$ii];
-            my $standard_spaces = $leading_spaces_to_go[$ii];
+            my $type        = $types_to_go[$ii];
+            my $token       = $tokens_to_go[$ii];
+            my $level       = $levels_to_go[$ii];
+            my $ci_level    = $ci_levels_to_go[$ii];
+            my $total_depth = $nesting_depth_to_go[$ii];
 
             #--------------------------------------------------
             # Adjust levels if necessary to recycle whitespace:
             #--------------------------------------------------
             if ( defined($radjusted_levels) && @{$radjusted_levels} == $Klimit )
             {
+                my $KK = $K_to_go[$ii];
                 $level = $radjusted_levels->[$KK];
-                if ( $level < 0 ) { $level = 0 }  # note: this should not happen
+                if ( $level < 0 ) {
+
+                    # should not happen
+                    DEVEL_MODE && Fault("unexpected level=$level\n");
+                    $level = 0;
+                }
             }
 
             # get the top state from the stack if it has changed
@@ -21213,523 +22892,44 @@ sub get_available_spaces_to_go {
                 else {
                     $current_ci_level = $rLP_top->[_lp_ci_level_];
                     $current_level    = $rLP_top->[_lp_level_];
-                    $space_count      = $rLP_top->[_lp_space_count_];
-                }
-                $stack_changed = 0;
-            }
-
-            #------------------------------
-            # update the position predictor
-            #------------------------------
-            if ( $type eq '{' || $type eq '(' ) {
-
-                $lp_comma_count{ $total_depth + 1 } = 0;
-                $lp_arrow_count{ $total_depth + 1 } = 0;
-
-                # If we come to an opening token after an '=' token of some
-                # type, see if it would be helpful to 'break' after the '=' to
-                # save space
-                my $last_equals = $last_lp_equals{$total_depth};
-
-                # Skip an empty set of parens, such as after channel():
-                #   my $exchange = $self->_channel()->exchange(
-                # This fixes issues b1318 b1322 b1323 b1328
-                # TODO: maybe also skip parens with just one token?
-                my $is_empty_container;
-                if ( $last_equals && $ii < $max_index_to_go ) {
-                    my $seqno    = $type_sequence_to_go[$ii];
-                    my $inext_nb = $ii + 1;
-                    $inext_nb++
-                      if ( $types_to_go[$inext_nb] eq 'b' );
-                    my $seqno_nb = $type_sequence_to_go[$inext_nb];
-                    $is_empty_container =
-                      $seqno && $seqno_nb && $seqno_nb == $seqno;
-                }
-
-                if (   $last_equals
-                    && $last_equals > $ii_begin_line
-                    && !$is_empty_container )
-                {
-
-                    my $seqno = $type_sequence_to_go[$ii];
-
-                    # find the position if we break at the '='
-                    my $i_test = $last_equals;
-
-                    # Fix for issue b1229, check for break before
-                    if ( $want_break_before{ $types_to_go[$i_test] } ) {
-                        if ( $i_test > 0 ) { $i_test-- }
-                    }
-                    elsif ( $types_to_go[ $i_test + 1 ] eq 'b' ) { $i_test++ }
-
-                    my $test_position = total_line_length( $i_test, $ii );
-                    my $mll =
-                      $maximum_line_length_at_level[ $levels_to_go[$i_test] ];
-
-                    #------------------------------------------------------
-                    # Break if structure will reach the maximum line length
-                    #------------------------------------------------------
-
-                    # Historically, -lp just used one-half line length here
-                    my $len_increase = $rOpts_maximum_line_length / 2;
-
-                    # For -xlp, we can also use the pre-computed lengths
-                    my $min_len = $rcollapsed_length_by_seqno->{$seqno};
-                    if ( $min_len && $min_len > $len_increase ) {
-                        $len_increase = $min_len;
-                    }
-
-                    if (
-
-                        # if we might exceed the maximum line length
-                        $lp_position_predictor + $len_increase > $mll
-
-                        # if a -bbx flag WANTS a break before this opening token
-                        || (   $seqno
-                            && $rbreak_before_container_by_seqno->{$seqno} )
-
-                        # or we are beyond the 1/4 point and there was an old
-                        # break at an assignment (not '=>') [fix for b1035]
-                        || (
-                            $lp_position_predictor >
-                            $mll - $rOpts_maximum_line_length * 3 / 4
-                            && $types_to_go[$last_equals] ne '=>'
-                            && (
-                                $old_breakpoint_to_go[$last_equals]
-                                || (   $last_equals > 0
-                                    && $old_breakpoint_to_go[ $last_equals - 1 ]
-                                )
-                                || (   $last_equals > 1
-                                    && $types_to_go[ $last_equals - 1 ] eq 'b'
-                                    && $old_breakpoint_to_go[ $last_equals - 2 ]
-                                )
-                            )
-                        )
-                      )
-                    {
-
-                        # then make the switch -- note that we do not set a
-                        # real breakpoint here because we may not really need
-                        # one; sub break_lists will do that if necessary.
-
-                        my $Kc = $K_closing_container->{$seqno};
-                        if (
-
-                            # For -lp, only if the closing token is in this
-                            # batch (c117).  Otherwise it cannot be done by sub
-                            # break_lists.
-                            defined($Kc) && $Kc <= $K_to_go[$max_index_to_go]
-
-                            # For -xlp, we only need one nonblank token after
-                            # the opening token.
-                            || $rOpts_extended_line_up_parentheses
-                          )
-                        {
-                            $ii_begin_line         = $i_test + 1;
-                            $lp_position_predictor = $test_position;
-
-                            #--------------------------------------------------
-                            # Fix for an opening container terminating a batch:
-                            #--------------------------------------------------
-                            # To get alignment of a -lp container with its
-                            # contents, we have to put a break after $i_test.
-                            # For $ii<$max_index_to_go, this will be done by
-                            # sub break_lists based on the indentation object.
-                            # But for $ii=$max_index_to_go, the indentation
-                            # object for this seqno will not be created until
-                            # the next batch, so we have to set a break at
-                            # $i_test right now in order to get one.
-                            if (   $ii == $max_index_to_go
-                                && !$block_type_to_go[$ii]
-                                && $type eq '{'
-                                && $seqno
-                                && !$ris_excluded_lp_container->{$seqno} )
-                            {
-                                $self->set_forced_lp_break( $ii_begin_line,
-                                    $ii );
-                            }
-                        }
-                    }
-                }
-            } ## end update position predictor
-
-            #------------------------
-            # Handle decreasing depth
-            #------------------------
-            # Note that one token may have both decreasing and then increasing
-            # depth. For example, (level, ci) can go from (1,1) to (2,0).  So,
-            # in this example we would first go back to (1,0) then up to (2,0)
-            # in a single call.
-            if ( $level < $current_level || $ci_level < $current_ci_level ) {
-
-                # loop to find the first entry at or completely below this level
-                while (1) {
-                    if ($max_lp_stack) {
-
-                        # save index of token which closes this level
-                        if ( $rLP->[$max_lp_stack]->[_lp_object_] ) {
-                            my $lp_object =
-                              $rLP->[$max_lp_stack]->[_lp_object_];
-
-                            $lp_object->set_closed($ii);
-
-                            my $comma_count = 0;
-                            my $arrow_count = 0;
-                            if ( $type eq '}' || $type eq ')' ) {
-                                $comma_count = $lp_comma_count{$total_depth};
-                                $arrow_count = $lp_arrow_count{$total_depth};
-                                $comma_count = 0 unless $comma_count;
-                                $arrow_count = 0 unless $arrow_count;
-                            }
-
-                            $lp_object->set_comma_count($comma_count);
-                            $lp_object->set_arrow_count($arrow_count);
-
-                            # Undo any extra indentation if we saw no commas
-                            my $available_spaces =
-                              $lp_object->get_available_spaces();
-                            my $K_start = $lp_object->get_K_begin_line();
-
-                            if (   $available_spaces > 0
-                                && $K_start >= $K_to_go[0]
-                                && ( $comma_count <= 0 || $arrow_count > 0 ) )
-                            {
-
-                                my $i = $lp_object->get_lp_item_index();
-
-                                # Safety check for a valid stack index. It
-                                # should be ok because we just checked that the
-                                # index K of the token associated with this
-                                # indentation is in this batch.
-                                if ( $i < 0 || $i > $max_lp_object_list ) {
-                                    if (DEVEL_MODE) {
-                                        my $lno = $rLL->[$KK]->[_LINE_INDEX_];
-                                        Fault(<<EOM);
-Program bug with -lp near line $lno.  Stack index i=$i should be >=0 and <= max=$max_lp_object_list
-EOM
-                                    }
-                                }
-                                else {
-                                    if ( $arrow_count == 0 ) {
-                                        $rlp_object_list->[$i]
-                                          ->permanently_decrease_available_spaces
-                                          ($available_spaces);
-                                    }
-                                    else {
-                                        $rlp_object_list->[$i]
-                                          ->tentatively_decrease_available_spaces
-                                          ($available_spaces);
-                                    }
-                                    foreach
-                                      my $j ( $i + 1 .. $max_lp_object_list )
-                                    {
-                                        $rlp_object_list->[$j]
-                                          ->decrease_SPACES($available_spaces);
-                                    }
-                                }
-                            }
-                        }
-
-                        # go down one level
-                        --$max_lp_stack;
-
-                        my $rLP_top = $rLP->[$max_lp_stack];
-                        my $ci_lev  = $rLP_top->[_lp_ci_level_];
-                        my $lev     = $rLP_top->[_lp_level_];
-                        my $spaces  = $rLP_top->[_lp_space_count_];
-                        if ( $rLP_top->[_lp_object_] ) {
-                            my $lp_obj = $rLP_top->[_lp_object_];
-                            ( $spaces, $lev, $ci_lev ) =
-                              @{ $lp_obj->get_spaces_level_ci() };
-                        }
-
-                        # stop when we reach a level at or below the current
-                        # level
-                        if ( $lev <= $level && $ci_lev <= $ci_level ) {
-                            $space_count      = $spaces;
-                            $current_level    = $lev;
-                            $current_ci_level = $ci_lev;
-                            last;
-                        }
-                    }
-
-                    # reached bottom of stack .. should never happen because
-                    # only negative levels can get here, and $level was forced
-                    # to be positive above.
-                    else {
-
-                        # non-fatal, keep going except in DEVEL_MODE
-                        if (DEVEL_MODE) {
-##program bug with -lp: stack_error. level=$level; lev=$lev; ci_level=$ci_level; ci_lev=$ci_lev; rerun with -nlp
-                            Fault(<<EOM);
-program bug with -lp: stack_error. level=$level; ci_level=$ci_level; rerun with -nlp
-EOM
-                        }
-                        last;
-                    }
-                }
-            } ## end decreasing depth
-
-            #------------------------
-            # handle increasing depth
-            #------------------------
-            if ( $level > $current_level || $ci_level > $current_ci_level ) {
-
-                $stack_changed = 1;
-
-                # Compute the standard incremental whitespace.  This will be
-                # the minimum incremental whitespace that will be used.  This
-                # choice results in a smooth transition between the gnu-style
-                # and the standard style.
-                my $standard_increment =
-                  ( $level - $current_level ) *
-                  $rOpts_indent_columns +
-                  ( $ci_level - $current_ci_level ) *
-                  $rOpts_continuation_indentation;
-
-                # Now we have to define how much extra incremental space
-                # ("$available_space") we want.  This extra space will be
-                # reduced as necessary when long lines are encountered or when
-                # it becomes clear that we do not have a good list.
-                my $available_spaces = 0;
-                my $align_seqno      = 0;
-
-                my $last_nonblank_seqno;
-                my $last_nonblank_block_type;
-                if ( defined($K_last_nonblank) ) {
-                    $last_nonblank_seqno =
-                      $rLL->[$K_last_nonblank]->[_TYPE_SEQUENCE_];
-                    $last_nonblank_block_type =
-                        $last_nonblank_seqno
-                      ? $rblock_type_of_seqno->{$last_nonblank_seqno}
-                      : undef;
-                }
-
-                $in_lp_mode = $rLP->[$max_lp_stack]->[_lp_object_];
-
-                #-----------------------------------------------
-                # Initialize indentation spaces on empty stack..
-                #-----------------------------------------------
-                if ( $max_lp_stack == 0 ) {
-                    $space_count = $level * $rOpts_indent_columns;
-                }
-
-                #----------------------------------------
-                # Add the standard space increment if ...
-                #----------------------------------------
-                elsif (
-
-                    # if this is a BLOCK, add the standard increment
-                    $last_nonblank_block_type
-
-                    # or if this is not a sequenced item
-                    || !$last_nonblank_seqno
-
-                    # or this container is excluded by user rules
-                    # or contains here-docs or multiline qw text
-                    || defined($last_nonblank_seqno)
-                    && $ris_excluded_lp_container->{$last_nonblank_seqno}
-
-                    # or if last nonblank token was not structural indentation
-                    || $last_nonblank_type ne '{'
-
-                    # and do not start -lp under stress .. fixes b1244, b1255
-                    || !$in_lp_mode && $level >= $lp_cutoff_level
-
-                  )
-                {
-
-                    # If we have entered lp mode, use the top lp object to get
-                    # the current indentation spaces because it may have
-                    # changed.  Fixes b1285, b1286.
-                    if ($in_lp_mode) {
-                        $space_count = $in_lp_mode->get_spaces();
-                    }
-                    $space_count += $standard_increment;
-                }
-
-                #---------------------------------------------------------------
-                # -lp mode: try to use space to the first non-blank level change
-                #---------------------------------------------------------------
-                else {
-
-                    # see how much space we have available
-                    my $test_space_count = $lp_position_predictor;
-                    my $excess           = 0;
-                    my $min_len =
-                      $rcollapsed_length_by_seqno->{$last_nonblank_seqno};
-                    my $next_opening_too_far;
-
-                    if ( defined($min_len) ) {
-                        $excess =
-                          $test_space_count +
-                          $min_len -
-                          $maximum_line_length_at_level[$level];
-                        if ( $excess > 0 ) {
-                            $test_space_count -= $excess;
-
-                            # will the next opening token be a long way out?
-                            $next_opening_too_far =
-                              $lp_position_predictor + $excess >
-                              $maximum_line_length_at_level[$level];
-                        }
-                    }
-
-                    my $rLP_top             = $rLP->[$max_lp_stack];
-                    my $min_gnu_indentation = $rLP_top->[_lp_space_count_];
-                    if ( $rLP_top->[_lp_object_] ) {
-                        $min_gnu_indentation =
-                          $rLP_top->[_lp_object_]->get_spaces();
-                    }
-                    $available_spaces =
-                      $test_space_count - $min_gnu_indentation;
-
-                    # Do not startup -lp indentation mode if no space ...
-                    # ... or if it puts the opening far to the right
-                    if ( !$in_lp_mode
-                        && ( $available_spaces <= 0 || $next_opening_too_far ) )
-                    {
-                        $space_count += $standard_increment;
-                        $available_spaces = 0;
-                    }
-
-                    # Use -lp mode
-                    else {
-                        $space_count = $test_space_count;
-
-                        $in_lp_mode = 1;
-                        if ( $available_spaces >= $standard_increment ) {
-                            $min_gnu_indentation += $standard_increment;
-                        }
-                        elsif ( $available_spaces > 1 ) {
-                            $min_gnu_indentation += $available_spaces + 1;
-                        }
-                        ##elsif ( $last_nonblank_token =~ /^[\{\[\(]$/ ) {
-                        elsif ( $is_opening_token{$last_nonblank_token} ) {
-                            if ( ( $tightness{$last_nonblank_token} < 2 ) ) {
-                                $min_gnu_indentation += 2;
-                            }
-                            else {
-                                $min_gnu_indentation += 1;
-                            }
-                        }
-                        else {
-                            $min_gnu_indentation += $standard_increment;
-                        }
-                        $available_spaces = $space_count - $min_gnu_indentation;
-
-                        if ( $available_spaces < 0 ) {
-                            $space_count      = $min_gnu_indentation;
-                            $available_spaces = 0;
-                        }
-                        $align_seqno = $last_nonblank_seqno;
-                    }
-                }
-
-                #-------------------------------------------
-                # update the state, but not on a blank token
-                #-------------------------------------------
-                if ( $type ne 'b' ) {
-
-                    if ( $rLP->[$max_lp_stack]->[_lp_object_] ) {
-                        $rLP->[$max_lp_stack]->[_lp_object_]->set_have_child(1);
-                        $in_lp_mode = 1;
-                    }
-
-                    #----------------------------------------
-                    # Create indentation object if in lp-mode
-                    #----------------------------------------
-                    ++$max_lp_stack;
-                    my $lp_object;
-                    if ($in_lp_mode) {
-
-                        # A negative level implies not to store the item in the
-                        # item_list
-                        my $lp_item_index = 0;
-                        if ( $level >= 0 ) {
-                            $lp_item_index = ++$max_lp_object_list;
-                        }
-
-                        my $K_begin_line = 0;
-                        if (   $ii_begin_line >= 0
-                            && $ii_begin_line <= $max_index_to_go )
-                        {
-                            $K_begin_line = $K_to_go[$ii_begin_line];
-                        }
-
-                        # Minor Fix: when creating indentation at a side
-                        # comment we don't know what the space to the actual
-                        # next code token will be.  We will allow a space for
-                        # sub correct_lp to move it in if necessary.
-                        if (   $type eq '#'
-                            && $max_index_to_go > 0
-                            && $align_seqno )
-                        {
-                            $available_spaces += 1;
-                        }
-
-                        $lp_object = Perl::Tidy::IndentationItem->new(
-                            spaces           => $space_count,
-                            level            => $level,
-                            ci_level         => $ci_level,
-                            available_spaces => $available_spaces,
-                            lp_item_index    => $lp_item_index,
-                            align_seqno      => $align_seqno,
-                            stack_depth      => $max_lp_stack,
-                            K_begin_line     => $K_begin_line,
-                            standard_spaces  => $standard_spaces,
-                        );
+                    $space_count      = $rLP_top->[_lp_space_count_];
+                }
+                $stack_changed = 0;
+            }
 
-                        DEBUG_LP && do {
-                            my $tok_beg = $rLL->[$K_begin_line]->[_TOKEN_];
-                            print STDERR <<EOM;
-DEBUG_LP: Created object at tok=$token type=$type for seqno $align_seqno level=$level ci=$ci_level spaces=$space_count avail=$available_spaces kbeg=$K_begin_line tokbeg=$tok_beg lp=$lp_position_predictor
-EOM
-                        };
+            #------------------------------------------------------------
+            # Break at a previous '=' if necessary to control line length
+            #------------------------------------------------------------
+            if ( $type eq '{' || $type eq '(' ) {
+                $lp_comma_count{ $total_depth + 1 } = 0;
+                $lp_arrow_count{ $total_depth + 1 } = 0;
 
-                        if ( $level >= 0 ) {
-                            $rlp_object_list->[$max_lp_object_list] =
-                              $lp_object;
-                        }
+                # If we come to an opening token after an '=' token of some
+                # type, see if it would be helpful to 'break' after the '=' to
+                # save space
+                my $ii_last_equals = $last_lp_equals{$total_depth};
+                if ($ii_last_equals) {
+                    $self->lp_equals_break_check( $ii, $ii_last_equals );
+                }
+            }
 
-                        ##if (   $last_nonblank_token =~ /^[\{\[\(]$/
-                        if (   $is_opening_token{$last_nonblank_token}
-                            && $last_nonblank_seqno )
-                        {
-                            $rlp_object_by_seqno->{$last_nonblank_seqno} =
-                              $lp_object;
-                        }
-                    }
+            #------------------------
+            # Handle decreasing depth
+            #------------------------
+            # Note that one token may have both decreasing and then increasing
+            # depth. For example, (level, ci) can go from (1,1) to (2,0).  So,
+            # in this example we would first go back to (1,0) then up to (2,0)
+            # in a single call.
+            if ( $level < $current_level || $ci_level < $current_ci_level ) {
+                $self->lp_decreasing_depth($ii);
+            }
 
-                    #------------------------------------
-                    # Store this indentation on the stack
-                    #------------------------------------
-                    $rLP->[$max_lp_stack]->[_lp_ci_level_] = $ci_level;
-                    $rLP->[$max_lp_stack]->[_lp_level_]    = $level;
-                    $rLP->[$max_lp_stack]->[_lp_object_]   = $lp_object;
-                    $rLP->[$max_lp_stack]->[_lp_container_seqno_] =
-                      $last_nonblank_seqno;
-                    $rLP->[$max_lp_stack]->[_lp_space_count_] = $space_count;
-
-                    # If the opening paren is beyond the half-line length, then
-                    # we will use the minimum (standard) indentation.  This will
-                    # help avoid problems associated with running out of space
-                    # near the end of a line.  As a result, in deeply nested
-                    # lists, there will be some indentations which are limited
-                    # to this minimum standard indentation. But the most deeply
-                    # nested container will still probably be able to shift its
-                    # parameters to the right for proper alignment, so in most
-                    # cases this will not be noticeable.
-                    if ( $available_spaces > 0 && $lp_object ) {
-                        my $halfway =
-                          $maximum_line_length_at_level[$level] -
-                          $rOpts_maximum_line_length / 2;
-                        $lp_object->tentatively_decrease_available_spaces(
-                            $available_spaces)
-                          if ( $space_count > $halfway );
-                    }
-                }
-            } ## end increasing depth
+            #------------------------
+            # handle increasing depth
+            #------------------------
+            if ( $level > $current_level || $ci_level > $current_ci_level ) {
+                $self->lp_increasing_depth($ii);
+            }
 
             #------------------
             # Handle all tokens
@@ -21757,73 +22957,75 @@ EOM
 
                 # this token might start a new line if ..
                 if (
+                    $ii > $ii_begin_line
 
-                    # this is the first nonblank token of the line
-                    $ii == 1 && $types_to_go[0] eq 'b'
+                    && (
 
-                    # or previous character was one of these:
-                    #  /^([\:\?\,f])$/
-                    || $hash_test2{$last_nonblank_type}
+                        # this is the first nonblank token of the line
+                        $ii == 1 && $types_to_go[0] eq 'b'
 
-                    # or previous character was opening and this is not closing
-                    || ( $last_nonblank_type eq '{' && $type ne '}' )
-                    || ( $last_nonblank_type eq '(' and $type ne ')' )
+                        # or previous character was one of these:
+                        #  /^([\:\?\,f])$/
+                        || $hash_test2{$last_nonblank_type}
 
-                    # or this token is one of these:
-                    #  /^([\.]|\|\||\&\&)$/
-                    || $hash_test3{$type}
+                        # or previous character was opening and this is not
+                        # closing
+                        || ( $last_nonblank_type eq '{' && $type ne '}' )
+                        || ( $last_nonblank_type eq '(' and $type ne ')' )
 
-                    # or this is a closing structure
-                    || (   $last_nonblank_type eq '}'
-                        && $last_nonblank_token eq $last_nonblank_type )
+                        # or this token is one of these:
+                        #  /^([\.]|\|\||\&\&)$/
+                        || $hash_test3{$type}
 
-                    # or previous token was keyword 'return'
-                    || (
-                        $last_nonblank_type eq 'k'
-                        && (   $last_nonblank_token eq 'return'
-                            && $type ne '{' )
-                    )
+                        # or this is a closing structure
+                        || (   $last_nonblank_type eq '}'
+                            && $last_nonblank_token eq $last_nonblank_type )
+
+                        # or previous token was keyword 'return'
+                        || (
+                            $last_nonblank_type eq 'k'
+                            && (   $last_nonblank_token eq 'return'
+                                && $type ne '{' )
+                        )
 
-                    # or starting a new line at certain keywords is fine
-                    || (   $type eq 'k'
-                        && $is_if_unless_and_or_last_next_redo_return{$token} )
+                        # or starting a new line at certain keywords is fine
+                        || ( $type eq 'k'
+                            && $is_if_unless_and_or_last_next_redo_return{
+                                $token} )
 
-                    # or this is after an assignment after a closing structure
-                    || (
-                        $is_assignment{$last_nonblank_type}
-                        && (
-                            # /^[\}\)\]]$/
-                            $hash_test1{$last_last_nonblank_type}
+                        # or this is after an assignment after a closing
+                        # structure
+                        || (
+                            $is_assignment{$last_nonblank_type}
+                            && (
+                                # /^[\}\)\]]$/
+                                $hash_test1{$last_last_nonblank_type}
 
-                            # and it is significantly to the right
-                            || $lp_position_predictor > (
-                                $maximum_line_length_at_level[$level] -
-                                  $rOpts_maximum_line_length / 2
+                                # and it is significantly to the right
+                                || $lp_position_predictor > (
+                                    $maximum_line_length_at_level[$level] -
+                                      $rOpts_maximum_line_length / 2
+                                )
                             )
                         )
                     )
                   )
                 {
-                    check_for_long_gnu_style_lines( $ii, $rlp_object_list );
+                    check_for_long_gnu_style_lines($ii);
                     $ii_begin_line = $ii;
 
                     # back up 1 token if we want to break before that type
                     # otherwise, we may strand tokens like '?' or ':' on a line
                     if ( $ii_begin_line > 0 ) {
-                        if ( $last_nonblank_type eq 'k' ) {
-
-                            if ( $want_break_before{$last_nonblank_token} ) {
-                                $ii_begin_line--;
-                            }
-                        }
-                        elsif ( $want_break_before{$last_nonblank_type} ) {
-                            $ii_begin_line--;
-                        }
+                        my $wbb =
+                            $last_nonblank_type eq 'k'
+                          ? $want_break_before{$last_nonblank_token}
+                          : $want_break_before{$last_nonblank_type};
+                        $ii_begin_line-- if ($wbb);
                     }
-                } ## end if ( $ii == 1 && $types_to_go...)
-
-                $K_last_nonblank = $KK;
+                }
 
+                $K_last_nonblank         = $K_to_go[$ii];
                 $last_last_nonblank_type = $last_nonblank_type;
                 $last_nonblank_type      = $type;
                 $last_nonblank_token     = $token;
             }
         } ## end loop over all tokens in this batch
 
-        undo_incomplete_lp_indentation($rlp_object_list)
+        undo_incomplete_lp_indentation()
           if ( !$rOpts_extended_line_up_parentheses );
 
         return;
     } ## end sub set_lp_indentation
 
+    sub lp_equals_break_check {
+
+        my ( $self, $ii, $ii_last_equals ) = @_;
+
+        # If we come to an opening token after an '=' token of some
+        # type, see if it would be helpful to 'break' after the '=' to
+        # save space.
+
+        # Given:
+        #   $ii = index of an opening token in the output batch
+        #   $ii_begin_line = index of token starting next output line
+        # Update:
+        #   $lp_position_predictor - updated position predictor
+        #   $ii_begin_line = updated starting token index
+
+        # Skip an empty set of parens, such as after channel():
+        #   my $exchange = $self->_channel()->exchange(
+        # This fixes issues b1318 b1322 b1323 b1328
+        my $is_empty_container;
+        if ( $ii_last_equals && $ii < $max_index_to_go ) {
+            my $seqno    = $type_sequence_to_go[$ii];
+            my $inext_nb = $ii + 1;
+            $inext_nb++
+              if ( $types_to_go[$inext_nb] eq 'b' );
+            my $seqno_nb = $type_sequence_to_go[$inext_nb];
+            $is_empty_container = $seqno && $seqno_nb && $seqno_nb == $seqno;
+        }
+
+        if (   $ii_last_equals
+            && $ii_last_equals > $ii_begin_line
+            && !$is_empty_container )
+        {
+
+            my $seqno = $type_sequence_to_go[$ii];
+
+            # find the position if we break at the '='
+            my $i_test = $ii_last_equals;
+
+            # Fix for issue b1229, check if want break before this token
+            # Fix for issue b1356, if i_test is a blank, the leading spaces may
+            #   be incorrect (if it was an interline blank).
+            # Fix for issue b1357 .. b1370, i_test must be prev nonblank
+            #   ( the ci value for blanks can vary )
+            # See also case b223
+            # Fix for issue b1371-b1374 : all of these and the above are fixed
+            # by simply backing up one index and setting the leading spaces of
+            # a blank equal to that of the equals.
+            if ( $want_break_before{ $types_to_go[$i_test] } ) {
+                $i_test -= 1;
+                $leading_spaces_to_go[$i_test] =
+                  $leading_spaces_to_go[$ii_last_equals]
+                  if ( $types_to_go[$i_test] eq 'b' );
+            }
+            elsif ( $types_to_go[ $i_test + 1 ] eq 'b' ) { $i_test++ }
+
+            my $test_position = total_line_length( $i_test, $ii );
+            my $mll = $maximum_line_length_at_level[ $levels_to_go[$i_test] ];
+
+            #------------------------------------------------------
+            # Break if structure will reach the maximum line length
+            #------------------------------------------------------
+
+            # Historically, -lp just used one-half line length here
+            my $len_increase = $rOpts_maximum_line_length / 2;
+
+            # For -xlp, we can also use the pre-computed lengths
+            my $min_len = $self->[_rcollapsed_length_by_seqno_]->{$seqno};
+            if ( $min_len && $min_len > $len_increase ) {
+                $len_increase = $min_len;
+            }
+
+            if (
+
+                # if we might exceed the maximum line length
+                $lp_position_predictor + $len_increase > $mll
+
+                # if a -bbx flag WANTS a break before this opening token
+                || (   $seqno
+                    && $self->[_rbreak_before_container_by_seqno_]->{$seqno} )
+
+                # or we are beyond the 1/4 point and there was an old
+                # break at an assignment (not '=>') [fix for b1035]
+                || (
+                    $lp_position_predictor >
+                    $mll - $rOpts_maximum_line_length * 3 / 4
+                    && $types_to_go[$ii_last_equals] ne '=>'
+                    && (
+                        $old_breakpoint_to_go[$ii_last_equals]
+                        || (   $ii_last_equals > 0
+                            && $old_breakpoint_to_go[ $ii_last_equals - 1 ] )
+                        || (   $ii_last_equals > 1
+                            && $types_to_go[ $ii_last_equals - 1 ] eq 'b'
+                            && $old_breakpoint_to_go[ $ii_last_equals - 2 ] )
+                    )
+                )
+              )
+            {
+
+                # then make the switch -- note that we do not set a
+                # real breakpoint here because we may not really need
+                # one; sub break_lists will do that if necessary.
+
+                my $Kc = $self->[_K_closing_container_]->{$seqno};
+                if (
+
+                    # For -lp, only if the closing token is in this
+                    # batch (c117).  Otherwise it cannot be done by sub
+                    # break_lists.
+                    defined($Kc) && $Kc <= $K_to_go[$max_index_to_go]
+
+                    # For -xlp, we only need one nonblank token after
+                    # the opening token.
+                    || $rOpts_extended_line_up_parentheses
+                  )
+                {
+                    $ii_begin_line         = $i_test + 1;
+                    $lp_position_predictor = $test_position;
+
+                    #--------------------------------------------------
+                    # Fix for an opening container terminating a batch:
+                    #--------------------------------------------------
+                    # To get alignment of a -lp container with its
+                    # contents, we have to put a break after $i_test.
+                    # For $ii<$max_index_to_go, this will be done by
+                    # sub break_lists based on the indentation object.
+                    # But for $ii=$max_index_to_go, the indentation
+                    # object for this seqno will not be created until
+                    # the next batch, so we have to set a break at
+                    # $i_test right now in order to get one.
+                    if (   $ii == $max_index_to_go
+                        && !$block_type_to_go[$ii]
+                        && $types_to_go[$ii] eq '{'
+                        && $seqno
+                        && !$self->[_ris_excluded_lp_container_]->{$seqno} )
+                    {
+                        $self->set_forced_lp_break( $ii_begin_line, $ii );
+                    }
+                }
+            }
+        }
+        return;
+    } ## end sub lp_equals_break_check
+
+    sub lp_decreasing_depth {
+        my ( $self, $ii ) = @_;
+
+        my $rLL = $self->[_rLL_];
+
+        my $level    = $levels_to_go[$ii];
+        my $ci_level = $ci_levels_to_go[$ii];
+
+        # loop to find the first entry at or completely below this level
+        while (1) {
+
+            # Be sure we have not hit the stack bottom - should never
+            # happen because only negative levels can get here, and
+            # $level was forced to be positive above.
+            if ( !$max_lp_stack ) {
+
+                # non-fatal, just keep going except in DEVEL_MODE
+                if (DEVEL_MODE) {
+                    Fault(<<EOM);
+program bug with -lp: stack_error. level=$level; ci_level=$ci_level; rerun with -nlp
+EOM
+                }
+                last;
+            }
+
+            # save index of token which closes this level
+            if ( $rLP->[$max_lp_stack]->[_lp_object_] ) {
+                my $lp_object = $rLP->[$max_lp_stack]->[_lp_object_];
+
+                $lp_object->set_closed($ii);
+
+                my $comma_count = 0;
+                my $arrow_count = 0;
+                my $type        = $types_to_go[$ii];
+                if ( $type eq '}' || $type eq ')' ) {
+                    my $total_depth = $nesting_depth_to_go[$ii];
+                    $comma_count = $lp_comma_count{$total_depth};
+                    $arrow_count = $lp_arrow_count{$total_depth};
+                    $comma_count = 0 unless $comma_count;
+                    $arrow_count = 0 unless $arrow_count;
+                }
+
+                $lp_object->set_comma_count($comma_count);
+                $lp_object->set_arrow_count($arrow_count);
+
+                # Undo any extra indentation if we saw no commas
+                my $available_spaces = $lp_object->get_available_spaces();
+                my $K_start          = $lp_object->get_K_begin_line();
+
+                if (   $available_spaces > 0
+                    && $K_start >= $K_to_go[0]
+                    && ( $comma_count <= 0 || $arrow_count > 0 ) )
+                {
+
+                    my $i = $lp_object->get_lp_item_index();
+
+                    # Safety check for a valid stack index. It
+                    # should be ok because we just checked that the
+                    # index K of the token associated with this
+                    # indentation is in this batch.
+                    if ( $i < 0 || $i > $max_lp_object_list ) {
+                        my $KK  = $K_to_go[$ii];
+                        my $lno = $rLL->[$KK]->[_LINE_INDEX_];
+                        DEVEL_MODE && Fault(<<EOM);
+Program bug with -lp near line $lno.  Stack index i=$i should be >=0 and <= max=$max_lp_object_list
+EOM
+                        last;
+                    }
+
+                    if ( $arrow_count == 0 ) {
+                        $rlp_object_list->[$i]
+                          ->permanently_decrease_available_spaces(
+                            $available_spaces);
+                    }
+                    else {
+                        $rlp_object_list->[$i]
+                          ->tentatively_decrease_available_spaces(
+                            $available_spaces);
+                    }
+                    foreach my $j ( $i + 1 .. $max_lp_object_list ) {
+                        $rlp_object_list->[$j]
+                          ->decrease_SPACES($available_spaces);
+                    }
+                }
+            }
+
+            # go down one level
+            --$max_lp_stack;
+
+            my $rLP_top = $rLP->[$max_lp_stack];
+            my $ci_lev  = $rLP_top->[_lp_ci_level_];
+            my $lev     = $rLP_top->[_lp_level_];
+            my $spaces  = $rLP_top->[_lp_space_count_];
+            if ( $rLP_top->[_lp_object_] ) {
+                my $lp_obj = $rLP_top->[_lp_object_];
+                ( $spaces, $lev, $ci_lev ) =
+                  @{ $lp_obj->get_spaces_level_ci() };
+            }
+
+            # stop when we reach a level at or below the current
+            # level
+            if ( $lev <= $level && $ci_lev <= $ci_level ) {
+                $space_count      = $spaces;
+                $current_level    = $lev;
+                $current_ci_level = $ci_lev;
+                last;
+            }
+        }
+        return;
+    } ## end sub lp_decreasing_depth
+
+    sub lp_increasing_depth {
+        my ( $self, $ii ) = @_;
+
+        my $rLL = $self->[_rLL_];
+
+        my $type     = $types_to_go[$ii];
+        my $level    = $levels_to_go[$ii];
+        my $ci_level = $ci_levels_to_go[$ii];
+
+        $stack_changed = 1;
+
+        # Compute the standard incremental whitespace.  This will be
+        # the minimum incremental whitespace that will be used.  This
+        # choice results in a smooth transition between the gnu-style
+        # and the standard style.
+        my $standard_increment =
+          ( $level - $current_level ) * $rOpts_indent_columns +
+          ( $ci_level - $current_ci_level ) * $rOpts_continuation_indentation;
+
+        # Now we have to define how much extra incremental space
+        # ("$available_space") we want.  This extra space will be
+        # reduced as necessary when long lines are encountered or when
+        # it becomes clear that we do not have a good list.
+        my $available_spaces = 0;
+        my $align_seqno      = 0;
+        my $K_extra_space;
+
+        my $last_nonblank_seqno;
+        my $last_nonblank_block_type;
+        if ( defined($K_last_nonblank) ) {
+            $last_nonblank_seqno = $rLL->[$K_last_nonblank]->[_TYPE_SEQUENCE_];
+            $last_nonblank_block_type =
+                $last_nonblank_seqno
+              ? $self->[_rblock_type_of_seqno_]->{$last_nonblank_seqno}
+              : undef;
+        }
+
+        $in_lp_mode = $rLP->[$max_lp_stack]->[_lp_object_];
+
+        #-----------------------------------------------
+        # Initialize indentation spaces on empty stack..
+        #-----------------------------------------------
+        if ( $max_lp_stack == 0 ) {
+            $space_count = $level * $rOpts_indent_columns;
+        }
+
+        #----------------------------------------
+        # Add the standard space increment if ...
+        #----------------------------------------
+        elsif (
+
+            # if this is a BLOCK, add the standard increment
+            $last_nonblank_block_type
+
+            # or if this is not a sequenced item
+            || !$last_nonblank_seqno
+
+            # or this container is excluded by user rules
+            # or contains here-docs or multiline qw text
+            || defined($last_nonblank_seqno)
+            && $self->[_ris_excluded_lp_container_]->{$last_nonblank_seqno}
+
+            # or if last nonblank token was not structural indentation
+            || $last_nonblank_type ne '{'
+
+            # and do not start -lp under stress .. fixes b1244, b1255
+            || !$in_lp_mode && $level >= $high_stress_level
+
+          )
+        {
+
+            # If we have entered lp mode, use the top lp object to get
+            # the current indentation spaces because it may have
+            # changed.  Fixes b1285, b1286.
+            if ($in_lp_mode) {
+                $space_count = $in_lp_mode->get_spaces();
+            }
+            $space_count += $standard_increment;
+        }
+
+        #---------------------------------------------------------------
+        # -lp mode: try to use space to the first non-blank level change
+        #---------------------------------------------------------------
+        else {
+
+            # see how much space we have available
+            my $test_space_count = $lp_position_predictor;
+            my $excess           = 0;
+            my $min_len =
+              $self->[_rcollapsed_length_by_seqno_]->{$last_nonblank_seqno};
+            my $next_opening_too_far;
+
+            if ( defined($min_len) ) {
+                $excess =
+                  $test_space_count +
+                  $min_len -
+                  $maximum_line_length_at_level[$level];
+                if ( $excess > 0 ) {
+                    $test_space_count -= $excess;
+
+                    # will the next opening token be a long way out?
+                    $next_opening_too_far =
+                      $lp_position_predictor + $excess >
+                      $maximum_line_length_at_level[$level];
+                }
+            }
+
+            my $rLP_top             = $rLP->[$max_lp_stack];
+            my $min_gnu_indentation = $rLP_top->[_lp_space_count_];
+            if ( $rLP_top->[_lp_object_] ) {
+                $min_gnu_indentation = $rLP_top->[_lp_object_]->get_spaces();
+            }
+            $available_spaces = $test_space_count - $min_gnu_indentation;
+
+            # Do not startup -lp indentation mode if no space ...
+            # ... or if it puts the opening far to the right
+            if ( !$in_lp_mode
+                && ( $available_spaces <= 0 || $next_opening_too_far ) )
+            {
+                $space_count += $standard_increment;
+                $available_spaces = 0;
+            }
+
+            # Use -lp mode
+            else {
+                $space_count = $test_space_count;
+
+                $in_lp_mode = 1;
+                if ( $available_spaces >= $standard_increment ) {
+                    $min_gnu_indentation += $standard_increment;
+                }
+                elsif ( $available_spaces > 1 ) {
+                    $min_gnu_indentation += $available_spaces + 1;
+
+                    # The "+1" space can cause mis-alignment if there is no
+                    # blank space between the opening paren and the next
+                    # nonblank token (i.e., -pt=2) and the container does not
+                    # get broken open.  So we will mark this token for later
+                    # space removal by sub 'xlp_tweak' if this container
+                    # remains intact (issue git #106).
+                    if (
+                        $type ne 'b'
+
+                        # Skip if the maximum line length is exceeded here
+                        && $excess <= 0
+
+                        # This is only for level changes, not ci level changes.
+                        # But note: this test is here out of caution but I have
+                        # not found a case where it is actually necessary.
+                        && $is_opening_token{$last_nonblank_token}
+
+                        # Be sure we are at consecutive nonblanks.  This test
+                        # should be true, but it guards against future coding
+                        # changes to level values assigned to blank spaces.
+                        && $ii > 0
+                        && $types_to_go[ $ii - 1 ] ne 'b'
+
+                      )
+                    {
+                        $K_extra_space = $K_to_go[$ii];
+                    }
+                }
+                elsif ( $is_opening_token{$last_nonblank_token} ) {
+                    if ( ( $tightness{$last_nonblank_token} < 2 ) ) {
+                        $min_gnu_indentation += 2;
+                    }
+                    else {
+                        $min_gnu_indentation += 1;
+                    }
+                }
+                else {
+                    $min_gnu_indentation += $standard_increment;
+                }
+                $available_spaces = $space_count - $min_gnu_indentation;
+
+                if ( $available_spaces < 0 ) {
+                    $space_count      = $min_gnu_indentation;
+                    $available_spaces = 0;
+                }
+                $align_seqno = $last_nonblank_seqno;
+            }
+        }
+
+        #-------------------------------------------
+        # update the state, but not on a blank token
+        #-------------------------------------------
+        if ( $type ne 'b' ) {
+
+            if ( $rLP->[$max_lp_stack]->[_lp_object_] ) {
+                $rLP->[$max_lp_stack]->[_lp_object_]->set_have_child(1);
+                $in_lp_mode = 1;
+            }
+
+            #----------------------------------------
+            # Create indentation object if in lp-mode
+            #----------------------------------------
+            ++$max_lp_stack;
+            my $lp_object;
+            if ($in_lp_mode) {
+
+                # A negative level implies not to store the item in the
+                # item_list
+                my $lp_item_index = 0;
+                if ( $level >= 0 ) {
+                    $lp_item_index = ++$max_lp_object_list;
+                }
+
+                my $K_begin_line = 0;
+                if (   $ii_begin_line >= 0
+                    && $ii_begin_line <= $max_index_to_go )
+                {
+                    $K_begin_line = $K_to_go[$ii_begin_line];
+                }
+
+                # Minor Fix: when creating indentation at a side
+                # comment we don't know what the space to the actual
+                # next code token will be.  We will allow a space for
+                # sub correct_lp to move it in if necessary.
+                if (   $type eq '#'
+                    && $max_index_to_go > 0
+                    && $align_seqno )
+                {
+                    $available_spaces += 1;
+                }
+
+                my $standard_spaces = $leading_spaces_to_go[$ii];
+                $lp_object = Perl::Tidy::IndentationItem->new(
+                    spaces           => $space_count,
+                    level            => $level,
+                    ci_level         => $ci_level,
+                    available_spaces => $available_spaces,
+                    lp_item_index    => $lp_item_index,
+                    align_seqno      => $align_seqno,
+                    stack_depth      => $max_lp_stack,
+                    K_begin_line     => $K_begin_line,
+                    standard_spaces  => $standard_spaces,
+                    K_extra_space    => $K_extra_space,
+                );
+
+                DEBUG_LP && do {
+                    my $tok_beg = $rLL->[$K_begin_line]->[_TOKEN_];
+                    my $token   = $tokens_to_go[$ii];
+                    print STDERR <<EOM;
+DEBUG_LP: Created object at tok=$token type=$type for seqno $align_seqno level=$level ci=$ci_level spaces=$space_count avail=$available_spaces kbeg=$K_begin_line tokbeg=$tok_beg lp=$lp_position_predictor
+EOM
+                };
+
+                if ( $level >= 0 ) {
+                    $rlp_object_list->[$max_lp_object_list] = $lp_object;
+                }
+
+                if (   $is_opening_token{$last_nonblank_token}
+                    && $last_nonblank_seqno )
+                {
+                    $self->[_rlp_object_by_seqno_]->{$last_nonblank_seqno} =
+                      $lp_object;
+                }
+            }
+
+            #------------------------------------
+            # Store this indentation on the stack
+            #------------------------------------
+            $rLP->[$max_lp_stack]->[_lp_ci_level_] = $ci_level;
+            $rLP->[$max_lp_stack]->[_lp_level_]    = $level;
+            $rLP->[$max_lp_stack]->[_lp_object_]   = $lp_object;
+            $rLP->[$max_lp_stack]->[_lp_container_seqno_] =
+              $last_nonblank_seqno;
+            $rLP->[$max_lp_stack]->[_lp_space_count_] = $space_count;
+
+            # If the opening paren is beyond the half-line length, then
+            # we will use the minimum (standard) indentation.  This will
+            # help avoid problems associated with running out of space
+            # near the end of a line.  As a result, in deeply nested
+            # lists, there will be some indentations which are limited
+            # to this minimum standard indentation. But the most deeply
+            # nested container will still probably be able to shift its
+            # parameters to the right for proper alignment, so in most
+            # cases this will not be noticeable.
+            if ( $available_spaces > 0 && $lp_object ) {
+                my $halfway =
+                  $maximum_line_length_at_level[$level] -
+                  $rOpts_maximum_line_length / 2;
+                $lp_object->tentatively_decrease_available_spaces(
+                    $available_spaces)
+                  if ( $space_count > $halfway );
+            }
+        }
+        return;
+    } ## end sub lp_increasing_depth
+
     sub check_for_long_gnu_style_lines {
 
         # look at the current estimated maximum line length, and
         # remove some whitespace if it exceeds the desired maximum
-        my ( $mx_index_to_go, $rlp_object_list ) = @_;
-
-        my $max_lp_object_list = @{$rlp_object_list} - 1;
+        my ($mx_index_to_go) = @_;
 
         # nothing can be done if no stack items defined for this line
         return if ( $max_lp_object_list < 0 );
@@ -21990,9 +23734,6 @@ EOM
         # was always done because it could cause problems otherwise, but recent
         # improvements allow fairly good results to be obtained by skipping
         # this step with the -xlp flag.
-        my ($rlp_object_list) = @_;
-
-        my $max_lp_object_list = @{$rlp_object_list} - 1;
 
         # nothing to do if no stack items defined for this line
         return if ( $max_lp_object_list < 0 );
@@ -22176,31 +23917,28 @@ sub convey_batch_to_vertical_aligner {
     # have been defined. Here we prepare the lines for passing to the vertical
     # aligner.  We do the following tasks:
     # - mark certain vertical alignment tokens, such as '=', in each line
-    # - make minor indentation adjustments
+    # - make final indentation adjustments
     # - do logical padding: insert extra blank spaces to help display certain
     #   logical constructions
+    # - send the line to the vertical aligner
+
+    my $rLL               = $self->[_rLL_];
+    my $Klimit            = $self->[_Klimit_];
+    my $ris_list_by_seqno = $self->[_ris_list_by_seqno_];
+    my $this_batch        = $self->[_this_batch_];
 
-    my $this_batch = $self->[_this_batch_];
-    my $ri_first   = $this_batch->[_ri_first_];
-    my $ri_last    = $this_batch->[_ri_last_];
+    my $do_not_pad              = $this_batch->[_do_not_pad_];
+    my $starting_in_quote       = $this_batch->[_starting_in_quote_];
+    my $ending_in_quote         = $this_batch->[_ending_in_quote_];
+    my $is_static_block_comment = $this_batch->[_is_static_block_comment_];
+    my $batch_CODE_type         = $this_batch->[_batch_CODE_type_];
+    my $ri_first                = $this_batch->[_ri_first_];
+    my $ri_last                 = $this_batch->[_ri_last_];
 
     $self->check_convey_batch_input( $ri_first, $ri_last ) if (DEVEL_MODE);
 
     my $n_last_line = @{$ri_first} - 1;
 
-    my $do_not_pad               = $this_batch->[_do_not_pad_];
-    my $peak_batch_size          = $this_batch->[_peak_batch_size_];
-    my $starting_in_quote        = $this_batch->[_starting_in_quote_];
-    my $ending_in_quote          = $this_batch->[_ending_in_quote_];
-    my $is_static_block_comment  = $this_batch->[_is_static_block_comment_];
-    my $rix_seqno_controlling_ci = $this_batch->[_rix_seqno_controlling_ci_];
-    my $batch_CODE_type          = $this_batch->[_batch_CODE_type_];
-
-    my $rLL                  = $self->[_rLL_];
-    my $Klimit               = $self->[_Klimit_];
-    my $rblock_type_of_seqno = $self->[_rblock_type_of_seqno_];
-    my $ris_list_by_seqno    = $self->[_ris_list_by_seqno_];
-
     my $ibeg_next = $ri_first->[0];
     my $iend_next = $ri_last->[0];
 
@@ -22208,29 +23946,37 @@ sub convey_batch_to_vertical_aligner {
     my $type_end_next  = $types_to_go[$iend_next];
     my $token_beg_next = $tokens_to_go[$ibeg_next];
 
-    my $is_block_comment = $max_index_to_go == 0 && $types_to_go[0] eq '#';
-
     my $rindentation_list = [0];    # ref to indentations for each line
-    my ( $cscw_block_comment, $closing_side_comment );
+    my ( $cscw_block_comment, $closing_side_comment, $is_block_comment );
+
+    if ( !$max_index_to_go && $type_beg_next eq '#' ) {
+        $is_block_comment = 1;
+    }
+
     if ($rOpts_closing_side_comments) {
         ( $closing_side_comment, $cscw_block_comment ) =
           $self->add_closing_side_comment( $ri_first, $ri_last );
     }
 
-    # flush before a long if statement to avoid unwanted alignment
-    if (   $n_last_line > 0
-        && $type_beg_next eq 'k'
-        && $is_if_unless{$token_beg_next} )
-    {
-        $self->flush_vertical_aligner();
+    if ( $n_last_line > 0 || $rOpts_extended_continuation_indentation ) {
+        $self->undo_ci( $ri_first, $ri_last,
+            $this_batch->[_rix_seqno_controlling_ci_] );
     }
 
-    $self->undo_ci( $ri_first, $ri_last, $rix_seqno_controlling_ci )
-      if ( $n_last_line > 0 || $rOpts_extended_continuation_indentation );
+    # for multi-line batches ...
+    if ( $n_last_line > 0 ) {
+
+        # flush before a long if statement to avoid unwanted alignment
+        $self->flush_vertical_aligner()
+          if ( $type_beg_next eq 'k'
+            && $is_if_unless{$token_beg_next} );
+
+        $self->set_logical_padding( $ri_first, $ri_last, $starting_in_quote )
+          if ($rOpts_logical_padding);
 
-    $self->set_logical_padding( $ri_first, $ri_last, $peak_batch_size,
-        $starting_in_quote )
-      if ( $n_last_line > 0 && $rOpts_logical_padding );
+        $self->xlp_tweak( $ri_first, $ri_last )
+          if ($rOpts_extended_line_up_parentheses);
+    }
 
     if (DEVEL_MODE) { $self->check_batch_summed_lengths() }
 
@@ -22243,7 +23989,7 @@ sub convey_batch_to_vertical_aligner {
     # ----------------------------------------------
     # loop to send each line to the vertical aligner
     # ----------------------------------------------
-    my ( $type_beg, $type_end, $token_beg );
+    my ( $type_beg, $type_end, $token_beg, $ljump );
 
     for my $n ( 0 .. $n_last_line ) {
 
@@ -22279,10 +24025,8 @@ sub convey_batch_to_vertical_aligner {
         my $Kend_code =
           $batch_CODE_type && $batch_CODE_type ne 'VER' ? undef : $Kend;
 
-        #  $ljump is a level jump needed by 'sub final_indentation_adjustment'
-        my $ljump = 0;
-
-        # Get some vars on line [n+1], if any:
+        # Get some vars on line [n+1], if any,
+        # and define $ljump = level jump needed by 'sub get_final_indentation'
         if ( $n < $n_last_line ) {
             $ibeg_next = $ri_first->[ $n + 1 ];
             $iend_next = $ri_last->[ $n + 1 ];
@@ -22311,6 +24055,9 @@ sub convey_batch_to_vertical_aligner {
             $ljump =
               $rLL->[$Kbeg_next]->[_LEVEL_] - $rLL->[$Kend]->[_LEVEL_];
         }
+        else {
+            $ljump = 0;
+        }
 
         # ---------------------------------------------
         # get the vertical alignment info for this line
@@ -22343,12 +24090,28 @@ EOM
         # --------------------------------------
         # get the final indentation of this line
         # --------------------------------------
-        my ( $indentation, $lev, $level_end, $terminal_type,
-            $terminal_block_type, $is_semicolon_terminated, $is_outdented_line )
-          = $self->final_indentation_adjustment( $ibeg, $iend, $rfields,
-            $rpatterns,         $ri_first, $ri_last,
-            $rindentation_list, $ljump,    $starting_in_quote,
-            $is_static_block_comment, );
+        my (
+
+            $indentation,
+            $lev,
+            $level_end,
+            $i_terminal,
+            $is_outdented_line,
+
+        ) = $self->get_final_indentation(
+
+            $ibeg,
+            $iend,
+            $rfields,
+            $rpatterns,
+            $ri_first,
+            $ri_last,
+            $rindentation_list,
+            $ljump,
+            $starting_in_quote,
+            $is_static_block_comment,
+
+        );
 
         # --------------------------------
         # define flag 'outdent_long_lines'
@@ -22415,7 +24178,7 @@ EOM
 
                 my $seqno_m = $rLL->[$Km]->[_TYPE_SEQUENCE_];
                 if ($seqno_m) {
-                    $block_type_m = $rblock_type_of_seqno->{$seqno_m};
+                    $block_type_m = $self->[_rblock_type_of_seqno_]->{$seqno_m};
                 }
             }
 
@@ -22442,7 +24205,8 @@ EOM
         $rvao_args->{rvertical_tightness_flags} =
           $self->set_vertical_tightness_flags( $n, $n_last_line, $ibeg, $iend,
             $ri_first, $ri_last, $ending_in_quote, $closing_side_comment )
-          if ( !$is_block_comment );
+          unless ( $is_block_comment
+            || $self->[_no_vertical_tightness_flags_] );
 
         # ----------------------------------
         # define 'is_terminal_ternary'  flag
@@ -22467,6 +24231,7 @@ EOM
 
             my $is_terminal_ternary = 0;
             my $last_leading_type   = $n > 0 ? $type_beg_last : ':';
+            my $terminal_type       = $types_to_go[$i_terminal];
             if (   $terminal_type ne ';'
                 && $n_last_line > $n
                 && $level_end == $lev )
@@ -22573,7 +24338,7 @@ EOM
         # This flag tells the vertical aligner to reset the side comment
         # location if we are entering a new block from level 0.  This is
         # intended to keep side comments from drifting too far to the right.
-        if (   $terminal_block_type
+        if (   $block_type_to_go[$i_terminal]
             && $nesting_depth_end > $nesting_depth_beg )
         {
             my $level_adj        = $lev;
@@ -22608,41 +24373,43 @@ EOM
 
         $do_not_pad = 0;
 
-        # Set flag indicating if this line ends in an opening
-        # token and is very short, so that a blank line is not
-        # needed if the subsequent line is a comment.
-        # Examples of what we are looking for:
-        #   {
-        #   && (
-        #   BEGIN {
-        #   default {
-        #   sub {
-        $self->[_last_output_short_opening_token_]
-
-          # line ends in opening token
-          #              /^[\{\(\[L]$/
-          = $is_opening_type{$type_end}
-
-          # and either
-          && (
-            # line has either single opening token
-            $Kend == $Kbeg
-
-            # or is a single token followed by opening token.
-            # Note that sub identifiers have blanks like 'sub doit'
-            #                                 $token_beg !~ /\s+/
-            || ( $Kend - $Kbeg <= 2 && index( $token_beg, SPACE ) < 0 )
-          )
+    } ## end of loop to output each line
 
-          # and limit total to 10 character widths
-          && token_sequence_length( $ibeg, $iend ) <= 10;
+    # Set flag indicating if the last line ends in an opening
+    # token and is very short, so that a blank line is not
+    # needed if the subsequent line is a comment.
+    # Examples of what we are looking for:
+    #   {
+    #   && (
+    #   BEGIN {
+    #   default {
+    #   sub {
+    $self->[_last_output_short_opening_token_]
+
+      # line ends in opening token
+      #              /^[\{\(\[L]$/
+      = $is_opening_type{$type_end}
+
+      # and either
+      && (
+        # line has either single opening token
+        $iend_next == $ibeg_next
+
+        # or is a single token followed by opening token.
+        # Note that sub identifiers have blanks like 'sub doit'
+        #                                 $token_beg !~ /\s+/
+        || ( $iend_next - $ibeg_next <= 2 && index( $token_beg, SPACE ) < 0 )
+      )
 
-    } ## end of loop to output each line
+      # and limit total to 10 character widths
+      && token_sequence_length( $ibeg_next, $iend_next ) <= 10;
 
     # remember indentation of lines containing opening containers for
-    # later use by sub final_indentation_adjustment
-    $self->save_opening_indentation( $ri_first, $ri_last, $rindentation_list )
-      if ( !$is_block_comment );
+    # later use by sub get_final_indentation
+    $self->save_opening_indentation( $ri_first, $ri_last,
+        $rindentation_list, $this_batch->[_runmatched_opening_indexes_] )
+      if ( $this_batch->[_runmatched_opening_indexes_]
+        || $types_to_go[$max_index_to_go] eq 'q' );
 
     # output any new -cscw block comment
     if ($cscw_block_comment) {
@@ -22661,7 +24428,7 @@ sub check_batch_summed_lengths {
 
     # Verify that the summed lengths are correct. We want to be sure that
     # errors have not been introduced by programming changes.  Summed lengths
-    # are defined in sub $store_token.  Operations like padding and unmasking
+    # are defined in sub store_token.  Operations like padding and unmasking
     # semicolons can change token lengths, but those operations are expected to
     # update the summed lengths when they make changes.  So the summed lengths
     # should always be correct.
@@ -22727,16 +24494,21 @@ EOM
 
     sub set_vertical_alignment_markers {
 
-        # This routine takes the first step toward vertical alignment of the
-        # lines of output text.  It looks for certain tokens which can serve as
-        # vertical alignment markers (such as an '=').
-        #
+        my ( $self, $ri_first, $ri_last ) = @_;
+
+        #----------------------------------------------------------------------
+        # This routine looks at output lines for certain tokens which can serve
+        # as vertical alignment markers (such as an '=').
+        #----------------------------------------------------------------------
+
+        # Input parameters:
+        #   $ri_first = ref to list of starting line indexes in _to_go arrays
+        #   $ri_last  = ref to list of ending line indexes in _to_go arrays
+
         # Method: We look at each token $i in this output batch and set
         # $ralignment_type_to_go->[$i] equal to those tokens at which we would
         # accept vertical alignment.
 
-        my ( $self, $ri_first, $ri_last ) = @_;
-
         my $ralignment_type_to_go;
         my $ralignment_counts       = [];
         my $ralignment_hash_by_line = [];
@@ -23036,25 +24808,25 @@ EOM
                         $alignment_type = EMPTY_STRING;
                     }
 
-                    # For a paren after keyword, only align something like this:
-                    #    if    ( $a ) { &a }
-                    #    elsif ( $b ) { &b }
                     if ( $token eq '(' ) {
 
-                        if ( $vert_last_nonblank_type eq 'k' ) {
-                            $alignment_type = EMPTY_STRING
-                              unless
-                              $is_if_unless_elsif{$vert_last_nonblank_token};
-                            ##unless $vert_last_nonblank_token =~ /^(if|unless|elsif)$/;
+                        # For a paren after keyword, only align if-like parens,
+                        # such as:
+                        #    if    ( $a ) { &a }
+                        #    elsif ( $b ) { &b }
+                        #          ^-------------------aligned parens
+                        if ( $vert_last_nonblank_type eq 'k'
+                            && !$is_if_unless_elsif{$vert_last_nonblank_token} )
+                        {
+                            $alignment_type = EMPTY_STRING;
                         }
 
                         # Do not align a spaced-function-paren if requested.
                         # Issue git #53, #73.
                         if ( !$rOpts_function_paren_vertical_alignment ) {
                             my $seqno = $type_sequence_to_go[$i];
-                            if ( $ris_function_call_paren->{$seqno} ) {
-                                $alignment_type = EMPTY_STRING;
-                            }
+                            $alignment_type = EMPTY_STRING
+                              if ( $ris_function_call_paren->{$seqno} );
                         }
 
                         # make () align with qw in a 'use' statement (git #93)
@@ -23063,6 +24835,11 @@ EOM
                             && $mate_index_to_go[$i] == $i + 1 )
                         {
                             $alignment_type = 'q';
+
+                            ## Note on discussion git #101. We could make this
+                            ## a separate type '()' to separate it from qw's:
+                            ## $alignment_type =
+                            ##  $rOpts_valign_empty_parens_with_qw ? 'q' : '()';
                         }
                     }
 
@@ -23089,18 +24866,12 @@ EOM
                 # because it may occur in short blocks).
                 elsif (
 
-                    # we haven't already set it
-                    ##!$alignment_type
-
                     # previous token IS one of these:
                     (
                            $vert_last_nonblank_type eq ','
                         || $vert_last_nonblank_type eq ';'
                     )
 
-                    # and its not the first token of the line
-                    ## && $i > $ibeg
-
                     # and it follows a blank
                     && $types_to_go[ $i - 1 ] eq 'b'
 
@@ -23190,8 +24961,16 @@ sub make_vertical_alignments {
     #---------------------------------------------------------
     # Step 1: Define the alignment tokens for the entire batch
     #---------------------------------------------------------
-    my ( $ralignment_type_to_go, $ralignment_counts, $ralignment_hash_by_line )
-      = $self->set_vertical_alignment_markers( $ri_first, $ri_last );
+    my ( $ralignment_type_to_go, $ralignment_counts, $ralignment_hash_by_line );
+
+    # We only need to make this call if vertical alignment of code is
+    # requested or if a line might have a side comment.
+    if (   $rOpts_valign_code
+        || $types_to_go[$max_index_to_go] eq '#' )
+    {
+        ( $ralignment_type_to_go, $ralignment_counts, $ralignment_hash_by_line )
+          = $self->set_vertical_alignment_markers( $ri_first, $ri_last );
+    }
 
     #----------------------------------------------
     # Step 2: Break each line into alignment fields
@@ -23254,7 +25033,6 @@ sub get_seqno {
         # Undo continuation indentation in certain sequences
         my ( $self, $ri_first, $ri_last, $rix_seqno_controlling_ci ) = @_;
         my ( $line_1, $line_2, $lev_last );
-        my $this_line_is_semicolon_terminated;
         my $max_line = @{$ri_first} - 1;
 
         my $rseqno_controlling_my_ci = $self->[_rseqno_controlling_my_ci_];
@@ -23340,20 +25118,21 @@ sub get_seqno {
 
                             # chain continues...
                             # check for chain ending at end of a statement
-                            if ( $line == $max_line ) {
+                            my $is_semicolon_terminated = (
+                                $line == $max_line
+                                  && (
+                                    $types_to_go[$iend] eq ';'
 
-                                # see of this line ends a statement
-                                $this_line_is_semicolon_terminated =
-                                  $types_to_go[$iend] eq ';'
+                                    # with possible side comment
+                                    || (   $types_to_go[$iend] eq '#'
+                                        && $iend - $ibeg >= 2
+                                        && $types_to_go[ $iend - 2 ] eq ';'
+                                        && $types_to_go[ $iend - 1 ] eq 'b' )
+                                  )
+                            );
 
-                                  # with possible side comment
-                                  || ( $types_to_go[$iend] eq '#'
-                                    && $iend - $ibeg >= 2
-                                    && $types_to_go[ $iend - 2 ] eq ';'
-                                    && $types_to_go[ $iend - 1 ] eq 'b' );
-                            }
                             $line_2 = $line
-                              if ($this_line_is_semicolon_terminated);
+                              if ($is_semicolon_terminated);
                         }
                         else {
 
@@ -23404,7 +25183,7 @@ sub get_seqno {
             # SECTION 2: Undo ci at cuddled blocks
             #-------------------------------------
 
-            # Note that sub final_indentation_adjustment will be called later to
+            # Note that sub get_final_indentation will be called later to
             # actually do this, but for now we will tentatively mark cuddled
             # lines with ci=0 so that the the -xci loop which follows will be
             # correct at cuddles.
@@ -23421,7 +25200,14 @@ sub get_seqno {
                         $terminal_type = $types_to_go[ $iend - 2 ];
                     }
                 }
-                if ( $terminal_type eq '{' ) {
+
+                # Patch for rt144979, part 2. Coordinated with part 1.
+                # Skip cuddled braces.
+                my $seqno_beg                = $type_sequence_to_go[$ibeg];
+                my $is_cuddled_closing_brace = $seqno_beg
+                  && $self->[_ris_cuddled_closing_brace_]->{$seqno_beg};
+
+                if ( $terminal_type eq '{' && !$is_cuddled_closing_brace ) {
                     my $Kbeg = $K_to_go[$ibeg];
                     $ci_levels_to_go[$ibeg] = 0;
                 }
@@ -23491,8 +25277,7 @@ sub get_seqno {
         #           &Error_OutOfRange;
         #       }
         #
-        my ( $self, $ri_first, $ri_last, $peak_batch_size, $starting_in_quote )
-          = @_;
+        my ( $self, $ri_first, $ri_last, $starting_in_quote ) = @_;
         my $max_line = @{$ri_first} - 1;
 
         my ( $ibeg, $ibeg_next, $ibegm, $iend, $iendm, $ipad, $pad_spaces,
@@ -23684,36 +25469,32 @@ sub get_seqno {
                  # : $i == 2 ? ( "Then",  "Rarity" )
                  # :           ( "Then",  "Name" );
 
-                        if ( $max_line > 1 ) {
-                            my $leading_token = $tokens_to_go[$ibeg_next];
-                            my $tokens_differ;
-
-                            # never indent line 1 of a '.' series because
-                            # previous line is most likely at same level.
-                            # TODO: we should also look at the leading_spaces
-                            # of the last output line and skip if it is same
-                            # as this line.
-                            next if ( $leading_token eq '.' );
-
-                            my $count = 1;
-                            foreach my $l ( 2 .. 3 ) {
-                                last if ( $line + $l > $max_line );
-                                my $ibeg_next_next = $ri_first->[ $line + $l ];
-                                if ( $tokens_to_go[$ibeg_next_next] ne
-                                    $leading_token )
-                                {
-                                    $tokens_differ = 1;
-                                    last;
-                                }
-                                $count++;
-                            }
-                            next if ($tokens_differ);
-                            next if ( $count < 3 && $leading_token ne ':' );
-                            $ipad = $ibeg;
-                        }
-                        else {
-                            next;
+                        next if ( $max_line <= 1 );
+
+                        my $leading_token = $tokens_to_go[$ibeg_next];
+                        my $tokens_differ;
+
+                        # never indent line 1 of a '.' series because
+                        # previous line is most likely at same level.
+                        # TODO: we should also look at the leading_spaces
+                        # of the last output line and skip if it is same
+                        # as this line.
+                        next if ( $leading_token eq '.' );
+
+                        my $count = 1;
+                        foreach my $l ( 2 .. 3 ) {
+                            last if ( $line + $l > $max_line );
+                            $count++;
+                            my $ibeg_next_next = $ri_first->[ $line + $l ];
+                            next
+                              if ( $tokens_to_go[$ibeg_next_next] eq
+                                $leading_token );
+                            $tokens_differ = 1;
+                            last;
                         }
+                        next if ($tokens_differ);
+                        next if ( $count < 3 && $leading_token ne ':' );
+                        $ipad = $ibeg;
                     }
                 }
             }
@@ -23752,26 +25533,10 @@ sub get_seqno {
             # an editor.  In that case either the user will see and
             # fix the problem or it will be corrected next time the
             # entire file is processed with perltidy.
+            my $this_batch      = $self->[_this_batch_];
+            my $peak_batch_size = $this_batch->[_peak_batch_size_];
             next if ( $ipad == 0 && $peak_batch_size <= 1 );
 
-## THIS PATCH REMOVES THE FOLLOWING POOR PADDING (math.t) with -pbp, BUT
-## IT DID MORE HARM THAN GOOD
-##            ceil(
-##                      $font->{'loca'}->{'glyphs'}[$x]->read->{'xMin'} * 1000
-##                    / $upem
-##            ),
-##            # do not put leading padding for just 2 lines of math
-##            if (   $ipad == $ibeg
-##                && $line > 0
-##                && $levels_to_go[$ipad] > $levels_to_go[ $ipad - 1 ]
-##                && $is_math_op{$type_next}
-##                && $line + 2 <= $max_line )
-##            {
-##                my $ibeg_next_next = $ri_first->[ $line + 2 ];
-##                my $type_next_next = $types_to_go[$ibeg_next_next];
-##                next if !$is_math_op{$type_next_next};
-##            }
-
             # next line must not be at greater depth
             my $iend_next = $ri_last->[ $line + 1 ];
             next
@@ -24002,6 +25767,9 @@ sub pad_token {
         $tok = SPACE x $pad_spaces . $tok;
         $tok_len += $pad_spaces;
     }
+    elsif ( $pad_spaces == 0 ) {
+        return;
+    }
     elsif ( $pad_spaces == -1 && $tokens_to_go[$ipad] eq SPACE ) {
         $tok     = EMPTY_STRING;
         $tok_len = 0;
@@ -24009,6 +25777,8 @@ sub pad_token {
     else {
 
         # shouldn't happen
+        DEVEL_MODE
+          && Fault("unexpected request for pad spaces = $pad_spaces\n");
         return;
     }
 
@@ -24024,6 +25794,64 @@ sub pad_token {
     return;
 } ## end sub pad_token
 
+sub xlp_tweak {
+
+    # Remove one indentation space from unbroken containers marked with
+    # 'K_extra_space'.  These are mostly two-line lists with short names
+    # formatted with -xlp -pt=2.
+    #
+    # Before this fix (extra space in line 2):
+    #    is($module->VERSION, $expected,
+    #        "$main_module->VERSION matches $module->VERSION ($expected)");
+    #
+    # After this fix:
+    #    is($module->VERSION, $expected,
+    #       "$main_module->VERSION matches $module->VERSION ($expected)");
+    #
+    # Notes:
+    #  - This fixes issue git #106
+    #  - This must be called after 'set_logical_padding'.
+    #  - This is currently only applied to -xlp. It would also work for -lp
+    #    but that style is essentially frozen.
+
+    my ( $self, $ri_first, $ri_last ) = @_;
+
+    # Must be 2 or more lines
+    return unless ( @{$ri_first} > 1 );
+
+    # Pull indentation object from start of second line
+    my $ibeg_1    = $ri_first->[1];
+    my $lp_object = $leading_spaces_to_go[$ibeg_1];
+    return if ( !ref($lp_object) );
+
+    # This only applies to an indentation object with a marked token
+    my $K_extra_space = $lp_object->get_K_extra_space();
+    return unless ($K_extra_space);
+
+    # Look for the marked token within the first line of this batch
+    my $ibeg_0 = $ri_first->[0];
+    my $iend_0 = $ri_last->[0];
+    my $ii     = $ibeg_0 + $K_extra_space - $K_to_go[$ibeg_0];
+    return if ( $ii <= $ibeg_0 || $ii > $iend_0 );
+
+    # Skip padded tokens, they have already been aligned
+    my $tok = $tokens_to_go[$ii];
+    return if ( substr( $tok, 0, 1 ) eq SPACE );
+
+    # Skip 'if'-like statements, this does not improve them
+    return
+      if ( $types_to_go[$ibeg_0] eq 'k'
+        && $is_if_unless_elsif{ $tokens_to_go[$ibeg_0] } );
+
+    # Looks okay, reduce indentation by 1 space if possible
+    my $spaces = $lp_object->get_spaces();
+    if ( $spaces > 0 ) {
+        $lp_object->decrease_SPACES(1);
+    }
+
+    return;
+}
+
 {    ## begin closure make_alignment_patterns
 
     my %keyword_map;
@@ -24093,8 +25921,8 @@ sub pad_token {
         @{is_binary_type}{@q} = (1) x scalar(@q);
 
         # token keywords which prevent using leading word as a container name
-        @_ = qw(and or err eq ne cmp);
-        @is_binary_keyword{@_} = (1) x scalar(@_);
+        @q = qw(and or err eq ne cmp);
+        @is_binary_keyword{@q} = (1) x scalar(@q);
 
         # Some common function calls whose args can be aligned.  These do not
         # give good alignments if the lengths differ significantly.
@@ -24108,11 +25936,26 @@ sub pad_token {
 
     sub make_alignment_patterns {
 
-        # Here we do some important preliminary work for the
-        # vertical aligner.  We create four arrays for one
-        # output line. These arrays contain strings that can
-        # be tested by the vertical aligner to see if
-        # consecutive lines can be aligned vertically.
+        my ( $self, $ibeg, $iend, $ralignment_type_to_go, $alignment_count,
+            $ralignment_hash )
+          = @_;
+
+        #------------------------------------------------------------------
+        # This sub creates arrays of vertical alignment info for one output
+        # line.
+        #------------------------------------------------------------------
+
+        # Input parameters:
+        #  $ibeg, $iend - index range of this line in the _to_go arrays
+        #  $ralignment_type_to_go - alignment type of tokens, like '=', if any
+        #  $alignment_count - number of alignment tokens in the line
+        #  $ralignment_hash - this contains all of the alignments for this
+        #    line.  It is not yet used but is available for future coding in
+        #    case there is a need to do a preliminary scan of alignment tokens.
+
+        # The arrays which are created contain strings that can be tested by
+        # the vertical aligner to see if consecutive lines can be aligned
+        # vertically.
         #
         # The four arrays are indexed on the vertical
         # alignment fields and are:
@@ -24129,13 +25972,6 @@ sub pad_token {
         #   allowed, even when the alignment tokens match.
         # @field_lengths - the display width of each field
 
-        my ( $self, $ibeg, $iend, $ralignment_type_to_go, $alignment_count,
-            $ralignment_hash )
-          = @_;
-
-        # The var $ralignment_hash contains all of the alignments for this
-        # line.  It is not yet used but is available for future coding in case
-        # there is a need to do a preliminary scan of the alignment tokens.
         if (DEVEL_MODE) {
             my $new_count = 0;
             if ( defined($ralignment_hash) ) {
@@ -24177,6 +26013,8 @@ sub pad_token {
 
         my $i_start        = $ibeg;
         my $depth          = 0;
+        my $i_depth_prev   = $i_start;
+        my $depth_prev     = $depth;
         my %container_name = ( 0 => EMPTY_STRING );
 
         my @tokens        = ();
@@ -24207,95 +26045,36 @@ sub pad_token {
                 && !$is_my_local_our{ $tokens_to_go[$ibeg] }
                 && $levels_to_go[$ibeg] eq $levels_to_go[$iterm] )
             {
-
-                # Make a container name by combining all leading barewords,
-                # keywords and functions.
-                my $name  = EMPTY_STRING;
-                my $count = 0;
-                my $count_max;
-                my $iname_end;
-                my $ilast_blank;
-                for ( $ibeg .. $iterm ) {
-                    my $type = $types_to_go[$_];
-
-                    if ( $type eq 'b' ) {
-                        $ilast_blank = $_;
-                        next;
-                    }
-
-                    my $token = $tokens_to_go[$_];
-
-                    # Give up if we find an opening paren, binary operator or
-                    # comma within or after the proposed container name.
-                    if (   $token eq '('
-                        || $is_binary_type{$type}
-                        || $type eq 'k' && $is_binary_keyword{$token} )
-                    {
-                        $name = EMPTY_STRING;
-                        last;
-                    }
-
-                    # The container name is only built of certain types:
-                    last if ( !$is_kwU{$type} );
-
-                    # Normally it is made of one word, but two words for 'use'
-                    if ( $count == 0 ) {
-                        if (   $type eq 'k'
-                            && $is_use_like{ $tokens_to_go[$_] } )
-                        {
-                            $count_max = 2;
-                        }
-                        else {
-                            $count_max = 1;
-                        }
-                    }
-                    elsif ( defined($count_max) && $count >= $count_max ) {
-                        last;
-                    }
-
-                    if ( defined( $name_map{$token} ) ) {
-                        $token = $name_map{$token};
-                    }
-
-                    $name .= SPACE . $token;
-                    $iname_end = $_;
-                    $count++;
-                }
-
-                # Require a space after the container name token(s)
-                if (   $name
-                    && defined($ilast_blank)
-                    && $ilast_blank > $iname_end )
-                {
-                    $name = substr( $name, 1 );
-                    $container_name{'0'} = $name;
-                }
+                $container_name{'0'} =
+                  make_uncontained_comma_name( $iterm, $ibeg, $iend );
             }
         }
 
-        # --------------------
-        # Loop over all tokens
-        # --------------------
+        #--------------------------------
+        # Begin main loop over all tokens
+        #--------------------------------
         my $j = 0;    # field index
 
         $patterns[0] = EMPTY_STRING;
         my %token_count;
         for my $i ( $ibeg .. $iend ) {
 
-            # Keep track of containers balanced on this line only.
+            #-------------------------------------------------------------
+            # Part 1: keep track of containers balanced on this line only.
+            #-------------------------------------------------------------
             # These are used below to prevent unwanted cross-line alignments.
             # Unbalanced containers already avoid aligning across
             # container boundaries.
-
-            my $type       = $types_to_go[$i];
-            my $token      = $tokens_to_go[$i];
-            my $depth_last = $depth;
+            my $type = $types_to_go[$i];
             if ( $type_sequence_to_go[$i] ) {
+                my $token = $tokens_to_go[$i];
                 if ( $is_opening_token{$token} ) {
 
                     # if container is balanced on this line...
                     my $i_mate = $mate_index_to_go[$i];
                     if ( $i_mate > $i && $i_mate <= $iend ) {
+                        $i_depth_prev = $i;
+                        $depth_prev   = $depth;
                         $depth++;
 
                      # Append the previous token name to make the container name
@@ -24315,10 +26094,8 @@ sub pad_token {
                    # is_d( { foo => $a, bar => $a }, { foo => $b, bar => $c } );
                    # is_d( [ \$a,       \$a ], [ \$b,             \$c ] );
 
-                        my $name = $token;
-                        if ( $token eq '(' ) {
-                            $name = $self->make_paren_name($i);
-                        }
+                        my $name =
+                          $token eq '(' ? $self->make_paren_name($i) : $token;
 
                         # name cannot be '.', so change to something else if so
                         if ( $name eq '.' ) { $name = 'dot' }
@@ -24354,32 +26131,7 @@ sub pad_token {
                         # if we are not aligning on this paren...
                         if ( !$ralignment_type_to_go->[$i] ) {
 
-                            # Sum length from previous alignment
-                            my $len = token_sequence_length( $i_start, $i - 1 );
-
-                            # Minor patch: do not include the length of any '!'.
-                            # Otherwise, commas in the following line will not
-                            # match
-                            #  ok( 20, tapprox( ( pdl 2,  3 ), ( pdl 2, 3 ) ) );
-                            #  ok( 21, !tapprox( ( pdl 2, 3 ), ( pdl 2, 4 ) ) );
-                            if ( grep { $_ eq '!' }
-                                @types_to_go[ $i_start .. $i - 1 ] )
-                            {
-                                $len -= 1;
-                            }
-
-                            if ( $i_start == $ibeg ) {
-
-                                # For first token, use distance from start of
-                                # line but subtract off the indentation due to
-                                # level.  Otherwise, results could vary with
-                                # indentation.
-                                $len +=
-                                  leading_spaces_to_go($ibeg) -
-                                  $levels_to_go[$i_start] *
-                                  $rOpts_indent_columns;
-                                if ( $len < 0 ) { $len = 0 }
-                            }
+                            my $len = length_tag( $i, $ibeg, $i_start );
 
                             # tack this length onto the container name to try
                             # to make a unique token name
@@ -24389,12 +26141,16 @@ sub pad_token {
                 } ## end if ( $is_opening_token...)
 
                 elsif ( $is_closing_type{$token} ) {
+                    $i_depth_prev = $i;
+                    $depth_prev   = $depth;
                     $depth-- if $depth > 0;
                 }
             } ## end if ( $type_sequence_to_go...)
 
-            # if we find a new synchronization token, we are done with
-            # a field
+            #------------------------------------------------------------
+            # Part 2: if we find a new synchronization token, we are done
+            # with a field
+            #------------------------------------------------------------
             if ( $i > $i_start && $ralignment_type_to_go->[$i] ) {
 
                 my $tok = my $raw_tok = $ralignment_type_to_go->[$i];
@@ -24415,6 +26171,7 @@ sub pad_token {
 
                   # If we are at an opening token which increased depth, we have
                   # to use the name from the previous depth.
+                    my $depth_last = $i == $i_depth_prev ? $depth_prev : $depth;
                     my $depth_p =
                       ( $depth_last < $depth ? $depth_last : $depth );
                     if ( $container_name{$depth_p} ) {
@@ -24497,7 +26254,9 @@ sub pad_token {
                 $patterns[$j] = EMPTY_STRING;
             } ## end if ( new synchronization token
 
-            # continue accumulating tokens
+            #-----------------------------------------------
+            # Part 3: continue accumulating the next pattern
+            #-----------------------------------------------
 
             # for keywords we have to use the actual text
             if ( $type eq 'k' ) {
@@ -24568,15 +26327,18 @@ sub pad_token {
             # everything else
             else {
                 $patterns[$j] .= $type;
-            }
 
-            # remove any zero-level name at first fat comma
-            if ( $depth == 0 && $type eq '=>' ) {
-                $container_name{$depth} = EMPTY_STRING;
+                # remove any zero-level name at first fat comma
+                if ( $depth == 0 && $type eq '=>' ) {
+                    $container_name{$depth} = EMPTY_STRING;
+                }
             }
+
         } ## end for my $i ( $ibeg .. $iend)
 
-        # done with this line .. join text of tokens to make the last field
+        #---------------------------------------------------------------
+        # End of main loop .. join text of tokens to make the last field
+        #---------------------------------------------------------------
         push( @fields,
             join( EMPTY_STRING, @tokens_to_go[ $i_start .. $iend ] ) );
         push @field_lengths,
@@ -24585,6 +26347,108 @@ sub pad_token {
         return [ \@tokens, \@fields, \@patterns, \@field_lengths ];
     } ## end sub make_alignment_patterns
 
+    sub make_uncontained_comma_name {
+        my ( $iterm, $ibeg, $iend ) = @_;
+
+        # Make a container name by combining all leading barewords,
+        # keywords and functions.
+        my $name  = EMPTY_STRING;
+        my $count = 0;
+        my $count_max;
+        my $iname_end;
+        my $ilast_blank;
+        for ( $ibeg .. $iterm ) {
+            my $type = $types_to_go[$_];
+
+            if ( $type eq 'b' ) {
+                $ilast_blank = $_;
+                next;
+            }
+
+            my $token = $tokens_to_go[$_];
+
+            # Give up if we find an opening paren, binary operator or
+            # comma within or after the proposed container name.
+            if (   $token eq '('
+                || $is_binary_type{$type}
+                || $type eq 'k' && $is_binary_keyword{$token} )
+            {
+                $name = EMPTY_STRING;
+                last;
+            }
+
+            # The container name is only built of certain types:
+            last if ( !$is_kwU{$type} );
+
+            # Normally it is made of one word, but two words for 'use'
+            if ( $count == 0 ) {
+                if (   $type eq 'k'
+                    && $is_use_like{ $tokens_to_go[$_] } )
+                {
+                    $count_max = 2;
+                }
+                else {
+                    $count_max = 1;
+                }
+            }
+            elsif ( defined($count_max) && $count >= $count_max ) {
+                last;
+            }
+
+            if ( defined( $name_map{$token} ) ) {
+                $token = $name_map{$token};
+            }
+
+            $name .= SPACE . $token;
+            $iname_end = $_;
+            $count++;
+        }
+
+        # Require a space after the container name token(s)
+        if (   $name
+            && defined($ilast_blank)
+            && $ilast_blank > $iname_end )
+        {
+            $name = substr( $name, 1 );
+        }
+        return $name;
+    } ## end sub make_uncontained_comma_name
+
+    sub length_tag {
+
+        my ( $i, $ibeg, $i_start ) = @_;
+
+        # Generate a line length to be used as a tag for rejecting bad
+        # alignments.  The tag is the length of the line from the previous
+        # matching token, or beginning of line, to the function name.  This
+        # will allow the vertical aligner to reject undesirable matches.
+
+        # The basic method: sum length from previous alignment
+        my $len = token_sequence_length( $i_start, $i - 1 );
+
+        # Minor patch: do not include the length of any '!'.
+        # Otherwise, commas in the following line will not
+        # match
+        #  ok( 20, tapprox( ( pdl 2,  3 ), ( pdl 2, 3 ) ) );
+        #  ok( 21, !tapprox( ( pdl 2, 3 ), ( pdl 2, 4 ) ) );
+        if ( grep { $_ eq '!' } @types_to_go[ $i_start .. $i - 1 ] ) {
+            $len -= 1;
+        }
+
+        if ( $i_start == $ibeg ) {
+
+            # For first token, use distance from start of
+            # line but subtract off the indentation due to
+            # level.  Otherwise, results could vary with
+            # indentation.
+            $len +=
+              leading_spaces_to_go($ibeg) -
+              $levels_to_go[$i_start] * $rOpts_indent_columns;
+        }
+        if ( $len < 0 ) { $len = 0 }
+        return $len;
+    } ## end sub length_tag
+
 } ## end closure make_alignment_patterns
 
 sub make_paren_name {
@@ -24617,23 +26481,40 @@ sub make_paren_name {
     return $name;
 } ## end sub make_paren_name
 
-{    ## begin closure final_indentation_adjustment
+{    ## begin closure get_final_indentation
 
     my ( $last_indentation_written, $last_unadjusted_indentation,
         $last_leading_token );
 
-    sub initialize_final_indentation_adjustment {
+    sub initialize_get_final_indentation {
         $last_indentation_written    = 0;
         $last_unadjusted_indentation = 0;
         $last_leading_token          = EMPTY_STRING;
         return;
     }
 
-    sub final_indentation_adjustment {
+    sub get_final_indentation {
 
-        #--------------------------------------------------------------------
-        # This routine sets the final indentation of a line in the Formatter.
-        #--------------------------------------------------------------------
+        my (
+            $self,    #
+
+            $ibeg,
+            $iend,
+            $rfields,
+            $rpatterns,
+            $ri_first,
+            $ri_last,
+            $rindentation_list,
+            $level_jump,
+            $starting_in_quote,
+            $is_static_block_comment,
+
+        ) = @_;
+
+        #--------------------------------------------------------------
+        # This routine makes any necessary adjustments to get the final
+        # indentation of a line in the Formatter.
+        #--------------------------------------------------------------
 
         # It starts with the basic indentation which has been defined for the
         # leading token, and then takes into account any options that the user
@@ -24656,22 +26537,6 @@ sub make_paren_name {
         #    undo_ci, which was processed earlier, so care has to be taken to
         #    keep them coordinated.
 
-        my (
-            $self,       $ibeg,
-            $iend,       $rfields,
-            $rpatterns,  $ri_first,
-            $ri_last,    $rindentation_list,
-            $level_jump, $starting_in_quote,
-            $is_static_block_comment,
-        ) = @_;
-
-        my $rLL                      = $self->[_rLL_];
-        my $Klimit                   = $self->[_Klimit_];
-        my $ris_bli_container        = $self->[_ris_bli_container_];
-        my $rseqno_controlling_my_ci = $self->[_rseqno_controlling_my_ci_];
-        my $rwant_reduced_ci         = $self->[_rwant_reduced_ci_];
-        my $rK_weld_left             = $self->[_rK_weld_left_];
-
         # Find the last code token of this line
         my $i_terminal    = $iend;
         my $terminal_type = $types_to_go[$iend];
@@ -24684,19 +26549,15 @@ sub make_paren_name {
             }
         }
 
-        my $terminal_block_type = $block_type_to_go[$i_terminal];
-        my $is_outdented_line   = 0;
+        my $is_outdented_line;
 
         my $type_beg            = $types_to_go[$ibeg];
         my $token_beg           = $tokens_to_go[$ibeg];
-        my $block_type_beg      = $block_type_to_go[$ibeg];
         my $level_beg           = $levels_to_go[$ibeg];
+        my $block_type_beg      = $block_type_to_go[$ibeg];
         my $leading_spaces_beg  = $leading_spaces_to_go[$ibeg];
-        my $K_beg               = $K_to_go[$ibeg];
         my $seqno_beg           = $type_sequence_to_go[$ibeg];
-        my $ibeg_weld_fix       = $ibeg;
         my $is_closing_type_beg = $is_closing_type{$type_beg};
-        my $is_bli_beg = $seqno_beg ? $ris_bli_container->{$seqno_beg} : 0;
 
         # QW INDENTATION PATCH 3:
         my $seqno_qw_closing;
@@ -24724,7 +26585,7 @@ sub make_paren_name {
         # }
         #
 
-        # MOJO: Set a flag if this lines begins with ')->'
+        # MOJO patch: Set a flag if this lines begins with ')->'
         my $leading_paren_arrow = (
                  $is_closing_type_beg
               && $token_beg eq ')'
@@ -24748,661 +26609,787 @@ sub make_paren_name {
         #       2 - vertically align with opening token
         #       3 - indent
         #---------------------------------------------------------
+
         my $adjust_indentation         = 0;
-        my $default_adjust_indentation = $adjust_indentation;
+        my $default_adjust_indentation = 0;
 
+        # Parameters needed for option 2, aligning with opening token:
         my (
             $opening_indentation, $opening_offset,
             $is_leading,          $opening_exists
         );
 
-        # Honor any flag to reduce -ci set by the -bbxi=n option
-        if ( $seqno_beg && $rwant_reduced_ci->{$seqno_beg} ) {
+        #-------------------------------------
+        # Section 1A:
+        # if line starts with a sequenced item
+        #-------------------------------------
+        if ( $seqno_beg || $seqno_qw_closing ) {
+
+            # This can be tedious so we let a sub do it
+            (
+                $adjust_indentation,
+                $default_adjust_indentation,
+                $opening_indentation,
+                $opening_offset,
+                $is_leading,
+                $opening_exists,
+
+            ) = $self->get_closing_token_indentation(
+
+                $ibeg,
+                $iend,
+                $ri_first,
+                $ri_last,
+                $rindentation_list,
+                $level_jump,
+                $i_terminal,
+                $is_semicolon_terminated,
+                $seqno_qw_closing,
+
+            );
+        }
+
+        #--------------------------------------------------------
+        # Section 1B:
+        # if at ');', '};', '>;', and '];' of a terminal qw quote
+        #--------------------------------------------------------
+        elsif (
+               substr( $rpatterns->[0], 0, 2 ) eq 'qb'
+            && substr( $rfields->[0], -1, 1 ) eq ';'
+            ##         $rpatterns->[0] =~ /^qb*;$/
+            && $rfields->[0] =~ /^([\)\}\]\>]);$/
+          )
+        {
+            if ( $closing_token_indentation{$1} == 0 ) {
+                $adjust_indentation = 1;
+            }
+            else {
+                $adjust_indentation = 3;
+            }
+        }
+
+        #---------------------------------------------------------
+        # Section 2: set indentation according to flag set above
+        #
+        # Select the indentation object to define leading
+        # whitespace.  If we are outdenting something like '} } );'
+        # then we want to use one level below the last token
+        # ($i_terminal) in order to get it to fully outdent through
+        # all levels.
+        #---------------------------------------------------------
+        my $indentation;
+        my $lev;
+        my $level_end = $levels_to_go[$iend];
+
+        #------------------------------------
+        # Section 2A: adjust_indentation == 0
+        # No change in indentation
+        #------------------------------------
+        if ( $adjust_indentation == 0 ) {
+            $indentation = $leading_spaces_beg;
+            $lev         = $level_beg;
+        }
+
+        #-------------------------------------------------------------------
+        # Secton 2B: adjust_indentation == 1
+        # Change the indentation to be that of a different token on the line
+        #-------------------------------------------------------------------
+        elsif ( $adjust_indentation == 1 ) {
+
+            # Previously, the indentation of the terminal token was used:
+            # OLD CODING:
+            # $indentation = $reduced_spaces_to_go[$i_terminal];
+            # $lev         = $levels_to_go[$i_terminal];
+
+            # Generalization for MOJO patch:
+            # Use the lowest level indentation of the tokens on the line.
+            # For example, here we can use the indentation of the ending ';':
+            #    } until ($selection > 0 and $selection < 10);   # ok to use ';'
+            # But this will not outdent if we use the terminal indentation:
+            #    )->then( sub {      # use indentation of the ->, not the {
+            # Warning: reduced_spaces_to_go[] may be a reference, do not
+            # do numerical checks with it
+
+            my $i_ind = $ibeg;
+            $indentation = $reduced_spaces_to_go[$i_ind];
+            $lev         = $levels_to_go[$i_ind];
+            while ( $i_ind < $i_terminal ) {
+                $i_ind++;
+                if ( $levels_to_go[$i_ind] < $lev ) {
+                    $indentation = $reduced_spaces_to_go[$i_ind];
+                    $lev         = $levels_to_go[$i_ind];
+                }
+            }
+        }
+
+        #--------------------------------------------------------------
+        # Secton 2C: adjust_indentation == 2
+        # Handle indented closing token which aligns with opening token
+        #--------------------------------------------------------------
+        elsif ( $adjust_indentation == 2 ) {
+
+            # handle option to align closing token with opening token
+            $lev = $level_beg;
+
+            # calculate spaces needed to align with opening token
+            my $space_count =
+              get_spaces($opening_indentation) + $opening_offset;
+
+            # Indent less than the previous line.
+            #
+            # Problem: For -lp we don't exactly know what it was if there
+            # were recoverable spaces sent to the aligner.  A good solution
+            # would be to force a flush of the vertical alignment buffer, so
+            # that we would know.  For now, this rule is used for -lp:
+            #
+            # When the last line did not start with a closing token we will
+            # be optimistic that the aligner will recover everything wanted.
+            #
+            # This rule will prevent us from breaking a hierarchy of closing
+            # tokens, and in a worst case will leave a closing paren too far
+            # indented, but this is better than frequently leaving it not
+            # indented enough.
+            my $last_spaces = get_spaces($last_indentation_written);
 
-            # if this is an opening, it must be alone on the line ...
-            if ( $is_closing_type{$type_beg} || $ibeg == $i_terminal ) {
-                $adjust_indentation = 1;
+            if ( ref($last_indentation_written)
+                && !$is_closing_token{$last_leading_token} )
+            {
+                $last_spaces +=
+                  get_recoverable_spaces($last_indentation_written);
             }
 
-            # ... or a single welded unit (fix for b1173)
-            elsif ($total_weld_count) {
-                my $Kterm      = $K_to_go[$i_terminal];
-                my $Kterm_test = $rK_weld_left->{$Kterm};
-                if ( defined($Kterm_test) && $Kterm_test >= $K_beg ) {
-                    $Kterm = $Kterm_test;
-                }
-                if ( $Kterm == $K_beg ) { $adjust_indentation = 1 }
-            }
-        }
+            # reset the indentation to the new space count if it works
+            # only options are all or none: nothing in-between looks good
+            $lev = $level_beg;
 
-        # Update the $is_bli flag as we go. It is initially 1.
-        # We note seeing a leading opening brace by setting it to 2.
-        # If we get to the closing brace without seeing the opening then we
-        # turn it off.  This occurs if the opening brace did not get output
-        # at the start of a line, so we will then indent the closing brace
-        # in the default way.
-        if ( $is_bli_beg && $is_bli_beg == 1 ) {
-            my $K_opening_container = $self->[_K_opening_container_];
-            my $K_opening           = $K_opening_container->{$seqno_beg};
-            if ( $K_beg eq $K_opening ) {
-                $ris_bli_container->{$seqno_beg} = $is_bli_beg = 2;
+            my $diff = $last_spaces - $space_count;
+            if ( $diff > 0 ) {
+                $indentation = $space_count;
             }
-            else { $is_bli_beg = 0 }
-        }
+            else {
 
-        # QW PATCH for the combination -lp -wn
-        # For -lp formatting use $ibeg_weld_fix to get around the problem
-        # that with -lp type formatting the opening and closing tokens to not
-        # have sequence numbers.
-        if ( $seqno_qw_closing && $total_weld_count ) {
-            my $i_plus = $inext_to_go[$ibeg];
-            if ( $i_plus <= $max_index_to_go ) {
-                my $K_plus = $K_to_go[$i_plus];
-                if ( defined( $rK_weld_left->{$K_plus} ) ) {
-                    $ibeg_weld_fix = $i_plus;
+                # We need to fix things ... but there is no good way to do it.
+                # The best solution is for the user to use a longer maximum
+                # line length.  We could get a smooth variation if we just move
+                # the paren in using
+                #    $space_count -= ( 1 - $diff );
+                # But unfortunately this can give a rather unbalanced look.
+
+                # For -xlp we currently allow a tolerance of one indentation
+                # level and then revert to a simpler default.  This will jump
+                # suddenly but keeps a balanced look.
+                if (   $rOpts_extended_line_up_parentheses
+                    && $diff >= -$rOpts_indent_columns
+                    && $space_count > $leading_spaces_beg )
+                {
+                    $indentation = $space_count;
+                }
+
+                # Otherwise revert to defaults
+                elsif ( $default_adjust_indentation == 0 ) {
+                    $indentation = $leading_spaces_beg;
+                }
+                elsif ( $default_adjust_indentation == 1 ) {
+                    $indentation = $reduced_spaces_to_go[$i_terminal];
+                    $lev         = $levels_to_go[$i_terminal];
                 }
             }
         }
 
-        # if we are at a closing token of some type..
-        if ( $is_closing_type_beg || $seqno_qw_closing ) {
-
-            # get the indentation of the line containing the corresponding
-            # opening token
-            (
-                $opening_indentation, $opening_offset,
-                $is_leading,          $opening_exists
-              )
-              = $self->get_opening_indentation( $ibeg_weld_fix, $ri_first,
-                $ri_last, $rindentation_list, $seqno_qw_closing );
+        #-------------------------------------------------------------
+        # Secton 2D: adjust_indentation == 3
+        # Full indentation of closing tokens (-icb and -icp or -cti=2)
+        #-------------------------------------------------------------
+        else {
 
-            my $terminal_is_in_list = $self->is_in_list_by_i($i_terminal);
+            # handle -icb (indented closing code block braces)
+            # Updated method for indented block braces: indent one full level if
+            # there is no continuation indentation.  This will occur for major
+            # structures such as sub, if, else, but not for things like map
+            # blocks.
+            #
+            # Note: only code blocks without continuation indentation are
+            # handled here (if, else, unless, ..). In the following snippet,
+            # the terminal brace of the sort block will have continuation
+            # indentation as shown so it will not be handled by the coding
+            # here.  We would have to undo the continuation indentation to do
+            # this, but it probably looks ok as is.  This is a possible future
+            # update for semicolon terminated lines.
+            #
+            #     if ($sortby eq 'date' or $sortby eq 'size') {
+            #         @files = sort {
+            #             $file_data{$a}{$sortby} <=> $file_data{$b}{$sortby}
+            #                 or $a cmp $b
+            #                 } @files;
+            #         }
+            #
+            if (   $block_type_beg
+                && $ci_levels_to_go[$i_terminal] == 0 )
+            {
+                my $spaces = get_spaces( $leading_spaces_to_go[$i_terminal] );
+                $indentation = $spaces + $rOpts_indent_columns;
 
-            # First set the default behavior:
-            if (
+                # NOTE: for -lp we could create a new indentation object, but
+                # there is probably no need to do it
+            }
 
-                # default behavior is to outdent closing lines
-                # of the form:   ");  };  ];  )->xxx;"
-                $is_semicolon_terminated
+            # handle -icp and any -icb block braces which fall through above
+            # test such as the 'sort' block mentioned above.
+            else {
 
-                # and 'cuddled parens' of the form:   ")->pack("
-                # Bug fix for RT #123749]: the types here were
-                # incorrectly '(' and ')'.  Corrected to be '{' and '}'
-                || (
-                       $terminal_type eq '{'
-                    && $type_beg eq '}'
-                    && ( $nesting_depth_to_go[$iend] + 1 ==
-                        $nesting_depth_to_go[$ibeg] )
-                )
+                # There are currently two ways to handle -icp...
+                # One way is to use the indentation of the previous line:
+                # $indentation = $last_indentation_written;
 
-                # remove continuation indentation for any line like
-                #       } ... {
-                # or without ending '{' and unbalanced, such as
-                #       such as '}->{$operator}'
-                || (
-                    $type_beg eq '}'
+                # The other way is to use the indentation that the previous line
+                # would have had if it hadn't been adjusted:
+                $indentation = $last_unadjusted_indentation;
 
-                    && (   $types_to_go[$iend] eq '{'
-                        || $levels_to_go[$iend] < $level_beg )
-                )
+                # Current method: use the minimum of the two. This avoids
+                # inconsistent indentation.
+                if ( get_spaces($last_indentation_written) <
+                    get_spaces($indentation) )
+                {
+                    $indentation = $last_indentation_written;
+                }
+            }
 
-                # and when the next line is at a lower indentation level...
+            # use previous indentation but use own level
+            # to cause list to be flushed properly
+            $lev = $level_beg;
+        }
 
-                # PATCH #1: and only if the style allows undoing continuation
-                # for all closing token types. We should really wait until
-                # the indentation of the next line is known and then make
-                # a decision, but that would require another pass.
+        #-------------------------------------------------------------
+        # Remember indentation except for multi-line quotes, which get
+        # no indentation
+        #-------------------------------------------------------------
+        if ( !( $ibeg == 0 && $starting_in_quote ) ) {
+            $last_indentation_written    = $indentation;
+            $last_unadjusted_indentation = $leading_spaces_beg;
+            $last_leading_token          = $token_beg;
 
-                # PATCH #2: and not if this token is under -xci control
-                || (   $level_jump < 0
-                    && !$some_closing_token_indentation
-                    && !$rseqno_controlling_my_ci->{$K_beg} )
+            # Patch to make a line which is the end of a qw quote work with the
+            # -lp option.  Make $token_beg look like a closing token as some
+            # type even if it is not.  This variable will become
+            # $last_leading_token at the end of this loop.  Then, if the -lp
+            # style is selected, and the next line is also a
+            # closing token, it will not get more indentation than this line.
+            # We need to do this because qw quotes (at present) only get
+            # continuation indentation, not one level of indentation, so we
+            # need to turn off the -lp indentation.
 
-                # Patch for -wn=2, multiple welded closing tokens
-                || (   $i_terminal > $ibeg
-                    && $is_closing_type{ $types_to_go[$iend] } )
+            # ... a picture is worth a thousand words:
 
-                # Alternate Patch for git #51, isolated closing qw token not
-                # outdented if no-delete-old-newlines is set. This works, but
-                # a more general patch elsewhere fixes the real problem: ljump.
-                # || ( $seqno_qw_closing && $ibeg == $i_terminal )
+            # perltidy -wn -gnu (Without this patch):
+            #   ok(defined(
+            #       $seqio = $gb->get_Stream_by_batch([qw(J00522 AF303112
+            #       2981014)])
+            #             ));
 
-              )
+            # perltidy -wn -gnu (With this patch):
+            #  ok(defined(
+            #      $seqio = $gb->get_Stream_by_batch([qw(J00522 AF303112
+            #      2981014)])
+            #  ));
+            if ( $seqno_qw_closing
+                && ( length($token_beg) > 1 || $token_beg eq '>' ) )
             {
-                $adjust_indentation = 1;
+                $last_leading_token = ')';
             }
+        }
 
-            # outdent something like '),'
-            if (
-                $terminal_type eq ','
+        #---------------------------------------------------------------------
+        # Rule: lines with leading closing tokens should not be outdented more
+        # than the line which contained the corresponding opening token.
+        #---------------------------------------------------------------------
 
-                # Removed this constraint for -wn
-                # OLD: allow just one character before the comma
-                # && $i_terminal == $ibeg + 1
+        # Updated per bug report in alex_bug.pl: we must not
+        # mess with the indentation of closing logical braces, so
+        # we must treat something like '} else {' as if it were
+        # an isolated brace
+        my $is_isolated_block_brace = $block_type_beg
+          && ( $i_terminal == $ibeg
+            || $is_if_elsif_else_unless_while_until_for_foreach{$block_type_beg}
+          );
 
-                # require LIST environment; otherwise, we may outdent too much -
-                # this can happen in calls without parentheses (overload.t);
-                && $terminal_is_in_list
-              )
-            {
-                $adjust_indentation = 1;
-            }
+        # only do this for a ':; which is aligned with its leading '?'
+        my $is_unaligned_colon = $type_beg eq ':' && !$is_leading;
 
-            # undo continuation indentation of a terminal closing token if
-            # it is the last token before a level decrease.  This will allow
-            # a closing token to line up with its opening counterpart, and
-            # avoids an indentation jump larger than 1 level.
-            if (   $i_terminal == $ibeg
-                && $is_closing_type_beg
-                && defined($K_beg)
-                && $K_beg < $Klimit )
-            {
-                my $K_plus    = $K_beg + 1;
-                my $type_plus = $rLL->[$K_plus]->[_TYPE_];
+        if (
+            defined($opening_indentation)
+            && !$leading_paren_arrow    # MOJO patch
+            && !$is_isolated_block_brace
+            && !$is_unaligned_colon
+          )
+        {
+            if ( get_spaces($opening_indentation) > get_spaces($indentation) ) {
+                $indentation = $opening_indentation;
+            }
+        }
 
-                if ( $type_plus eq 'b' && $K_plus < $Klimit ) {
-                    $type_plus = $rLL->[ ++$K_plus ]->[_TYPE_];
-                }
+        #----------------------------------------------------
+        # remember the indentation of each line of this batch
+        #----------------------------------------------------
+        push @{$rindentation_list}, $indentation;
 
-                if ( $type_plus eq '#' && $K_plus < $Klimit ) {
-                    $type_plus = $rLL->[ ++$K_plus ]->[_TYPE_];
-                    if ( $type_plus eq 'b' && $K_plus < $Klimit ) {
-                        $type_plus = $rLL->[ ++$K_plus ]->[_TYPE_];
-                    }
+        #---------------------------------------------
+        # outdent lines with certain leading tokens...
+        #---------------------------------------------
+        if (
 
-                    # Note: we have skipped past just one comment (perhaps a
-                    # side comment).  There could be more, and we could easily
-                    # skip past all the rest with the following code, or with a
-                    # while loop.  It would be rare to have to do this, and
-                    # those block comments would still be indented, so it would
-                    # to leave them indented.  So it seems best to just stop at
-                    # a maximum of one comment.
-                    ##if ($type_plus eq '#') {
-                    ##   $K_plus = $self->K_next_code($K_plus);
-                    ##}
-                }
+            # must be first word of this batch
+            $ibeg == 0
 
-                if ( !$is_bli_beg && defined($K_plus) ) {
-                    my $lev        = $level_beg;
-                    my $level_next = $rLL->[$K_plus]->[_LEVEL_];
+            # and ...
+            && (
 
-                    # and do not undo ci if it was set by the -xci option
-                    $adjust_indentation = 1
-                      if ( $level_next < $lev
-                        && !$rseqno_controlling_my_ci->{$K_beg} );
-                }
+                # certain leading keywords if requested
+                $rOpts_outdent_keywords
+                && $type_beg eq 'k'
+                && $outdent_keyword{$token_beg}
 
-                # Patch for RT #96101, in which closing brace of anonymous subs
-                # was not outdented.  We should look ahead and see if there is
-                # a level decrease at the next token (i.e., a closing token),
-                # but right now we do not have that information.  For now
-                # we see if we are in a list, and this works well.
-                # See test files 'sub*.t' for good test cases.
-                if (   $terminal_is_in_list
-                    && !$rOpts_indent_closing_brace
-                    && $block_type_beg
-                    && $block_type_beg =~ /$ASUB_PATTERN/ )
-                {
-                    (
-                        $opening_indentation, $opening_offset,
-                        $is_leading,          $opening_exists
-                      )
-                      = $self->get_opening_indentation( $ibeg, $ri_first,
-                        $ri_last, $rindentation_list );
-                    my $indentation = $leading_spaces_beg;
-                    if ( defined($opening_indentation)
-                        && get_spaces($indentation) >
-                        get_spaces($opening_indentation) )
-                    {
-                        $adjust_indentation = 1;
-                    }
-                }
-            }
+                # or labels if requested
+                || $rOpts_outdent_labels && $type_beg eq 'J'
 
-            # YVES patch 1 of 2:
-            # Undo ci of line with leading closing eval brace,
-            # but not beyond the indentation of the line with
-            # the opening brace.
-            if (
-                $block_type_beg eq 'eval'
-                ##&& !$rOpts_line_up_parentheses
-                && !ref($leading_spaces_beg)
-                && !$rOpts_indent_closing_brace
-              )
-            {
-                (
-                    $opening_indentation, $opening_offset,
-                    $is_leading,          $opening_exists
-                  )
-                  = $self->get_opening_indentation( $ibeg, $ri_first, $ri_last,
-                    $rindentation_list );
-                my $indentation = $leading_spaces_beg;
-                if ( defined($opening_indentation)
-                    && get_spaces($indentation) >
-                    get_spaces($opening_indentation) )
-                {
-                    $adjust_indentation = 1;
+                # or static block comments if requested
+                || $is_static_block_comment
+                && $rOpts_outdent_static_block_comments
+            )
+          )
+        {
+            my $space_count = leading_spaces_to_go($ibeg);
+            if ( $space_count > 0 ) {
+                $space_count -= $rOpts_continuation_indentation;
+                $is_outdented_line = 1;
+                if ( $space_count < 0 ) { $space_count = 0 }
+
+                # do not promote a spaced static block comment to non-spaced;
+                # this is not normally necessary but could be for some
+                # unusual user inputs (such as -ci = -i)
+                if ( $type_beg eq '#' && $space_count == 0 ) {
+                    $space_count = 1;
                 }
+
+                $indentation = $space_count;
             }
+        }
 
-            # patch for issue git #40: -bli setting has priority
-            $adjust_indentation = 0 if ($is_bli_beg);
+        return (
 
-            $default_adjust_indentation = $adjust_indentation;
+            $indentation,
+            $lev,
+            $level_end,
+            $i_terminal,
+            $is_outdented_line,
 
-            # Now modify default behavior according to user request:
-            # handle option to indent non-blocks of the form );  };  ];
-            # But don't do special indentation to something like ')->pack('
-            if ( !$block_type_beg ) {
+        );
+    } ## end sub get_final_indentation
 
-                # Note that logical padding has already been applied, so we may
-                # need to remove some spaces to get a valid hash key.
-                my $tok = $token_beg;
-                my $cti = $closing_token_indentation{$tok};
+    sub get_closing_token_indentation {
 
-                # Fix the value of 'cti' for an isolated non-welded closing qw
-                # delimiter.
-                if ( $seqno_qw_closing && $ibeg_weld_fix == $ibeg ) {
+        # Determine indentation adjustment for a line with a leading closing
+        # token - i.e. one of these:     ) ] } :
 
-                    # A quote delimiter which is not a container will not have
-                    # a cti value defined.  In this case use the style of a
-                    # paren. For example
-                    #   my @fars = (
-                    #      qw<
-                    #        far
-                    #        farfar
-                    #        farfars-far
-                    #      >,
-                    #   );
-                    if ( !defined($cti) && length($tok) == 1 ) {
+        my (
+            $self,    #
+
+            $ibeg,
+            $iend,
+            $ri_first,
+            $ri_last,
+            $rindentation_list,
+            $level_jump,
+            $i_terminal,
+            $is_semicolon_terminated,
+            $seqno_qw_closing,
 
-                        # something other than ')', '}', ']' ; use flag for ')'
-                        $cti = $closing_token_indentation{')'};
+        ) = @_;
 
-                        # But for now, do not outdent non-container qw
-                        # delimiters because it would would change existing
-                        # formatting.
-                        if ( $tok ne '>' ) { $cti = 3 }
-                    }
+        my $adjust_indentation         = 0;
+        my $default_adjust_indentation = $adjust_indentation;
+        my $terminal_type              = $types_to_go[$i_terminal];
 
-                    # A non-welded closing qw cannot currently use -cti=1
-                    # because that option requires a sequence number to find
-                    # the opening indentation, and qw quote delimiters are not
-                    # sequenced items.
-                    if ( defined($cti) && $cti == 1 ) { $cti = 0 }
-                }
+        my $type_beg            = $types_to_go[$ibeg];
+        my $token_beg           = $tokens_to_go[$ibeg];
+        my $level_beg           = $levels_to_go[$ibeg];
+        my $block_type_beg      = $block_type_to_go[$ibeg];
+        my $leading_spaces_beg  = $leading_spaces_to_go[$ibeg];
+        my $seqno_beg           = $type_sequence_to_go[$ibeg];
+        my $is_closing_type_beg = $is_closing_type{$type_beg};
 
-                if ( !defined($cti) ) {
+        my (
+            $opening_indentation, $opening_offset,
+            $is_leading,          $opening_exists
+        );
 
-                    # $cti may not be defined for several reasons.
-                    # -padding may have been applied so the character
-                    #  has a length > 1
-                    # - we may have welded to a closing quote token.
-                    #   Here is an example (perltidy -wn):
-                    #       __PACKAGE__->load_components( qw(
-                    #  >         Core
-                    #  >
-                    #  >     ) );
-                    $adjust_indentation = 0;
+        # Honor any flag to reduce -ci set by the -bbxi=n option
+        if ( $seqno_beg && $self->[_rwant_reduced_ci_]->{$seqno_beg} ) {
 
-                }
-                elsif ( $cti == 1 ) {
-                    if (   $i_terminal <= $ibeg + 1
-                        || $is_semicolon_terminated )
-                    {
-                        $adjust_indentation = 2;
-                    }
-                    else {
-                        $adjust_indentation = 0;
-                    }
-                }
-                elsif ( $cti == 2 ) {
-                    if ($is_semicolon_terminated) {
-                        $adjust_indentation = 3;
-                    }
-                    else {
-                        $adjust_indentation = 0;
-                    }
-                }
-                elsif ( $cti == 3 ) {
-                    $adjust_indentation = 3;
-                }
+            # if this is an opening, it must be alone on the line ...
+            if ( $is_closing_type{$type_beg} || $ibeg == $i_terminal ) {
+                $adjust_indentation = 1;
             }
 
-            # handle option to indent blocks
-            else {
-                if (
-                    $rOpts_indent_closing_brace
-                    && (
-                        $i_terminal == $ibeg    #  isolated terminal '}'
-                        || $is_semicolon_terminated
-                    )
-                  )                             #  } xxxx ;
-                {
-                    $adjust_indentation = 3;
+            # ... or a single welded unit (fix for b1173)
+            elsif ($total_weld_count) {
+                my $K_beg      = $K_to_go[$ibeg];
+                my $Kterm      = $K_to_go[$i_terminal];
+                my $Kterm_test = $self->[_rK_weld_left_]->{$Kterm};
+                if ( defined($Kterm_test) && $Kterm_test >= $K_beg ) {
+                    $Kterm = $Kterm_test;
                 }
+                if ( $Kterm == $K_beg ) { $adjust_indentation = 1 }
             }
         }
 
-        # if at ');', '};', '>;', and '];' of a terminal qw quote
-        elsif (
-               substr( $rpatterns->[0], 0, 2 ) eq 'qb'
-            && substr( $rfields->[0], -1, 1 ) eq ';'
-            ##&& $rpatterns->[0] =~ /^qb*;$/
-            && $rfields->[0] =~ /^([\)\}\]\>]);$/
-          )
-        {
-            if ( $closing_token_indentation{$1} == 0 ) {
-                $adjust_indentation = 1;
+        my $ris_bli_container = $self->[_ris_bli_container_];
+        my $is_bli_beg = $seqno_beg ? $ris_bli_container->{$seqno_beg} : 0;
+
+        # Update the $is_bli flag as we go. It is initially 1.
+        # We note seeing a leading opening brace by setting it to 2.
+        # If we get to the closing brace without seeing the opening then we
+        # turn it off.  This occurs if the opening brace did not get output
+        # at the start of a line, so we will then indent the closing brace
+        # in the default way.
+        if ( $is_bli_beg && $is_bli_beg == 1 ) {
+            my $K_opening_container = $self->[_K_opening_container_];
+            my $K_opening           = $K_opening_container->{$seqno_beg};
+            my $K_beg               = $K_to_go[$ibeg];
+            if ( $K_beg eq $K_opening ) {
+                $ris_bli_container->{$seqno_beg} = $is_bli_beg = 2;
             }
-            else {
-                $adjust_indentation = 3;
+            else { $is_bli_beg = 0 }
+        }
+
+        # QW PATCH for the combination -lp -wn
+        # For -lp formatting use $ibeg_weld_fix to get around the problem
+        # that with -lp type formatting the opening and closing tokens to not
+        # have sequence numbers.
+        my $ibeg_weld_fix = $ibeg;
+        if ( $seqno_qw_closing && $total_weld_count ) {
+            my $i_plus = $inext_to_go[$ibeg];
+            if ( $i_plus <= $max_index_to_go ) {
+                my $K_plus = $K_to_go[$i_plus];
+                if ( defined( $self->[_rK_weld_left_]->{$K_plus} ) ) {
+                    $ibeg_weld_fix = $i_plus;
+                }
             }
         }
 
-        # if line begins with a ':', align it with any
-        # previous line leading with corresponding ?
-        elsif ( $type_beg eq ':' ) {
+        # if we are at a closing token of some type..
+        if ( $is_closing_type_beg || $seqno_qw_closing ) {
+
+            my $K_beg = $K_to_go[$ibeg];
+
+            # get the indentation of the line containing the corresponding
+            # opening token
             (
                 $opening_indentation, $opening_offset,
                 $is_leading,          $opening_exists
               )
-              = $self->get_opening_indentation( $ibeg, $ri_first, $ri_last,
-                $rindentation_list );
-            if ($is_leading) { $adjust_indentation = 2; }
-        }
-
-        #---------------------------------------------------------
-        # Section 2: set indentation according to flag set above
-        #
-        # Select the indentation object to define leading
-        # whitespace.  If we are outdenting something like '} } );'
-        # then we want to use one level below the last token
-        # ($i_terminal) in order to get it to fully outdent through
-        # all levels.
-        #---------------------------------------------------------
-        my $indentation;
-        my $lev;
-        my $level_end = $levels_to_go[$iend];
-
-        if ( $adjust_indentation == 0 ) {
-            $indentation = $leading_spaces_beg;
-            $lev         = $level_beg;
-        }
-        elsif ( $adjust_indentation == 1 ) {
+              = $self->get_opening_indentation( $ibeg_weld_fix, $ri_first,
+                $ri_last, $rindentation_list, $seqno_qw_closing );
 
-            # Change the indentation to be that of a different token on the line
-            # Previously, the indentation of the terminal token was used:
-            # OLD CODING:
-            # $indentation = $reduced_spaces_to_go[$i_terminal];
-            # $lev         = $levels_to_go[$i_terminal];
+            # Patch for rt144979, part 1. Coordinated with part 2.
+            # Do not undo ci for a cuddled closing brace control; it
+            # needs to be treated exactly the same ci as an isolated
+            # closing brace.
+            my $is_cuddled_closing_brace = $seqno_beg
+              && $self->[_ris_cuddled_closing_brace_]->{$seqno_beg};
 
-            # Generalization for MOJO:
-            # Use the lowest level indentation of the tokens on the line.
-            # For example, here we can use the indentation of the ending ';':
-            #    } until ($selection > 0 and $selection < 10);   # ok to use ';'
-            # But this will not outdent if we use the terminal indentation:
-            #    )->then( sub {      # use indentation of the ->, not the {
-            # Warning: reduced_spaces_to_go[] may be a reference, do not
-            # do numerical checks with it
+            # First set the default behavior:
+            if (
 
-            my $i_ind = $ibeg;
-            $indentation = $reduced_spaces_to_go[$i_ind];
-            $lev         = $levels_to_go[$i_ind];
-            while ( $i_ind < $i_terminal ) {
-                $i_ind++;
-                if ( $levels_to_go[$i_ind] < $lev ) {
-                    $indentation = $reduced_spaces_to_go[$i_ind];
-                    $lev         = $levels_to_go[$i_ind];
-                }
-            }
-        }
+                # default behavior is to outdent closing lines
+                # of the form:   ");  };  ];  )->xxx;"
+                $is_semicolon_terminated
 
-        # handle indented closing token which aligns with opening token
-        elsif ( $adjust_indentation == 2 ) {
+                # and 'cuddled parens' of the form:   ")->pack(". Bug fix for RT
+                # #123749]: the TYPES here were incorrectly ')' and '('.  The
+                # corrected TYPES are '}' and '{'. But skip a cuddled block.
+                || (
+                       $terminal_type eq '{'
+                    && $type_beg eq '}'
+                    && ( $nesting_depth_to_go[$iend] + 1 ==
+                        $nesting_depth_to_go[$ibeg] )
+                    && !$is_cuddled_closing_brace
+                )
 
-            # handle option to align closing token with opening token
-            $lev = $level_beg;
+                # remove continuation indentation for any line like
+                #       } ... {
+                # or without ending '{' and unbalanced, such as
+                #       such as '}->{$operator}'
+                || (
+                    $type_beg eq '}'
 
-            # calculate spaces needed to align with opening token
-            my $space_count =
-              get_spaces($opening_indentation) + $opening_offset;
+                    && (   $types_to_go[$iend] eq '{'
+                        || $levels_to_go[$iend] < $level_beg )
 
-            # Indent less than the previous line.
-            #
-            # Problem: For -lp we don't exactly know what it was if there
-            # were recoverable spaces sent to the aligner.  A good solution
-            # would be to force a flush of the vertical alignment buffer, so
-            # that we would know.  For now, this rule is used for -lp:
-            #
-            # When the last line did not start with a closing token we will
-            # be optimistic that the aligner will recover everything wanted.
-            #
-            # This rule will prevent us from breaking a hierarchy of closing
-            # tokens, and in a worst case will leave a closing paren too far
-            # indented, but this is better than frequently leaving it not
-            # indented enough.
-            my $last_spaces = get_spaces($last_indentation_written);
+                    # but not if a cuddled block
+                    && !$is_cuddled_closing_brace
+                )
 
-            if ( ref($last_indentation_written)
-                && !$is_closing_token{$last_leading_token} )
-            {
-                $last_spaces +=
-                  get_recoverable_spaces($last_indentation_written);
-            }
+                # and when the next line is at a lower indentation level...
 
-            # reset the indentation to the new space count if it works
-            # only options are all or none: nothing in-between looks good
-            $lev = $level_beg;
+                # PATCH #1: and only if the style allows undoing continuation
+                # for all closing token types. We should really wait until
+                # the indentation of the next line is known and then make
+                # a decision, but that would require another pass.
 
-            my $diff = $last_spaces - $space_count;
-            if ( $diff > 0 ) {
-                $indentation = $space_count;
-            }
-            else {
+                # PATCH #2: and not if this token is under -xci control
+                || (   $level_jump < 0
+                    && !$some_closing_token_indentation
+                    && !$self->[_rseqno_controlling_my_ci_]->{$K_beg} )
 
-                # We need to fix things ... but there is no good way to do it.
-                # The best solution is for the user to use a longer maximum
-                # line length.  We could get a smooth variation if we just move
-                # the paren in using
-                #    $space_count -= ( 1 - $diff );
-                # But unfortunately this can give a rather unbalanced look.
+                # Patch for -wn=2, multiple welded closing tokens
+                || (   $i_terminal > $ibeg
+                    && $is_closing_type{ $types_to_go[$iend] } )
 
-                # For -xlp we currently allow a tolerance of one indentation
-                # level and then revert to a simpler default.  This will jump
-                # suddenly but keeps a balanced look.
-                if (   $rOpts_extended_line_up_parentheses
-                    && $diff >= -$rOpts_indent_columns
-                    && $space_count > $leading_spaces_beg )
-                {
-                    $indentation = $space_count;
-                }
+                # Alternate Patch for git #51, isolated closing qw token not
+                # outdented if no-delete-old-newlines is set. This works, but
+                # a more general patch elsewhere fixes the real problem: ljump.
+                # || ( $seqno_qw_closing && $ibeg == $i_terminal )
 
-                # Otherwise revert to defaults
-                elsif ( $default_adjust_indentation == 0 ) {
-                    $indentation = $leading_spaces_beg;
-                }
-                elsif ( $default_adjust_indentation == 1 ) {
-                    $indentation = $reduced_spaces_to_go[$i_terminal];
-                    $lev         = $levels_to_go[$i_terminal];
-                }
+              )
+            {
+                $adjust_indentation = 1;
             }
-        }
 
-        # Full indentation of closing tokens (-icb and -icp or -cti=2)
-        else {
+            # outdent something like '),'
+            if (
+                $terminal_type eq ','
 
-            # handle -icb (indented closing code block braces)
-            # Updated method for indented block braces: indent one full level if
-            # there is no continuation indentation.  This will occur for major
-            # structures such as sub, if, else, but not for things like map
-            # blocks.
-            #
-            # Note: only code blocks without continuation indentation are
-            # handled here (if, else, unless, ..). In the following snippet,
-            # the terminal brace of the sort block will have continuation
-            # indentation as shown so it will not be handled by the coding
-            # here.  We would have to undo the continuation indentation to do
-            # this, but it probably looks ok as is.  This is a possible future
-            # update for semicolon terminated lines.
-            #
-            #     if ($sortby eq 'date' or $sortby eq 'size') {
-            #         @files = sort {
-            #             $file_data{$a}{$sortby} <=> $file_data{$b}{$sortby}
-            #                 or $a cmp $b
-            #                 } @files;
-            #         }
-            #
-            if (   $block_type_beg
-                && $ci_levels_to_go[$i_terminal] == 0 )
-            {
-                my $spaces = get_spaces( $leading_spaces_to_go[$i_terminal] );
-                $indentation = $spaces + $rOpts_indent_columns;
+                # Removed this constraint for -wn
+                # OLD: allow just one character before the comma
+                # && $i_terminal == $ibeg + 1
 
-                # NOTE: for -lp we could create a new indentation object, but
-                # there is probably no need to do it
+                # require LIST environment; otherwise, we may outdent too much -
+                # this can happen in calls without parentheses (overload.t);
+                && $self->is_in_list_by_i($i_terminal)
+              )
+            {
+                $adjust_indentation = 1;
             }
 
-            # handle -icp and any -icb block braces which fall through above
-            # test such as the 'sort' block mentioned above.
-            else {
-
-                # There are currently two ways to handle -icp...
-                # One way is to use the indentation of the previous line:
-                # $indentation = $last_indentation_written;
-
-                # The other way is to use the indentation that the previous line
-                # would have had if it hadn't been adjusted:
-                $indentation = $last_unadjusted_indentation;
+            # undo continuation indentation of a terminal closing token if
+            # it is the last token before a level decrease.  This will allow
+            # a closing token to line up with its opening counterpart, and
+            # avoids an indentation jump larger than 1 level.
+            my $rLL    = $self->[_rLL_];
+            my $Klimit = $self->[_Klimit_];
+            if (   $i_terminal == $ibeg
+                && $is_closing_type_beg
+                && defined($K_beg)
+                && $K_beg < $Klimit )
+            {
+                my $K_plus    = $K_beg + 1;
+                my $type_plus = $rLL->[$K_plus]->[_TYPE_];
 
-                # Current method: use the minimum of the two. This avoids
-                # inconsistent indentation.
-                if ( get_spaces($last_indentation_written) <
-                    get_spaces($indentation) )
-                {
-                    $indentation = $last_indentation_written;
+                if ( $type_plus eq 'b' && $K_plus < $Klimit ) {
+                    $type_plus = $rLL->[ ++$K_plus ]->[_TYPE_];
                 }
-            }
-
-            # use previous indentation but use own level
-            # to cause list to be flushed properly
-            $lev = $level_beg;
-        }
 
-        # remember indentation except for multi-line quotes, which get
-        # no indentation
-        unless ( $ibeg == 0 && $starting_in_quote ) {
-            $last_indentation_written    = $indentation;
-            $last_unadjusted_indentation = $leading_spaces_beg;
-            $last_leading_token          = $token_beg;
+                if ( $type_plus eq '#' && $K_plus < $Klimit ) {
+                    $type_plus = $rLL->[ ++$K_plus ]->[_TYPE_];
+                    if ( $type_plus eq 'b' && $K_plus < $Klimit ) {
+                        $type_plus = $rLL->[ ++$K_plus ]->[_TYPE_];
+                    }
 
-            # Patch to make a line which is the end of a qw quote work with the
-            # -lp option.  Make $token_beg look like a closing token as some
-            # type even if it is not.  This variable will become
-            # $last_leading_token at the end of this loop.  Then, if the -lp
-            # style is selected, and the next line is also a
-            # closing token, it will not get more indentation than this line.
-            # We need to do this because qw quotes (at present) only get
-            # continuation indentation, not one level of indentation, so we
-            # need to turn off the -lp indentation.
+                    # Note: we have skipped past just one comment (perhaps a
+                    # side comment).  There could be more, and we could easily
+                    # skip past all the rest with the following code, or with a
+                    # while loop.  It would be rare to have to do this, and
+                    # those block comments would still be indented, so it would
+                    # to leave them indented.  So it seems best to just stop at
+                    # a maximum of one comment.
+                    ##if ($type_plus eq '#') {
+                    ##   $K_plus = $self->K_next_code($K_plus);
+                    ##}
+                }
 
-            # ... a picture is worth a thousand words:
+                if ( !$is_bli_beg && defined($K_plus) ) {
+                    my $lev        = $level_beg;
+                    my $level_next = $rLL->[$K_plus]->[_LEVEL_];
 
-            # perltidy -wn -gnu (Without this patch):
-            #   ok(defined(
-            #       $seqio = $gb->get_Stream_by_batch([qw(J00522 AF303112
-            #       2981014)])
-            #             ));
+                    # and do not undo ci if it was set by the -xci option
+                    $adjust_indentation = 1
+                      if ( $level_next < $lev
+                        && !$self->[_rseqno_controlling_my_ci_]->{$K_beg} );
+                }
 
-            # perltidy -wn -gnu (With this patch):
-            #  ok(defined(
-            #      $seqio = $gb->get_Stream_by_batch([qw(J00522 AF303112
-            #      2981014)])
-            #  ));
-            if ( $seqno_qw_closing
-                && ( length($token_beg) > 1 || $token_beg eq '>' ) )
+                # Patch for RT #96101, in which closing brace of anonymous subs
+                # was not outdented.  We should look ahead and see if there is
+                # a level decrease at the next token (i.e., a closing token),
+                # but right now we do not have that information.  For now
+                # we see if we are in a list, and this works well.
+                # See test files 'sub*.t' for good test cases.
+                if (  !$rOpts_indent_closing_brace
+                    && $block_type_beg
+                    && $self->[_ris_asub_block_]->{$seqno_beg}
+                    && $self->is_in_list_by_i($i_terminal) )
+                {
+                    (
+                        $opening_indentation, $opening_offset,
+                        $is_leading,          $opening_exists
+                      )
+                      = $self->get_opening_indentation( $ibeg, $ri_first,
+                        $ri_last, $rindentation_list );
+                    my $indentation = $leading_spaces_beg;
+                    if ( defined($opening_indentation)
+                        && get_spaces($indentation) >
+                        get_spaces($opening_indentation) )
+                    {
+                        $adjust_indentation = 1;
+                    }
+                }
+            }
+
+            # YVES patch 1 of 2:
+            # Undo ci of line with leading closing eval brace,
+            # but not beyond the indentation of the line with
+            # the opening brace.
+            if (   $block_type_beg eq 'eval'
+                && !ref($leading_spaces_beg)
+                && !$rOpts_indent_closing_brace )
             {
-                $last_leading_token = ')';
+                (
+                    $opening_indentation, $opening_offset,
+                    $is_leading,          $opening_exists
+                  )
+                  = $self->get_opening_indentation( $ibeg, $ri_first, $ri_last,
+                    $rindentation_list );
+                my $indentation = $leading_spaces_beg;
+                if ( defined($opening_indentation)
+                    && get_spaces($indentation) >
+                    get_spaces($opening_indentation) )
+                {
+                    $adjust_indentation = 1;
+                }
             }
-        }
 
-        # be sure lines with leading closing tokens are not outdented more
-        # than the line which contained the corresponding opening token.
+            # patch for issue git #40: -bli setting has priority
+            $adjust_indentation = 0 if ($is_bli_beg);
 
-        #--------------------------------------------------------
-        # updated per bug report in alex_bug.pl: we must not
-        # mess with the indentation of closing logical braces so
-        # we must treat something like '} else {' as if it were
-        # an isolated brace
-        #--------------------------------------------------------
-        my $is_isolated_block_brace = $block_type_beg
-          && ( $i_terminal == $ibeg
-            || $is_if_elsif_else_unless_while_until_for_foreach{$block_type_beg}
-          );
+            $default_adjust_indentation = $adjust_indentation;
 
-        # only do this for a ':; which is aligned with its leading '?'
-        my $is_unaligned_colon = $type_beg eq ':' && !$is_leading;
+            # Now modify default behavior according to user request:
+            # handle option to indent non-blocks of the form );  };  ];
+            # But don't do special indentation to something like ')->pack('
+            if ( !$block_type_beg ) {
 
-        if (
-            defined($opening_indentation)
-            && !$leading_paren_arrow    # MOJO
-            && !$is_isolated_block_brace
-            && !$is_unaligned_colon
-          )
-        {
-            if ( get_spaces($opening_indentation) > get_spaces($indentation) ) {
-                $indentation = $opening_indentation;
-            }
-        }
+                # Note that logical padding has already been applied, so we may
+                # need to remove some spaces to get a valid hash key.
+                my $tok = $token_beg;
+                my $cti = $closing_token_indentation{$tok};
 
-        # remember the indentation of each line of this batch
-        push @{$rindentation_list}, $indentation;
+                # Fix the value of 'cti' for an isolated non-welded closing qw
+                # delimiter.
+                if ( $seqno_qw_closing && $ibeg_weld_fix == $ibeg ) {
 
-        # outdent lines with certain leading tokens...
-        if (
+                    # A quote delimiter which is not a container will not have
+                    # a cti value defined.  In this case use the style of a
+                    # paren. For example
+                    #   my @fars = (
+                    #      qw<
+                    #        far
+                    #        farfar
+                    #        farfars-far
+                    #      >,
+                    #   );
+                    if ( !defined($cti) && length($tok) == 1 ) {
 
-            # must be first word of this batch
-            $ibeg == 0
+                        # something other than ')', '}', ']' ; use flag for ')'
+                        $cti = $closing_token_indentation{')'};
 
-            # and ...
-            && (
+                        # But for now, do not outdent non-container qw
+                        # delimiters because it would would change existing
+                        # formatting.
+                        if ( $tok ne '>' ) { $cti = 3 }
+                    }
 
-                # certain leading keywords if requested
-                $rOpts_outdent_keywords
-                && $type_beg eq 'k'
-                && $outdent_keyword{$token_beg}
+                    # A non-welded closing qw cannot currently use -cti=1
+                    # because that option requires a sequence number to find
+                    # the opening indentation, and qw quote delimiters are not
+                    # sequenced items.
+                    if ( defined($cti) && $cti == 1 ) { $cti = 0 }
+                }
 
-                # or labels if requested
-                || $rOpts_outdent_labels && $type_beg eq 'J'
+                if ( !defined($cti) ) {
 
-                # or static block comments if requested
-                || $is_static_block_comment
-                && $rOpts_outdent_static_block_comments
-            )
-          )
-        {
-            my $space_count = leading_spaces_to_go($ibeg);
-            if ( $space_count > 0 ) {
-                $space_count -= $rOpts_continuation_indentation;
-                $is_outdented_line = 1;
-                if ( $space_count < 0 ) { $space_count = 0 }
+                    # $cti may not be defined for several reasons.
+                    # -padding may have been applied so the character
+                    #  has a length > 1
+                    # - we may have welded to a closing quote token.
+                    #   Here is an example (perltidy -wn):
+                    #       __PACKAGE__->load_components( qw(
+                    #  >         Core
+                    #  >
+                    #  >     ) );
+                    $adjust_indentation = 0;
 
-                # do not promote a spaced static block comment to non-spaced;
-                # this is not normally necessary but could be for some
-                # unusual user inputs (such as -ci = -i)
-                if ( $type_beg eq '#' && $space_count == 0 ) {
-                    $space_count = 1;
                 }
+                elsif ( $cti == 1 ) {
+                    if (   $i_terminal <= $ibeg + 1
+                        || $is_semicolon_terminated )
+                    {
+                        $adjust_indentation = 2;
+                    }
+                    else {
+                        $adjust_indentation = 0;
+                    }
+                }
+                elsif ( $cti == 2 ) {
+                    if ($is_semicolon_terminated) {
+                        $adjust_indentation = 3;
+                    }
+                    else {
+                        $adjust_indentation = 0;
+                    }
+                }
+                elsif ( $cti == 3 ) {
+                    $adjust_indentation = 3;
+                }
+            }
 
-                $indentation = $space_count;
+            # handle option to indent blocks
+            else {
+                if (
+                    $rOpts_indent_closing_brace
+                    && (
+                        $i_terminal == $ibeg    #  isolated terminal '}'
+                        || $is_semicolon_terminated
+                    )
+                  )                             #  } xxxx ;
+                {
+                    $adjust_indentation = 3;
+                }
             }
+        } ## end if ( $is_closing_type_beg || $seqno_qw_closing )
+
+        # if line begins with a ':', align it with any
+        # previous line leading with corresponding ?
+        elsif ( $type_beg eq ':' ) {
+            (
+                $opening_indentation, $opening_offset,
+                $is_leading,          $opening_exists
+              )
+              = $self->get_opening_indentation( $ibeg, $ri_first, $ri_last,
+                $rindentation_list );
+            if ($is_leading) { $adjust_indentation = 2; }
         }
 
-        return ( $indentation, $lev, $level_end, $terminal_type,
-            $terminal_block_type, $is_semicolon_terminated,
-            $is_outdented_line );
-    } ## end sub final_indentation_adjustment
-} ## end closure final_indentation_adjustment
+        return (
+
+            $adjust_indentation,
+            $default_adjust_indentation,
+            $opening_indentation,
+            $opening_offset,
+            $is_leading,
+            $opening_exists,
+
+        );
+    }
+} ## end closure get_final_indentation
 
 sub get_opening_indentation {
 
@@ -25419,7 +27406,7 @@ sub get_opening_indentation {
     # $rindentation_list - reference to a list containing the indentation
     #            used for each line.
     # $qw_seqno - optional sequence number to use if normal seqno not defined
-    #           (TODO: would be more general to just look this up from index i)
+    #           (NOTE: would be more general to just look this up from index i)
     #
     # return:
     #   -the indentation of the line which contained the opening token
@@ -25451,6 +27438,61 @@ sub get_opening_indentation {
     return ( $indent, $offset, $is_leading, $exists );
 } ## end sub get_opening_indentation
 
+sub examine_vertical_tightness_flags {
+    my ($self) = @_;
+
+    # For efficiency, we will set a flag to skip all calls to sub
+    # 'set_vertical_tightness_flags' if vertical tightness is not possible with
+    # the user input parameters.  If vertical tightness is possible, we will
+    # simply leave the flag undefined and return.
+
+    # Vertical tightness is never possible with --freeze-whitespace
+    if ($rOpts_freeze_whitespace) {
+        $self->[_no_vertical_tightness_flags_] = 1;
+        return;
+    }
+
+    # This sub is coordinated with sub set_vertical_tightness_flags.
+    # The Section numbers in the following comments are the sections
+    # in sub set_vertical_tightness_flags:
+
+    # Examine controls for Section 1a:
+    return if ($rOpts_line_up_parentheses);
+
+    foreach my $key ( keys %opening_vertical_tightness ) {
+        return if ( $opening_vertical_tightness{$key} );
+    }
+
+    # Examine controls for Section 1b:
+    foreach my $key ( keys %closing_vertical_tightness ) {
+        return if ( $closing_vertical_tightness{$key} );
+    }
+
+    # Examine controls for Section 1c:
+    foreach my $key ( keys %opening_token_right ) {
+        return if ( $opening_token_right{$key} );
+    }
+
+    # Examine controls for Section 1d:
+    foreach my $key ( keys %stack_opening_token ) {
+        return if ( $stack_opening_token{$key} );
+    }
+    foreach my $key ( keys %stack_closing_token ) {
+        return if ( $stack_closing_token{$key} );
+    }
+
+    # Examine controls for Section 2:
+    return if ($rOpts_block_brace_vertical_tightness);
+
+    # Examine controls for Section 3:
+    return if ($rOpts_stack_closing_block_brace);
+
+    # None of the controls used for vertical tightness are set, so
+    # we can skip all calls to sub set_vertical_tightness_flags
+    $self->[_no_vertical_tightness_flags_] = 1;
+    return;
+}
+
 sub set_vertical_tightness_flags {
 
     my ( $self, $n, $n_last_line, $ibeg, $iend, $ri_first, $ri_last,
@@ -25458,6 +27500,8 @@ sub set_vertical_tightness_flags {
       = @_;
 
     # Define vertical tightness controls for the nth line of a batch.
+    # Note: do not call this sub for a block comment or if
+    # $rOpts_freeze_whitespace is set.
 
     # These parameters are passed to the vertical aligner to indicated
     # if we should combine this line with the next line to achieve the
@@ -25487,11 +27531,6 @@ sub set_vertical_tightness_flags {
     # continually increase if we allowed it when the -fws flag is set.
     # See case b499 for an example.
 
-    # Speedup: just return for a comment
-    if ( $max_index_to_go == 0 && $types_to_go[0] eq '#' ) {
-        return;
-    }
-
     # Define these values...
     my $vt_type         = 0;
     my $vt_opening_flag = 0;
@@ -25503,13 +27542,11 @@ sub set_vertical_tightness_flags {
     my $vt_min_lines    = 0;
     my $vt_max_lines    = 0;
 
-    goto RETURN
-      if ($rOpts_freeze_whitespace);
-
     # Uses these global parameters:
     #   $rOpts_block_brace_tightness
     #   $rOpts_block_brace_vertical_tightness
     #   $rOpts_stack_closing_block_brace
+    #   $rOpts_line_up_parentheses
     #   %opening_vertical_tightness
     #   %closing_vertical_tightness
     #   %opening_token_right
@@ -25560,17 +27597,19 @@ sub set_vertical_tightness_flags {
               if ( $self->[_rK_weld_left_]->{ $K_to_go[$iend_next] }
                 && $is_closing_type{$type_end_next} );
 
-            # Avoid conflict of -bom and -pt=1 or -pt=2, fixes b1270
-            # See similar patch above for $cvt.
+           # The flag '_rwant_container_open_' avoids conflict of -bom and -pt=1
+           # or -pt=2; fixes b1270. See similar patch above for $cvt.
             my $seqno = $type_sequence_to_go[$iend];
-            if ( $ovt && $self->[_rwant_container_open_]->{$seqno} ) {
+            if (   $ovt
+                && $self->[_rwant_container_open_]->{$seqno} )
+            {
                 $ovt = 0;
             }
 
-            if (   $ovt == 2
-                && $self->[_rreduce_vertical_tightness_by_seqno_]->{$seqno} )
-            {
-                $ovt = 1;
+            # The flag '_rmax_vertical_tightness_' avoids welding conflicts.
+            if ( defined( $self->[_rmax_vertical_tightness_]->{$seqno} ) ) {
+                $ovt =
+                  min( $ovt, $self->[_rmax_vertical_tightness_]->{$seqno} );
             }
 
             unless (
@@ -25603,7 +27642,6 @@ sub set_vertical_tightness_flags {
             && $is_closing_token{$token_next}
             && $types_to_go[$iend] ne '#' )    # for safety, shouldn't happen!
         {
-            my $ovt = $opening_vertical_tightness{$token_next};
             my $cvt = $closing_vertical_tightness{$token_next};
 
             # Avoid conflict of -bom and -pvt=1 or -pvt=2, fixes b977, b1303
@@ -25628,6 +27666,17 @@ sub set_vertical_tightness_flags {
                 $cvt = 1;
             }
 
+            # Fix for b1379, b1380, b1381, b1382, b1384 part 2,
+            # instablility with adding and deleting trailing commas:
+            # Reducing -cvt=2 to =1 fixes stability for -wtc=b in b1379,1380.
+            # Reducing -cvt>0 to =0 fixes stability for -wtc=b in b1381,1382.
+            # Reducing -cvt>0 to =0 fixes stability for -wtc=m in b1384
+            if (   $cvt
+                && $self->[_ris_bare_trailing_comma_by_seqno_]->{$seqno} )
+            {
+                $cvt = 0;
+            }
+
             if (
 
                 # Never append a trailing line like   ')->pack(' because it
@@ -25740,8 +27789,9 @@ sub set_vertical_tightness_flags {
             && $token_end ne '||' && $token_end ne '&&'
 
             # Keep break after '=' if -lp. Fixes b964 b1040 b1062 b1083 b1089.
+            # Generalized from '=' to $is_assignment to fix b1375.
             && !(
-                   $token_end eq '='
+                   $is_assignment{ $types_to_go[$iend] }
                 && $rOpts_line_up_parentheses
                 && $self->[_rlp_object_by_seqno_]
                 ->{ $type_sequence_to_go[$ibeg_next] }
@@ -25875,8 +27925,6 @@ sub set_vertical_tightness_flags {
         $vt_seqno_end = $self->get_seqno( $iend, $ending_in_quote );
     }
 
-  RETURN:
-
     my $rvertical_tightness_flags = {
         _vt_type         => $vt_type,
         _vt_opening_flag => $vt_opening_flag,
@@ -26503,35 +28551,33 @@ sub add_closing_side_comment {
 ## "## perltidy -cscw $year-$month-$day: $tokens_to_go[$max_index_to_go]";
                     }
                 }
-                else {
 
-                    # No differences.. we can safely delete old comment if we
-                    # are below the threshold
-                    if ( $block_line_count <
-                        $rOpts->{'closing-side-comment-interval'} )
+                # No differences.. we can safely delete old comment if we
+                # are below the threshold
+                elsif ( $block_line_count <
+                    $rOpts->{'closing-side-comment-interval'} )
+                {
+                    # Since the line breaks have already been set, we have
+                    # to remove the token from the _to_go array and also
+                    # from the line range (this fixes issue c081).
+                    # Note that we can only get here if -cscw has been set
+                    # because otherwise the old comment is already deleted.
+                    $token = undef;
+                    my $ibeg = $ri_first->[-1];
+                    my $iend = $ri_last->[-1];
+                    if (   $iend > $ibeg
+                        && $iend == $max_index_to_go
+                        && $types_to_go[$max_index_to_go] eq '#' )
                     {
-                        # Since the line breaks have already been set, we have
-                        # to remove the token from the _to_go array and also
-                        # from the line range (this fixes issue c081).
-                        # Note that we can only get here if -cscw has been set
-                        # because otherwise the old comment is already deleted.
-                        $token = undef;
-                        my $ibeg = $ri_first->[-1];
-                        my $iend = $ri_last->[-1];
+                        $iend--;
+                        $max_index_to_go--;
                         if (   $iend > $ibeg
-                            && $iend == $max_index_to_go
-                            && $types_to_go[$max_index_to_go] eq '#' )
+                            && $types_to_go[$max_index_to_go] eq 'b' )
                         {
                             $iend--;
                             $max_index_to_go--;
-                            if (   $iend > $ibeg
-                                && $types_to_go[$max_index_to_go] eq 'b' )
-                            {
-                                $iend--;
-                                $max_index_to_go--;
-                            }
-                            $ri_last->[-1] = $iend;
                         }
+                        $ri_last->[-1] = $iend;
                     }
                 }
             }
@@ -26573,7 +28619,7 @@ sub wrapup {
 
     # This is the last routine called when a file is formatted.
     # Flush buffer and write any informative messages
-    my $self = shift;
+    my ( $self, $severe_error ) = @_;
 
     $self->flush();
     my $file_writer_object = $self->[_file_writer_object_];
@@ -26712,7 +28758,10 @@ sub wrapup {
 
     $file_writer_object->report_line_length_errors();
 
-    $self->[_converged_] = $file_writer_object->get_convergence_check()
+    # Define the formatter self-check for convergence.
+    $self->[_converged_] =
+         $severe_error
+      || $file_writer_object->get_convergence_check()
       || $rOpts->{'indent-only'};
 
     return;
index ac1abb2ba25cd0bf6ea1953069e9693fc6a3d449..62f69ebd6d69a5121ef02176bf12c28a5efdc1cb 100644 (file)
@@ -7,7 +7,7 @@
 package Perl::Tidy::HtmlWriter;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 use English qw( -no_match_vars );
 use File::Basename;
@@ -385,7 +385,6 @@ BEGIN {
     );
 
     # These token types will all be called identifiers for now
-    # FIXME: could separate user defined modules as separate type
     my @identifier = qw< i t U C Y Z G :: CORE::>;
     @token_short_names{@identifier} = ('i') x scalar(@identifier);
 
@@ -754,7 +753,7 @@ sub pod_to_html {
         # this error shouldn't happen ... we just used this filename
         Perl::Tidy::Warn(
             "unable to open temporary file $tmpfile; cannot use pod2html\n");
-        goto RETURN;
+        return $success_flag;
     }
 
     my $html_fh = $self->{_html_fh};
@@ -950,7 +949,6 @@ sub pod_to_html {
         $success_flag = 0;
     }
 
-  RETURN:
     close_object($html_fh);
 
     # note that we have to unlink tmpfile before making frames
index d74960f69ca35c20c9ea053ae8a14e7c161ada6a..983258cab9b2540602818f0bee670bbcc28beebb 100644 (file)
@@ -10,7 +10,7 @@ package Perl::Tidy::IOScalar;
 use strict;
 use warnings;
 use Carp;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 use constant EMPTY_STRING => q{};
 
index 6f9f768ed1e95dd62462e4a5c2c3734ef5dcca33..f110fd70dbaa34138b09e3a9748db1552a05a83e 100644 (file)
@@ -14,7 +14,7 @@ package Perl::Tidy::IOScalarArray;
 use strict;
 use warnings;
 use Carp;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 sub AUTOLOAD {
 
index 635eb296759568b0e4bdd3cafc414db1f376e8b9..2ccd8a1b48d7deb223c43c60a2d521c6a57a2ec0 100644 (file)
@@ -8,7 +8,7 @@
 package Perl::Tidy::IndentationItem;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 BEGIN {
 
@@ -31,6 +31,7 @@ BEGIN {
         _K_begin_line_       => $i++,
         _arrow_count_        => $i++,
         _standard_spaces_    => $i++,
+        _K_extra_space_      => $i++,
     };
 }
 
@@ -102,6 +103,7 @@ sub new {
     $self->[_K_begin_line_]       = $input_hash{K_begin_line};
     $self->[_arrow_count_]        = 0;
     $self->[_standard_spaces_]    = $input_hash{standard_spaces};
+    $self->[_K_extra_space_]      = $input_hash{K_extra_space};
 
     bless $self, $class;
     return $self;
@@ -187,6 +189,7 @@ sub decrease_SPACES {
 
 sub decrease_available_spaces {
     my ( $self, $value ) = @_;
+
     if ( defined($value) ) {
         $self->[_available_spaces_] -= $value;
     }
@@ -238,6 +241,10 @@ sub get_K_begin_line {
     return $_[0]->[_K_begin_line_];
 }
 
+sub get_K_extra_space {
+    return $_[0]->[_K_extra_space_];
+}
+
 sub set_have_child {
     my ( $self, $value ) = @_;
     if ( defined($value) ) {
index bdd51a5174aa5e919e9f9722e70951eb28f6b8b6..a59f992748574bdc0a50d1ecaaeb0204c5b00e0b 100644 (file)
@@ -12,7 +12,7 @@
 package Perl::Tidy::LineBuffer;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 sub AUTOLOAD {
 
index ae0bfd20133dccdb443cace95b8f34a894c20b20..111d75b4fcc111989138974f2e4280504d7e42bf 100644 (file)
@@ -8,7 +8,7 @@
 package Perl::Tidy::LineSink;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 
 sub AUTOLOAD {
 
@@ -41,46 +41,24 @@ sub new {
     my ( $class, @args ) = @_;
 
     my %defaults = (
-        output_file              => undef,
-        line_separator           => undef,
-        rOpts                    => undef,
-        rpending_logfile_message => undef,
-        is_encoded_data          => undef,
+        output_file     => undef,
+        line_separator  => undef,
+        is_encoded_data => undef,
     );
     my %args = ( %defaults, @args );
 
-    my $output_file              = $args{output_file};
-    my $line_separator           = $args{line_separator};
-    my $rOpts                    = $args{rOpts};
-    my $rpending_logfile_message = $args{rpending_logfile_message};
-    my $is_encoded_data          = $args{is_encoded_data};
+    my $output_file     = $args{output_file};
+    my $line_separator  = $args{line_separator};
+    my $is_encoded_data = $args{is_encoded_data};
 
     my $fh = undef;
 
     my $output_file_open = 0;
 
-    if ( $rOpts->{'format'} eq 'tidy' ) {
-        ( $fh, $output_file ) =
-          Perl::Tidy::streamhandle( $output_file, 'w', $is_encoded_data );
-        unless ($fh) { Perl::Tidy::Die("Cannot write to output stream\n"); }
-        $output_file_open = 1;
-    }
-
-    # in order to check output syntax when standard output is used,
-    # or when it is an object, we have to make a copy of the file
-    if ( $output_file eq '-' || ref $output_file ) {
-        if ( $rOpts->{'check-syntax'} ) {
-
-            # Turning off syntax check when standard output is used.
-            # The reason is that temporary files cause problems on
-            # on many systems.
-            $rOpts->{'check-syntax'} = 0;
-            ${$rpending_logfile_message} .= <<EOM;
-Note: --syntax check will be skipped because standard output is used
-EOM
-
-        }
-    }
+    ( $fh, $output_file ) =
+      Perl::Tidy::streamhandle( $output_file, 'w', $is_encoded_data );
+    unless ($fh) { Perl::Tidy::Die("Cannot write to output stream\n"); }
+    $output_file_open = 1;
 
     return bless {
         _fh               => $fh,
index 3306d6b2d4f4f58ba8abe3a923a45cadaa455212..389020d1016dce741ed9f4c6d2ac47958e1cb234 100644 (file)
@@ -8,7 +8,10 @@
 package Perl::Tidy::LineSource;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+use English qw( -no_match_vars );
+our $VERSION = '20221112';
+
+use constant DEVEL_MODE => 0;
 
 sub AUTOLOAD {
 
@@ -41,16 +44,14 @@ sub new {
     my ( $class, @args ) = @_;
 
     my %defaults = (
-        input_file               => undef,
-        rOpts                    => undef,
-        rpending_logfile_message => undef,
+        input_file => undef,
+        rOpts      => undef,
     );
 
     my %args = ( %defaults, @args );
 
-    my $input_file               = $args{input_file};
-    my $rOpts                    = $args{rOpts};
-    my $rpending_logfile_message = $args{rpending_logfile_message};
+    my $input_file = $args{input_file};
+    my $rOpts      = $args{rOpts};
 
     my $input_line_ending;
     if ( $rOpts->{'preserve-line-endings'} ) {
@@ -60,22 +61,6 @@ sub new {
     ( my $fh, $input_file ) = Perl::Tidy::streamhandle( $input_file, 'r' );
     return unless $fh;
 
-    # in order to check output syntax when standard output is used,
-    # or when it is an object, we have to make a copy of the file
-    if ( ( $input_file eq '-' || ref $input_file ) && $rOpts->{'check-syntax'} )
-    {
-
-        # Turning off syntax check when input output is used.
-        # The reason is that temporary files cause problems on
-        # on many systems.
-        $rOpts->{'check-syntax'} = 0;
-
-        ${$rpending_logfile_message} .= <<EOM;
-Note: --syntax check will be skipped because standard input is used
-EOM
-
-    }
-
     return bless {
         _fh                => $fh,
         _filename          => $input_file,
@@ -91,7 +76,10 @@ sub close_input_file {
     # Only close physical files, not STDIN and other objects
     my $filename = $self->{_filename};
     if ( $filename ne '-' && !ref $filename ) {
-        eval { $self->{_fh}->close() };
+        my $ok = eval { $self->{_fh}->close(); 1 };
+        if ( !$ok && DEVEL_MODE ) {
+            Fault("Could not close file handle(): $EVAL_ERROR\n");
+        }
     }
     return;
 }
@@ -124,4 +112,3 @@ sub get_line {
     return $line;
 }
 1;
-
index 194ca81c7e3a4f824f1451b0096fad44b107fc40..f1c43860d03895d12a6b8fea231772028acbfebd 100644 (file)
@@ -7,9 +7,10 @@
 package Perl::Tidy::Logger;
 use strict;
 use warnings;
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 use English qw( -no_match_vars );
 
+use constant DEVEL_MODE   => 0;
 use constant EMPTY_STRING => q{};
 use constant SPACE        => q{ };
 
@@ -458,7 +459,7 @@ sub get_save_logfile {
 sub finish {
 
     # called after all formatting to summarize errors
-    my ( $self, $formatter ) = @_;
+    my ($self) = @_;
 
     my $rOpts         = $self->{_rOpts};
     my $warning_count = $self->{_warning_count};
@@ -496,11 +497,13 @@ sub finish {
             my $routput_array = $self->{_output_array};
             foreach my $line ( @{$routput_array} ) { $fh->print($line) }
             if ( $log_file ne '-' && !ref $log_file ) {
-                eval { $fh->close() };
+                my $ok = eval { $fh->close(); 1 };
+                if ( !$ok && DEVEL_MODE ) {
+                    Fault("Could not close file handle(): $EVAL_ERROR\n");
+                }
             }
         }
     }
     return;
 }
 1;
-
index be828299e7b9bc3ad7b0ea0b84f7ddf6e5d8fdb3..41c684afb4498f88128553e126948e6eedb6f081 100644 (file)
@@ -23,14 +23,20 @@ use strict;
 use warnings;
 use English qw( -no_match_vars );
 
-our $VERSION = '20220613';
+our $VERSION = '20221112';
+
+use Perl::Tidy::LineBuffer;
+use Carp;
 
 use constant DEVEL_MODE   => 0;
 use constant EMPTY_STRING => q{};
 use constant SPACE        => q{ };
 
-use Perl::Tidy::LineBuffer;
-use Carp;
+# Decimal values of some ascii characters for quick checks
+use constant ORD_TAB           => 9;
+use constant ORD_SPACE         => 32;
+use constant ORD_PRINTABLE_MIN => 33;
+use constant ORD_PRINTABLE_MAX => 126;
 
 # PACKAGE VARIABLES for processing an entire FILE.
 # These must be package variables because most may get localized during
@@ -281,8 +287,8 @@ sub bad_pattern {
     # but it should be safe because the pattern has been constructed
     # by this program.
     my ($pattern) = @_;
-    eval "'##'=~/$pattern/";
-    return $EVAL_ERROR;
+    my $ok = eval "'##'=~/$pattern/";
+    return !defined($ok) || $EVAL_ERROR;
 }
 
 sub make_code_skipping_pattern {
@@ -466,7 +472,7 @@ sub new {
     $tokenizer_self = $self;
 
     prepare_for_a_new_file();
-    find_starting_indentation_level();
+    $self->find_starting_indentation_level();
 
     # This is not a full class yet, so die if an attempt is made to
     # create more than one object.
@@ -781,26 +787,29 @@ sub get_input_line_number {
     return $tokenizer_self->[_last_line_number_];
 }
 
+sub log_numbered_msg {
+    my ( $self, $msg ) = @_;
+
+    # write input line number + message to logfile
+    my $input_line_number = $self->[_last_line_number_];
+    write_logfile_entry("Line $input_line_number: $msg");
+    return;
+}
+
 # returns the next tokenized line
 sub get_line {
 
     my $self = shift;
 
-    # USES GLOBAL VARIABLES: $tokenizer_self, $brace_depth,
-    # $square_bracket_depth, $paren_depth
+    # USES GLOBAL VARIABLES:
+    #   $brace_depth, $square_bracket_depth, $paren_depth
 
-    my $input_line = $tokenizer_self->[_line_buffer_object_]->get_line();
-    $tokenizer_self->[_line_of_text_] = $input_line;
+    my $input_line = $self->[_line_buffer_object_]->get_line();
+    $self->[_line_of_text_] = $input_line;
 
     return unless ($input_line);
 
-    my $input_line_number = ++$tokenizer_self->[_last_line_number_];
-
-    my $write_logfile_entry = sub {
-        my ($msg) = @_;
-        write_logfile_entry("Line $input_line_number: $msg");
-        return;
-    };
+    my $input_line_number = ++$self->[_last_line_number_];
 
     # Find and remove what characters terminate this line, including any
     # control r
@@ -820,7 +829,7 @@ sub get_line {
     # for backwards compatibility we keep the line text terminated with
     # a newline character
     $input_line .= "\n";
-    $tokenizer_self->[_line_of_text_] = $input_line;    # update
+    $self->[_line_of_text_] = $input_line;
 
     # create a data structure describing this line which will be
     # returned to the caller.
@@ -860,26 +869,23 @@ sub get_line {
         _square_bracket_depth      => $square_bracket_depth,
         _paren_depth               => $paren_depth,
         _quote_character           => EMPTY_STRING,
-##        _rtoken_type               => undef,
-##        _rtokens                   => undef,
-##        _rlevels                   => undef,
-##        _rblock_type               => undef,
-##        _rcontainer_type           => undef,
-##        _rcontainer_environment    => undef,
-##        _rtype_sequence            => undef,
-##        _rnesting_tokens           => undef,
-##        _rci_levels                => undef,
-##        _rnesting_blocks           => undef,
-##        _starting_in_quote         => 0,
-##        _ending_in_quote           => 0,
+## Skip these needless initializations for efficiency:
+##      _rtoken_type               => undef,
+##      _rtokens                   => undef,
+##      _rlevels                   => undef,
+##      _rblock_type               => undef,
+##      _rtype_sequence            => undef,
+##      _rci_levels                => undef,
+##      _starting_in_quote         => 0,
+##      _ending_in_quote           => 0,
     };
 
     # must print line unchanged if we are in a here document
-    if ( $tokenizer_self->[_in_here_doc_] ) {
+    if ( $self->[_in_here_doc_] ) {
 
         $line_of_tokens->{_line_type} = 'HERE';
-        my $here_doc_target      = $tokenizer_self->[_here_doc_target_];
-        my $here_quote_character = $tokenizer_self->[_here_quote_character_];
+        my $here_doc_target      = $self->[_here_doc_target_];
+        my $here_quote_character = $self->[_here_quote_character_];
         my $candidate_target     = $input_line;
         chomp $candidate_target;
 
@@ -889,27 +895,26 @@ sub get_line {
             $candidate_target =~ s/^\s*//;
         }
         if ( $candidate_target eq $here_doc_target ) {
-            $tokenizer_self->[_nearly_matched_here_target_at_] = undef;
+            $self->[_nearly_matched_here_target_at_] = undef;
             $line_of_tokens->{_line_type} = 'HERE_END';
-            $write_logfile_entry->("Exiting HERE document $here_doc_target\n");
+            $self->log_numbered_msg("Exiting HERE document $here_doc_target\n");
 
-            my $rhere_target_list = $tokenizer_self->[_rhere_target_list_];
+            my $rhere_target_list = $self->[_rhere_target_list_];
             if ( @{$rhere_target_list} ) {  # there can be multiple here targets
                 ( $here_doc_target, $here_quote_character ) =
                   @{ shift @{$rhere_target_list} };
-                $tokenizer_self->[_here_doc_target_] = $here_doc_target;
-                $tokenizer_self->[_here_quote_character_] =
-                  $here_quote_character;
-                $write_logfile_entry->(
+                $self->[_here_doc_target_]      = $here_doc_target;
+                $self->[_here_quote_character_] = $here_quote_character;
+                $self->log_numbered_msg(
                     "Entering HERE document $here_doc_target\n");
-                $tokenizer_self->[_nearly_matched_here_target_at_] = undef;
-                $tokenizer_self->[_started_looking_for_here_target_at_] =
+                $self->[_nearly_matched_here_target_at_] = undef;
+                $self->[_started_looking_for_here_target_at_] =
                   $input_line_number;
             }
             else {
-                $tokenizer_self->[_in_here_doc_]          = 0;
-                $tokenizer_self->[_here_doc_target_]      = EMPTY_STRING;
-                $tokenizer_self->[_here_quote_character_] = EMPTY_STRING;
+                $self->[_in_here_doc_]          = 0;
+                $self->[_here_doc_target_]      = EMPTY_STRING;
+                $self->[_here_quote_character_] = EMPTY_STRING;
             }
         }
 
@@ -919,24 +924,23 @@ sub get_line {
             $candidate_target =~ s/\s*$//;
             $candidate_target =~ s/^\s*//;
             if ( $candidate_target eq $here_doc_target ) {
-                $tokenizer_self->[_nearly_matched_here_target_at_] =
-                  $input_line_number;
+                $self->[_nearly_matched_here_target_at_] = $input_line_number;
             }
         }
         return $line_of_tokens;
     }
 
     # Print line unchanged if we are in a format section
-    elsif ( $tokenizer_self->[_in_format_] ) {
+    elsif ( $self->[_in_format_] ) {
 
         if ( $input_line =~ /^\.[\s#]*$/ ) {
 
             # Decrement format depth count at a '.' after a 'format'
-            $tokenizer_self->[_in_format_]--;
+            $self->[_in_format_]--;
 
             # This is the end when count reaches 0
-            if ( !$tokenizer_self->[_in_format_] ) {
-                $write_logfile_entry->("Exiting format section\n");
+            if ( !$self->[_in_format_] ) {
+                $self->log_numbered_msg("Exiting format section\n");
                 $line_of_tokens->{_line_type} = 'FORMAT_END';
             }
         }
@@ -946,22 +950,22 @@ sub get_line {
 
                 # Increment format depth count at a 'format' within a 'format'
                 # This is a simple way to handle nested formats (issue c019).
-                $tokenizer_self->[_in_format_]++;
+                $self->[_in_format_]++;
             }
         }
         return $line_of_tokens;
     }
 
     # must print line unchanged if we are in pod documentation
-    elsif ( $tokenizer_self->[_in_pod_] ) {
+    elsif ( $self->[_in_pod_] ) {
 
         $line_of_tokens->{_line_type} = 'POD';
         if ( $input_line =~ /^=cut/ ) {
             $line_of_tokens->{_line_type} = 'POD_END';
-            $write_logfile_entry->("Exiting POD section\n");
-            $tokenizer_self->[_in_pod_] = 0;
+            $self->log_numbered_msg("Exiting POD section\n");
+            $self->[_in_pod_] = 0;
         }
-        if ( $input_line =~ /^\#\!.*perl\b/ && !$tokenizer_self->[_in_end_] ) {
+        if ( $input_line =~ /^\#\!.*perl\b/ && !$self->[_in_end_] ) {
             warning(
                 "Hash-bang in pod can cause older versions of perl to fail! \n"
             );
@@ -971,13 +975,13 @@ sub get_line {
     }
 
     # print line unchanged if in skipped section
-    elsif ( $tokenizer_self->[_in_skipped_] ) {
+    elsif ( $self->[_in_skipped_] ) {
 
         $line_of_tokens->{_line_type} = 'SKIP';
         if ( $input_line =~ /$code_skipping_pattern_end/ ) {
             $line_of_tokens->{_line_type} = 'SKIP_END';
-            $write_logfile_entry->("Exiting code-skipping section\n");
-            $tokenizer_self->[_in_skipped_] = 0;
+            $self->log_numbered_msg("Exiting code-skipping section\n");
+            $self->[_in_skipped_] = 0;
         }
         return $line_of_tokens;
     }
@@ -986,13 +990,13 @@ sub get_line {
     # are seeing illegal tokens and cannot continue.  Syntax errors do
     # not pass this route).  Calling routine can decide what to do, but
     # the default can be to just pass all lines as if they were after __END__
-    elsif ( $tokenizer_self->[_in_error_] ) {
+    elsif ( $self->[_in_error_] ) {
         $line_of_tokens->{_line_type} = 'ERROR';
         return $line_of_tokens;
     }
 
     # print line unchanged if we are __DATA__ section
-    elsif ( $tokenizer_self->[_in_data_] ) {
+    elsif ( $self->[_in_data_] ) {
 
         # ...but look for POD
         # Note that the _in_data and _in_end flags remain set
@@ -1000,8 +1004,8 @@ sub get_line {
         # end of a pod section
         if ( $input_line =~ /^=(\w+)\b/ && $1 ne 'cut' ) {
             $line_of_tokens->{_line_type} = 'POD_START';
-            $write_logfile_entry->("Entering POD section\n");
-            $tokenizer_self->[_in_pod_] = 1;
+            $self->log_numbered_msg("Entering POD section\n");
+            $self->[_in_pod_] = 1;
             return $line_of_tokens;
         }
         else {
@@ -1011,7 +1015,7 @@ sub get_line {
     }
 
     # print line unchanged if we are in __END__ section
-    elsif ( $tokenizer_self->[_in_end_] ) {
+    elsif ( $self->[_in_end_] ) {
 
         # ...but look for POD
         # Note that the _in_data and _in_end flags remain set
@@ -1019,8 +1023,8 @@ sub get_line {
         # end of a pod section
         if ( $input_line =~ /^=(\w+)\b/ && $1 ne 'cut' ) {
             $line_of_tokens->{_line_type} = 'POD_START';
-            $write_logfile_entry->("Entering POD section\n");
-            $tokenizer_self->[_in_pod_] = 1;
+            $self->log_numbered_msg("Entering POD section\n");
+            $self->[_in_pod_] = 1;
             return $line_of_tokens;
         }
         else {
@@ -1030,17 +1034,17 @@ sub get_line {
     }
 
     # check for a hash-bang line if we haven't seen one
-    if ( !$tokenizer_self->[_saw_hash_bang_] ) {
+    if ( !$self->[_saw_hash_bang_] ) {
         if ( $input_line =~ /^\#\!.*perl\b/ ) {
-            $tokenizer_self->[_saw_hash_bang_] = $input_line_number;
+            $self->[_saw_hash_bang_] = $input_line_number;
 
             # check for -w and -P flags
             if ( $input_line =~ /^\#\!.*perl\s.*-.*P/ ) {
-                $tokenizer_self->[_saw_perl_dash_P_] = 1;
+                $self->[_saw_perl_dash_P_] = 1;
             }
 
             if ( $input_line =~ /^\#\!.*perl\s.*-.*w/ ) {
-                $tokenizer_self->[_saw_perl_dash_w_] = 1;
+                $self->[_saw_perl_dash_w_] = 1;
             }
 
             if (
@@ -1052,7 +1056,7 @@ sub get_line {
                        $last_nonblank_block_type
                     && $last_nonblank_block_type eq 'BEGIN'
                 )
-                && !$tokenizer_self->[_look_for_hash_bang_]
+                && !$self->[_look_for_hash_bang_]
 
                 # Try to avoid giving a false alarm at a simple comment.
                 # These look like valid hash-bang lines:
@@ -1073,7 +1077,7 @@ sub get_line {
 
                 # this is helpful for VMS systems; we may have accidentally
                 # tokenized some DCL commands
-                if ( $tokenizer_self->[_started_tokenizing_] ) {
+                if ( $self->[_started_tokenizing_] ) {
                     warning(
 "There seems to be a hash-bang after line 1; do you need to run with -x ?\n"
                     );
@@ -1093,8 +1097,8 @@ sub get_line {
     }
 
     # wait for a hash-bang before parsing if the user invoked us with -x
-    if ( $tokenizer_self->[_look_for_hash_bang_]
-        && !$tokenizer_self->[_saw_hash_bang_] )
+    if ( $self->[_look_for_hash_bang_]
+        && !$self->[_saw_hash_bang_] )
     {
         $line_of_tokens->{_line_type} = 'SYSTEM';
         return $line_of_tokens;
@@ -1117,33 +1121,32 @@ sub get_line {
     #        _in_skipped_
     #        _in_pod_
     #        _in_quote_
-    my $ending_in_quote_last = $tokenizer_self->[_in_quote_];
-    tokenize_this_line($line_of_tokens);
+    my $ending_in_quote_last = $self->[_in_quote_];
+    $self->tokenize_this_line($line_of_tokens);
 
     # Now finish defining the return structure and return it
-    $line_of_tokens->{_ending_in_quote} = $tokenizer_self->[_in_quote_];
+    $line_of_tokens->{_ending_in_quote} = $self->[_in_quote_];
 
     # handle severe error (binary data in script)
-    if ( $tokenizer_self->[_in_error_] ) {
-        $tokenizer_self->[_in_quote_] = 0;    # to avoid any more messages
+    if ( $self->[_in_error_] ) {
+        $self->[_in_quote_] = 0;    # to avoid any more messages
         warning("Giving up after error\n");
         $line_of_tokens->{_line_type} = 'ERROR';
-        reset_indentation_level(0);           # avoid error messages
+        reset_indentation_level(0);    # avoid error messages
         return $line_of_tokens;
     }
 
     # handle start of pod documentation
-    if ( $tokenizer_self->[_in_pod_] ) {
+    if ( $self->[_in_pod_] ) {
 
         # This gets tricky..above a __DATA__ or __END__ section, perl
         # accepts '=cut' as the start of pod section. But afterwards,
         # only pod utilities see it and they may ignore an =cut without
         # leading =head.  In any case, this isn't good.
         if ( $input_line =~ /^=cut\b/ ) {
-            if ( $tokenizer_self->[_saw_data_] || $tokenizer_self->[_saw_end_] )
-            {
+            if ( $self->[_saw_data_] || $self->[_saw_end_] ) {
                 complain("=cut while not in pod ignored\n");
-                $tokenizer_self->[_in_pod_] = 0;
+                $self->[_in_pod_] = 0;
                 $line_of_tokens->{_line_type} = 'POD_END';
             }
             else {
@@ -1151,67 +1154,66 @@ sub get_line {
                 warning(
 "=cut starts a pod section .. this can fool pod utilities.\n"
                 ) unless (DEVEL_MODE);
-                $write_logfile_entry->("Entering POD section\n");
+                $self->log_numbered_msg("Entering POD section\n");
             }
         }
 
         else {
             $line_of_tokens->{_line_type} = 'POD_START';
-            $write_logfile_entry->("Entering POD section\n");
+            $self->log_numbered_msg("Entering POD section\n");
         }
 
         return $line_of_tokens;
     }
 
     # handle start of skipped section
-    if ( $tokenizer_self->[_in_skipped_] ) {
+    if ( $self->[_in_skipped_] ) {
 
         $line_of_tokens->{_line_type} = 'SKIP';
-        $write_logfile_entry->("Entering code-skipping section\n");
+        $self->log_numbered_msg("Entering code-skipping section\n");
         return $line_of_tokens;
     }
 
     # see if this line contains here doc targets
-    my $rhere_target_list = $tokenizer_self->[_rhere_target_list_];
+    my $rhere_target_list = $self->[_rhere_target_list_];
     if ( @{$rhere_target_list} ) {
 
         my ( $here_doc_target, $here_quote_character ) =
           @{ shift @{$rhere_target_list} };
-        $tokenizer_self->[_in_here_doc_]          = 1;
-        $tokenizer_self->[_here_doc_target_]      = $here_doc_target;
-        $tokenizer_self->[_here_quote_character_] = $here_quote_character;
-        $write_logfile_entry->("Entering HERE document $here_doc_target\n");
-        $tokenizer_self->[_started_looking_for_here_target_at_] =
-          $input_line_number;
+        $self->[_in_here_doc_]          = 1;
+        $self->[_here_doc_target_]      = $here_doc_target;
+        $self->[_here_quote_character_] = $here_quote_character;
+        $self->log_numbered_msg("Entering HERE document $here_doc_target\n");
+        $self->[_started_looking_for_here_target_at_] = $input_line_number;
     }
 
     # NOTE: __END__ and __DATA__ statements are written unformatted
     # because they can theoretically contain additional characters
     # which are not tokenized (and cannot be read with <DATA> either!).
-    if ( $tokenizer_self->[_in_data_] ) {
+    if ( $self->[_in_data_] ) {
         $line_of_tokens->{_line_type} = 'DATA_START';
-        $write_logfile_entry->("Starting __DATA__ section\n");
-        $tokenizer_self->[_saw_data_] = 1;
+        $self->log_numbered_msg("Starting __DATA__ section\n");
+        $self->[_saw_data_] = 1;
 
         # keep parsing after __DATA__ if use SelfLoader was seen
-        if ( $tokenizer_self->[_saw_selfloader_] ) {
-            $tokenizer_self->[_in_data_] = 0;
-            $write_logfile_entry->(
+        if ( $self->[_saw_selfloader_] ) {
+            $self->[_in_data_] = 0;
+            $self->log_numbered_msg(
                 "SelfLoader seen, continuing; -nlsl deactivates\n");
         }
 
         return $line_of_tokens;
     }
 
-    elsif ( $tokenizer_self->[_in_end_] ) {
+    elsif ( $self->[_in_end_] ) {
         $line_of_tokens->{_line_type} = 'END_START';
-        $write_logfile_entry->("Starting __END__ section\n");
-        $tokenizer_self->[_saw_end_] = 1;
+        $self->log_numbered_msg("Starting __END__ section\n");
+        $self->[_saw_end_] = 1;
 
         # keep parsing after __END__ if use AutoLoader was seen
-        if ( $tokenizer_self->[_saw_autoloader_] ) {
-            $tokenizer_self->[_in_end_] = 0;
-            $write_logfile_entry->(
+        if ( $self->[_saw_autoloader_] ) {
+            $self->[_in_end_] = 0;
+            $self->log_numbered_msg(
                 "AutoLoader seen, continuing; -nlal deactivates\n");
         }
         return $line_of_tokens;
@@ -1221,42 +1223,39 @@ sub get_line {
     $line_of_tokens->{_line_type} = 'CODE';
 
     # remember if we have seen any real code
-    if (  !$tokenizer_self->[_started_tokenizing_]
+    if (  !$self->[_started_tokenizing_]
         && $input_line !~ /^\s*$/
         && $input_line !~ /^\s*#/ )
     {
-        $tokenizer_self->[_started_tokenizing_] = 1;
+        $self->[_started_tokenizing_] = 1;
     }
 
-    if ( $tokenizer_self->[_debugger_object_] ) {
-        $tokenizer_self->[_debugger_object_]
-          ->write_debug_entry($line_of_tokens);
+    if ( $self->[_debugger_object_] ) {
+        $self->[_debugger_object_]->write_debug_entry($line_of_tokens);
     }
 
     # Note: if keyword 'format' occurs in this line code, it is still CODE
     # (keyword 'format' need not start a line)
-    if ( $tokenizer_self->[_in_format_] ) {
-        $write_logfile_entry->("Entering format section\n");
+    if ( $self->[_in_format_] ) {
+        $self->log_numbered_msg("Entering format section\n");
     }
 
-    if ( $tokenizer_self->[_in_quote_]
-        and ( $tokenizer_self->[_line_start_quote_] < 0 ) )
+    if ( $self->[_in_quote_]
+        and ( $self->[_line_start_quote_] < 0 ) )
     {
 
         #if ( ( my $quote_target = get_quote_target() ) !~ /^\s*$/ ) {
-        if ( ( my $quote_target = $tokenizer_self->[_quote_target_] ) !~
-            /^\s*$/ )
-        {
-            $tokenizer_self->[_line_start_quote_] = $input_line_number;
-            $write_logfile_entry->(
+        if ( ( my $quote_target = $self->[_quote_target_] ) !~ /^\s*$/ ) {
+            $self->[_line_start_quote_] = $input_line_number;
+            $self->log_numbered_msg(
                 "Start multi-line quote or pattern ending in $quote_target\n");
         }
     }
-    elsif ( ( $tokenizer_self->[_line_start_quote_] >= 0 )
-        && !$tokenizer_self->[_in_quote_] )
+    elsif ( ( $self->[_line_start_quote_] >= 0 )
+        && !$self->[_in_quote_] )
     {
-        $tokenizer_self->[_line_start_quote_] = -1;
-        $write_logfile_entry->("End of multi-line quote or pattern\n");
+        $self->[_line_start_quote_] = -1;
+        $self->log_numbered_msg("End of multi-line quote or pattern\n");
     }
 
     # we are returning a line of CODE
@@ -1271,17 +1270,17 @@ sub find_starting_indentation_level {
     # example) it may not be zero.  The user may specify this with the
     # -sil=n parameter but normally doesn't so we have to guess.
     #
-    # USES GLOBAL VARIABLES: $tokenizer_self
+    my ($self) = @_;
     my $starting_level = 0;
 
     # use value if given as parameter
-    if ( $tokenizer_self->[_know_starting_level_] ) {
-        $starting_level = $tokenizer_self->[_starting_level_];
+    if ( $self->[_know_starting_level_] ) {
+        $starting_level = $self->[_starting_level_];
     }
 
     # if we know there is a hash_bang line, the level must be zero
-    elsif ( $tokenizer_self->[_look_for_hash_bang_] ) {
-        $tokenizer_self->[_know_starting_level_] = 1;
+    elsif ( $self->[_look_for_hash_bang_] ) {
+        $self->[_know_starting_level_] = 1;
     }
 
     # otherwise figure it out from the input file
@@ -1291,9 +1290,7 @@ sub find_starting_indentation_level {
 
         # keep looking at lines until we find a hash bang or piece of code
         my $msg = EMPTY_STRING;
-        while ( $line =
-            $tokenizer_self->[_line_buffer_object_]->peek_ahead( $i++ ) )
-        {
+        while ( $line = $self->[_line_buffer_object_]->peek_ahead( $i++ ) ) {
 
             # if first line is #! then assume starting level is zero
             if ( $i == 1 && $line =~ /^\#\!/ ) {
@@ -1308,7 +1305,7 @@ sub find_starting_indentation_level {
         $msg = "Line $i implies starting-indentation-level = $starting_level\n";
         write_logfile_entry("$msg");
     }
-    $tokenizer_self->[_starting_level_] = $starting_level;
+    $self->[_starting_level_] = $starting_level;
     reset_indentation_level($starting_level);
     return;
 } ## end sub find_starting_indentation_level
@@ -1488,7 +1485,7 @@ sub prepare_for_a_new_file {
     # TV4: SCALARS for multi-line identifiers and
     # statements. These are initialized with a subroutine call
     # and continually updated as lines are processed.
-    my ( $id_scan_state, $identifier, $want_paren, $indented_if_level );
+    my ( $id_scan_state, $identifier, $want_paren );
 
     # TV5: SCALARS for tracking indentation level.
     # Initialized once and continually updated as lines are
@@ -1531,10 +1528,9 @@ sub prepare_for_a_new_file {
         $allowed_quote_modifiers = EMPTY_STRING;
 
         # TV4:
-        $id_scan_state     = EMPTY_STRING;
-        $identifier        = EMPTY_STRING;
-        $want_paren        = EMPTY_STRING;
-        $indented_if_level = 0;
+        $id_scan_state = EMPTY_STRING;
+        $identifier    = EMPTY_STRING;
+        $want_paren    = EMPTY_STRING;
 
         # TV5:
         $nesting_token_string   = EMPTY_STRING;
@@ -1587,8 +1583,7 @@ sub prepare_for_a_new_file {
             $quoted_string_2, $allowed_quote_modifiers,
         ];
 
-        my $rTV4 =
-          [ $id_scan_state, $identifier, $want_paren, $indented_if_level ];
+        my $rTV4 = [ $id_scan_state, $identifier, $want_paren ];
 
         my $rTV5 = [
             $nesting_token_string,      $nesting_type_string,
@@ -1636,8 +1631,7 @@ sub prepare_for_a_new_file {
             $quoted_string_1, $quoted_string_2, $allowed_quote_modifiers,
         ) = @{$rTV3};
 
-        ( $id_scan_state, $identifier, $want_paren, $indented_if_level ) =
-          @{$rTV4};
+        ( $id_scan_state, $identifier, $want_paren ) = @{$rTV4};
 
         (
             $nesting_token_string,      $nesting_type_string,
@@ -1746,9 +1740,6 @@ EOM
     } ## end sub split_pretoken
 
     sub get_indentation_level {
-
-        # patch to avoid reporting error if indented if is not terminated
-        if ($indented_if_level) { return $level_in_tokenizer - 1 }
         return $level_in_tokenizer;
     }
 
@@ -1863,25 +1854,25 @@ EOM
     );
 
     my %is_for_foreach;
-    @_ = qw(for foreach);
-    @is_for_foreach{@_} = (1) x scalar(@_);
+    @q = qw(for foreach);
+    @is_for_foreach{@q} = (1) x scalar(@q);
 
     my %is_my_our_state;
-    @_ = qw(my our state);
-    @is_my_our_state{@_} = (1) x scalar(@_);
+    @q = qw(my our state);
+    @is_my_our_state{@q} = (1) x scalar(@q);
 
     # These keywords may introduce blocks after parenthesized expressions,
     # in the form:
     # keyword ( .... ) { BLOCK }
     # patch for SWITCH/CASE: added 'switch' 'case' 'given' 'when'
     my %is_blocktype_with_paren;
-    @_ =
+    @q =
       qw(if elsif unless while until for foreach switch case given when catch);
-    @is_blocktype_with_paren{@_} = (1) x scalar(@_);
+    @is_blocktype_with_paren{@q} = (1) x scalar(@q);
 
     my %is_case_default;
-    @_ = qw(case default);
-    @is_case_default{@_} = (1) x scalar(@_);
+    @q = qw(case default);
+    @is_case_default{@q} = (1) x scalar(@q);
 
     #------------------------
     # end of tokenizer hashes
@@ -1936,12 +1927,10 @@ EOM
         _decrement_count();    # avoid error check for multiple tokenizers
 
         # make a new tokenizer
-        my $rOpts = {};
-        my $rpending_logfile_message;
+        my $rOpts         = {};
         my $source_object = Perl::Tidy::LineSource->new(
-            input_file               => \$replacement_text,
-            rOpts                    => $rOpts,
-            rpending_logfile_message => $rpending_logfile_message,
+            input_file => \$replacement_text,
+            rOpts      => $rOpts,
         );
         my $tokenizer = Perl::Tidy::Tokenizer->new(
             source_object        => $source_object,
@@ -2047,24 +2036,41 @@ EOM
         # This gives the same results as the full scanner in about 1/4 the
         # total runtime for a typical input stream.
 
+        # Notation:
+        #     $var * 2
+        #     ^^   ^
+        #     ||  |
+        #     ||  ---- $i_next [= next nonblank pretoken ]
+        #     |----$i_plus_1 [= a bareword ]
+        #     ---$i_begin [= a sigil]
+
         my $i_begin   = $i;
         my $tok_begin = $tok;
+        my $i_plus_1  = $i + 1;
         my $fast_scan_type;
 
-        ###############################
+        #-------------------------------------------------------
+        # Do full scan for anything following a pointer, such as
+        #      $cref->&*;    # a postderef
+        #-------------------------------------------------------
+        if ( $last_nonblank_token eq '->' ) {
+
+        }
+
+        #------------------------------
         # quick scan with leading sigil
-        ###############################
-        if (  !$id_scan_state
-            && $i + 1 <= $max_token_index
+        #------------------------------
+        elsif ( !$id_scan_state
+            && $i_plus_1 <= $max_token_index
             && $fast_scan_context{$tok} )
         {
             $context = $fast_scan_context{$tok};
 
             # look for $var, @var, ...
-            if ( $rtoken_type->[ $i + 1 ] eq 'w' ) {
+            if ( $rtoken_type->[$i_plus_1] eq 'w' ) {
                 my $pretype_next = EMPTY_STRING;
-                my $i_next       = $i + 2;
-                if ( $i_next <= $max_token_index ) {
+                if ( $i_plus_1 < $max_token_index ) {
+                    my $i_next = $i_plus_1 + 1;
                     if (   $rtoken_type->[$i_next] eq 'b'
                         && $i_next < $max_token_index )
                     {
@@ -2075,10 +2081,10 @@ EOM
                 if ( $pretype_next ne ':' && $pretype_next ne "'" ) {
 
                     # Found type 'i' like '$var', '@var', or '%var'
-                    $identifier     = $tok . $rtokens->[ $i + 1 ];
+                    $identifier     = $tok . $rtokens->[$i_plus_1];
                     $tok            = $identifier;
                     $type           = 'i';
-                    $i              = $i + 1;
+                    $i              = $i_plus_1;
                     $fast_scan_type = $type;
                 }
             }
@@ -2087,7 +2093,7 @@ EOM
             # But we must let the full scanner handle things ${ because it may
             # keep going to get a complete identifier like '${#}'  .
             elsif (
-                $rtoken_type->[ $i + 1 ] eq '{'
+                $rtoken_type->[$i_plus_1] eq '{'
                 && (   $tok_begin eq '@'
                     || $tok_begin eq '%' )
               )
@@ -2099,15 +2105,15 @@ EOM
             }
         }
 
-        ############################
+        #---------------------------
         # Quick scan with leading ->
         # Look for ->[ and ->{
-        ############################
+        #---------------------------
         elsif (
                $tok eq '->'
             && $i < $max_token_index
-            && (   $rtokens->[ $i + 1 ] eq '{'
-                || $rtokens->[ $i + 1 ] eq '[' )
+            && (   $rtokens->[$i_plus_1] eq '{'
+                || $rtokens->[$i_plus_1] eq '[' )
           )
         {
             $type           = $tok;
@@ -2116,9 +2122,9 @@ EOM
             $context        = UNKNOWN_CONTEXT;
         }
 
-        #######################################
+        #--------------------------------------
         # Verify correctness during development
-        #######################################
+        #--------------------------------------
         if ( VERIFY_FASTSCAN && $fast_scan_type ) {
 
             # We will call the full method
@@ -2146,9 +2152,9 @@ EOM
             }
         }
 
-        ###################################################
+        #-------------------------------------------------
         # call full scanner if fast method did not succeed
-        ###################################################
+        #-------------------------------------------------
         if ( !$fast_scan_type ) {
             scan_identifier();
         }
@@ -2182,9 +2188,9 @@ EOM
         my $tok_begin = $tok;
         my $number;
 
-        ##################################
+        #---------------------------------
         # Quick check for (signed) integer
-        ##################################
+        #---------------------------------
 
         # This will be the string of digits:
         my $i_d   = $i;
@@ -2225,9 +2231,9 @@ EOM
             }
         }
 
-        #######################################
+        #--------------------------------------
         # Verify correctness during development
-        #######################################
+        #--------------------------------------
         if ( VERIFY_FASTNUM && defined($number) ) {
 
             # We will call the full method
@@ -2251,9 +2257,9 @@ EOM
             }
         }
 
-        #########################################
+        #----------------------------------------
         # call full scanner if may not be integer
-        #########################################
+        #----------------------------------------
         if ( !defined($number) ) {
             $number = scan_number();
         }
@@ -2328,7 +2334,8 @@ EOM
         # (vorboard.pl, sort.t).  Something like:
         #   /^(print|printf|sort|exec|system)$/
         if (
-            $is_indirect_object_taker{$last_nonblank_token}
+               $is_indirect_object_taker{$last_nonblank_token}
+            && $last_nonblank_type eq 'k'
             || ( ( $last_nonblank_token eq '(' )
                 && $is_indirect_object_taker{ $paren_type[$paren_depth] } )
             || (   $last_nonblank_type eq 'w'
@@ -2372,7 +2379,6 @@ EOM
                 # are not marked as a block, we might have a method call.
                 # Added ')' to fix case c017, something like ()()()
                 && $last_nonblank_token !~ /^([\]\}\)\&]|\-\>)/
-
               )
             {
 
@@ -2421,7 +2427,15 @@ EOM
                 } ## end else [ if ( $last_last_nonblank_token...
             } ## end if ( $expecting == OPERATOR...
         }
-        $paren_type[$paren_depth] = $container_type;
+
+        # Do not update container type at ') ('; fix for git #105.  This will
+        # propagate the container type onward so that any subsequent brace gets
+        # correctly marked.  I have implemented this as a general rule, which
+        # should be safe, but if necessary it could be restricted to certain
+        # container statement types such as 'for'.
+        $paren_type[$paren_depth] = $container_type
+          if ( $last_nonblank_token ne ')' );
+
         ( $type_sequence, $indent_flag ) =
           increase_nesting_depth( PAREN, $rtoken_map->[$i_tok] );
 
@@ -3300,18 +3314,6 @@ EOM
     sub do_POINTER {
 
         #  '->'
-        # if -> points to a bare word, we must scan for an identifier,
-        # otherwise something like ->y would look like the y operator
-
-        # NOTE: this will currently allow things like
-        #     '->@array'    '->*VAR'  '->%hash'
-        # to get parsed as identifiers, even though these are not currently
-        # allowed syntax.  To catch syntax errors like this we could first
-        # check that the next character and skip this call if it is one of
-        # ' @ % * '.  A disadvantage with doing this is that this would
-        # have to be fixed if the perltidy syntax is ever extended to make
-        # any of these valid.  So for now this check is not done.
-        scan_simple_identifier();
         return;
     } ## end sub do_POINTER
 
@@ -3451,7 +3453,7 @@ EOM
                         rtokens         => $rtokens,
                         rtoken_map      => $rtoken_map,
                         id_scan_state   => $id_scan_state,
-                        max_token_index => $max_token_index
+                        max_token_index => $max_token_index,
                     }
                 );
 
@@ -3660,24 +3662,6 @@ EOM
                 );
             }
         }
-        elsif ( $tok eq 'continue' ) {
-            if (   $last_nonblank_token ne ';'
-                && $last_nonblank_block_type !~
-                /(^(\{|\}|;|while|until|for|foreach)|:$)/ )
-            {
-
-                # note: ';' '{' and '}' in list above
-                # because continues can follow bare blocks;
-                # ':' is labeled block
-                #
-                ############################################
-                # NOTE: This check has been deactivated because
-                # continue has an alternative usage for given/when
-                # blocks in perl 5.10
-                ## warning("'$tok' should follow a block\n");
-                ############################################
-            }
-        }
 
         # patch for SWITCH/CASE if 'case' and 'when are
         # treated as keywords.  Also 'default' for Switch::Plain
@@ -3688,31 +3672,17 @@ EOM
             $statement_type = $tok;    # next '{' is block
         }
 
-        #
-        # indent trailing if/unless/while/until
-        # outdenting will be handled by later indentation loop
-## DEACTIVATED: unfortunately this can cause some unwanted indentation like:
-##$opt_o = 1
-##  if !(
-##             $opt_b
-##          || $opt_c
-##          || $opt_d
-##          || $opt_f
-##          || $opt_i
-##          || $opt_l
-##          || $opt_o
-##          || $opt_x
-##  );
-##                    if (   $tok =~ /^(if|unless|while|until)$/
-##                        && $next_nonblank_token ne '(' )
-##                    {
-##                        $indent_flag = 1;
-##                    }
+        # feature 'err' was removed in Perl 5.10.  So mark this as
+        # a bareword unless an operator is expected (see c158).
+        elsif ( $tok eq 'err' ) {
+            if ( $expecting != OPERATOR ) { $type = 'w' }
+        }
+
         return;
     } ## end sub do_KEYWORD
 
     sub do_QUOTE_OPERATOR {
-##NICOL PATCH
+
         if ( $expecting == OPERATOR ) {
 
             # Be careful not to call an error for a qw quote
@@ -3779,7 +3749,7 @@ EOM
                 #    '-' => \&sse_sub,
                 #    '*' => \&sse_mul,
                 #    '/' => \&sse_div;
-                # FIXME: this should eventually be generalized
+                # TODO: this could eventually be generalized
                 if (   $saw_use_module{$current_package}->{'RPerl'}
                     && $tok =~ /^sse_(mul|div|add|sub)$/ )
                 {
@@ -3822,9 +3792,17 @@ EOM
             $next_tok = $rtokens->[ $i + 1 ];
             if ( $next_tok eq '(' ) {
 
+                # Patch for issue c151, where we are processing a snippet and
+                # have not seen that SPACE is a constant.  In this case 'x' is
+                # probably an operator. The only disadvantage with an incorrect
+                # guess is that the space after it may be incorrect. For example
+                #   $str .= SPACE x ( 16 - length($str) ); See also b1410.
+                if ( $tok eq 'x' && $last_nonblank_type eq 'w' ) { $type = 'x' }
+
                 # Fix part 2 for git #63.  Leave type as 'w' to keep
                 # the type the same as if the -> were not separated
-                $type = 'U' unless ( $last_nonblank_type eq '->' );
+                elsif ( $last_nonblank_type ne '->' ) { $type = 'U' }
+
             }
 
             # underscore after file test operator is file handle
@@ -3843,16 +3821,22 @@ EOM
                 $statement_type = $tok;    # next '{' is block
                 $type           = 'k';     # for keyword syntax coloring
             }
+            if ( $next_nonblank_token eq '(' ) {
 
-            # patch for SWITCH/CASE if switch and given not keywords
-            # Switch is not a perl 5 keyword, but we will gamble
-            # and mark switch followed by paren as a keyword.  This
-            # is only necessary to get html syntax coloring nice,
-            # and does not commit this as being a switch/case.
-            if ( $next_nonblank_token eq '('
-                && ( $tok eq 'switch' || $tok eq 'given' ) )
-            {
-                $type = 'k';    # for keyword syntax coloring
+                # patch for SWITCH/CASE if switch and given not keywords
+                # Switch is not a perl 5 keyword, but we will gamble
+                # and mark switch followed by paren as a keyword.  This
+                # is only necessary to get html syntax coloring nice,
+                # and does not commit this as being a switch/case.
+                if ( $tok eq 'switch' || $tok eq 'given' ) {
+                    $type = 'k';    # for keyword syntax coloring
+                }
+
+                # mark 'x' as operator for something like this (see b1410)
+                #  my $line = join( LD_X, map { LD_H x ( $_ + 2 ) } @$widths );
+                elsif ( $tok eq 'x' && $last_nonblank_type eq 'w' ) {
+                    $type = 'x';
+                }
             }
         }
         return;
@@ -3890,13 +3874,6 @@ EOM
         #    true if this token ends the current line
         #    false otherwise
 
-        # Patch for c043, part 3: A bareword after '->' expects a TERM
-        # FIXME: It would be cleaner to give method calls a new type 'M'
-        # and update sub operator_expected to handle this.
-        if ( $last_nonblank_type eq '->' ) {
-            $expecting = TERM;
-        }
-
         my ( $next_nonblank_token, $i_next ) =
           find_next_nonblank_token( $i, $rtokens, $max_token_index );
 
@@ -3932,10 +3909,19 @@ EOM
 
         # They may also need to check and set various flags
 
+        # Scan a bare word following a -> as an identifier; it could
+        # have a long package name.  Fixes c037, c041.
+        if ( $last_nonblank_token eq '->' ) {
+            scan_bare_identifier();
+
+            # a bareward after '->' gets type 'i'
+            $type = 'i';
+        }
+
         # Quote a word followed by => operator
         # unless the word __END__ or __DATA__ and the only word on
         # the line.
-        if (  !$is_END_or_DATA
+        elsif ( !$is_END_or_DATA
             && $next_nonblank_token eq '='
             && $rtokens->[ $i_next + 1 ] eq '>' )
         {
@@ -3960,17 +3946,6 @@ EOM
             $type = 'w';
         }
 
-        # Scan a bare word following a -> as an identifier; it could
-        # have a long package name.  Fixes c037, c041.
-        elsif ( $last_nonblank_token eq '->' ) {
-            scan_bare_identifier();
-
-            # Patch for c043, part 4; use type 'w' after a '->'.
-            # This is just a safety check on sub scan_bare_identifier,
-            # which should get this case correct.
-            $type = 'w';
-        }
-
         # handle operator x (now we know it isn't $x=)
         elsif (
                $expecting == OPERATOR
@@ -4053,7 +4028,6 @@ EOM
             && ( $i_next <= $max_token_index )    # colon on same line
 
             # like 'sub : lvalue' ?
-            ##&& !$sub_attribute_ok_here            # like 'sub : lvalue' ?
             && !sub_attribute_ok_here( $tok_kw, $next_nonblank_token, $i_next )
             && label_ok()
           )
@@ -4134,22 +4108,30 @@ EOM
 
         }
 
-        # Removed to fix b1280.  This is not needed and was causing the
-        # starting type 'qw' to be lost, leading to mis-tokenization of
-        # a trailing block brace in a parenless for stmt 'for .. qw.. {'
-        ##$tok = $quote_character if ($quote_character);
-
         # scan for the end of the quote or pattern
         (
-            $i, $in_quote, $quote_character, $quote_pos, $quote_depth,
-            $quoted_string_1, $quoted_string_2
-          )
-          = do_quote(
-            $i,               $in_quote,    $quote_character,
-            $quote_pos,       $quote_depth, $quoted_string_1,
-            $quoted_string_2, $rtokens,     $rtoken_map,
-            $max_token_index
-          );
+            $i,
+            $in_quote,
+            $quote_character,
+            $quote_pos,
+            $quote_depth,
+            $quoted_string_1,
+            $quoted_string_2,
+
+        ) = do_quote(
+
+            $i,
+            $in_quote,
+            $quote_character,
+            $quote_pos,
+            $quote_depth,
+            $quoted_string_1,
+            $quoted_string_2,
+            $rtokens,
+            $rtoken_map,
+            $max_token_index,
+
+        );
 
         # all done if we didn't find it
         if ($in_quote) { return }
@@ -4430,30 +4412,23 @@ EOM
   #
   # -----------------------------------------------------------------------
 
-        my $line_of_tokens = shift;
+        my ( $self, $line_of_tokens ) = @_;
         my ($untrimmed_input_line) = $line_of_tokens->{_line_text};
 
-        # patch while coding change is underway
-        # make callers private data to allow access
-        # $tokenizer_self = $caller_tokenizer_self;
-
-        # extract line number for use in error messages
+        # Extract line number for use in error messages
         $input_line_number = $line_of_tokens->{_line_number};
 
-        # reinitialize for multi-line quote
-        $line_of_tokens->{_starting_in_quote} = $in_quote && $quote_type eq 'Q';
-
-        # check for pod documentation
+        # Check for pod documentation
         if ( substr( $untrimmed_input_line, 0, 1 ) eq '='
             && $untrimmed_input_line =~ /^=[A-Za-z_]/ )
         {
 
-            # must not be in multi-line quote
+            # Must not be in multi-line quote
             # and must not be in an equation
             if ( !$in_quote
                 && ( operator_expected( [ 'b', '=', 'b' ] ) == TERM ) )
             {
-                $tokenizer_self->[_in_pod_] = 1;
+                $self->[_in_pod_] = 1;
                 return;
             }
         }
@@ -4467,27 +4442,33 @@ EOM
         # a fat comma.
         my $is_END_or_DATA;
 
-        # trim start of this line unless we are continuing a quoted line
-        # do not trim end because we might end in a quote (test: deken4.pl)
-        # Perl::Tidy::Formatter will delete needless trailing blanks
-        unless ( $in_quote && ( $quote_type eq 'Q' ) ) {
-            $input_line =~ s/^(\s+)//;    # trim left end
+        # Reinitialize the multi-line quote flag
+        if ( $in_quote && $quote_type eq 'Q' ) {
+            $line_of_tokens->{_starting_in_quote} = 1;
+        }
+        else {
+            $line_of_tokens->{_starting_in_quote} = 0;
+
+            # Trim start of this line unless we are continuing a quoted line.
+            # Do not trim end because we might end in a quote (test: deken4.pl)
+            # Perl::Tidy::Formatter will delete needless trailing blanks
+            $input_line =~ s/^(\s+)//;
 
-            # calculate a guessed level for nonblank lines to avoid calls to
+            # Calculate a guessed level for nonblank lines to avoid calls to
             #    sub guess_old_indentation_level()
-            if ( $input_line && $1 ) {
+            if ( length($input_line) && $1 ) {
                 my $leading_spaces = $1;
                 my $spaces         = length($leading_spaces);
 
                 # handle leading tabs
-                if ( ord( substr( $leading_spaces, 0, 1 ) ) == 9
+                if ( ord( substr( $leading_spaces, 0, 1 ) ) == ORD_TAB
                     && $leading_spaces =~ /^(\t+)/ )
                 {
-                    my $tabsize = $tokenizer_self->[_tabsize_];
+                    my $tabsize = $self->[_tabsize_];
                     $spaces += length($1) * ( $tabsize - 1 );
                 }
 
-                my $indent_columns = $tokenizer_self->[_indent_columns_];
+                my $indent_columns = $self->[_indent_columns_];
                 $line_of_tokens->{_guessed_indentation_level} =
                   int( $spaces / $indent_columns );
             }
@@ -4496,9 +4477,50 @@ EOM
               && $input_line =~ /^__(END|DATA)__\s*$/;
         }
 
+        # Optimize for a full-line comment.
+        if ( !$in_quote ) {
+            if ( substr( $input_line, 0, 1 ) eq '#' ) {
+
+                # and check for skipped section
+                if (   $rOpts_code_skipping
+                    && $input_line =~ /$code_skipping_pattern_begin/ )
+                {
+                    $self->[_in_skipped_] = 1;
+                    return;
+                }
+
+                # Optional fast processing of a block comment
+                my $ci_string_sum =
+                  ( my $str = $ci_string_in_tokenizer ) =~ tr/1/0/;
+                my $ci_string_i = $ci_string_sum + $in_statement_continuation;
+                $line_of_tokens->{_line_type}        = 'CODE';
+                $line_of_tokens->{_rtokens}          = [$input_line];
+                $line_of_tokens->{_rtoken_type}      = ['#'];
+                $line_of_tokens->{_rlevels}          = [$level_in_tokenizer];
+                $line_of_tokens->{_rci_levels}       = [$ci_string_i];
+                $line_of_tokens->{_rblock_type}      = [EMPTY_STRING];
+                $line_of_tokens->{_nesting_tokens_0} = $nesting_token_string;
+                $line_of_tokens->{_nesting_blocks_0} = $nesting_block_string;
+                return;
+            }
+
+            # Optimize handling of a blank line
+            if ( !length($input_line) ) {
+                $line_of_tokens->{_line_type}        = 'CODE';
+                $line_of_tokens->{_rtokens}          = [];
+                $line_of_tokens->{_rtoken_type}      = [];
+                $line_of_tokens->{_rlevels}          = [];
+                $line_of_tokens->{_rci_levels}       = [];
+                $line_of_tokens->{_rblock_type}      = [];
+                $line_of_tokens->{_nesting_tokens_0} = $nesting_token_string;
+                $line_of_tokens->{_nesting_blocks_0} = $nesting_block_string;
+                return;
+            }
+        }
+
         # update the copy of the line for use in error messages
         # This must be exactly what we give the pre_tokenizer
-        $tokenizer_self->[_line_of_text_] = $input_line;
+        $self->[_line_of_text_] = $input_line;
 
         # re-initialize for the main loop
         $routput_token_list     = [];    # stack of output token indexes
@@ -4519,63 +4541,37 @@ EOM
         $indent_flag     = 0;
         $peeked_ahead    = 0;
 
-        # This variable signals pre_tokenize to get all tokens.
-        # But note that it is no longer needed with fast block comment
-        # option below.
-        my $max_tokens_wanted = 0;
-
-        # optimize for a full-line comment
-        if ( !$in_quote && substr( $input_line, 0, 1 ) eq '#' ) {
-            $max_tokens_wanted = 1;    # no use tokenizing a comment
-
-            # and check for skipped section
-            if (   $rOpts_code_skipping
-                && $input_line =~ /$code_skipping_pattern_begin/ )
-            {
-                $tokenizer_self->[_in_skipped_] = 1;
-                return;
-            }
-
-            # Optional fast processing of a block comment
-            my $ci_string_sum =
-              ( my $str = $ci_string_in_tokenizer ) =~ tr/1/0/;
-            my $ci_string_i = $ci_string_sum + $in_statement_continuation;
-            $line_of_tokens->{_line_type}        = 'CODE';
-            $line_of_tokens->{_rtokens}          = [$input_line];
-            $line_of_tokens->{_rtoken_type}      = ['#'];
-            $line_of_tokens->{_rlevels}          = [$level_in_tokenizer];
-            $line_of_tokens->{_rci_levels}       = [$ci_string_i];
-            $line_of_tokens->{_rblock_type}      = [EMPTY_STRING];
-            $line_of_tokens->{_nesting_tokens_0} = $nesting_token_string;
-            $line_of_tokens->{_nesting_blocks_0} = $nesting_block_string;
-            return;
-        }
-
-        tokenizer_main_loop( $max_tokens_wanted, $is_END_or_DATA );
+        $self->tokenizer_main_loop($is_END_or_DATA);
 
         #-----------------------------------------------
         # all done tokenizing this line ...
         # now prepare the final list of tokens and types
         #-----------------------------------------------
 
-        tokenizer_wrapup_line($line_of_tokens);
+        $self->tokenizer_wrapup_line($line_of_tokens);
 
         return;
     } ## end sub tokenize_this_line
 
     sub tokenizer_main_loop {
-        my ( $max_tokens_wanted, $is_END_or_DATA ) = @_;
 
-        # tokenization is done in two stages..
-        # stage 1 is a very simple pre-tokenization
+        my ( $self, $is_END_or_DATA ) = @_;
+
+        #---------------------------------
+        # Break one input line into tokens
+        #---------------------------------
+
+        # Input parameter:
+        #   $is_END_or_DATA is true for a __END__ or __DATA__ line
 
         # start by breaking the line into pre-tokens
+        my $max_tokens_wanted = 0; # this signals pre_tokenize to get all tokens
         ( $rtokens, $rtoken_map, $rtoken_type ) =
           pre_tokenize( $input_line, $max_tokens_wanted );
 
         $max_token_index = scalar( @{$rtokens} ) - 1;
         push( @{$rtokens}, SPACE, SPACE, SPACE )
-          ;    # extra whitespace simplifies logic
+          ;                        # extra whitespace simplifies logic
         push( @{$rtoken_map},  0,   0,   0 );     # shouldn't be referenced
         push( @{$rtoken_type}, 'b', 'b', 'b' );
 
@@ -4593,9 +4589,9 @@ EOM
         $i     = -1;
         $i_tok = -1;
 
-        # ------------------------------------------------------------
+        #-----------------------------
         # begin main tokenization loop
-        # ------------------------------------------------------------
+        #-----------------------------
 
         # we are looking at each pre-token of one line and combining them
         # into tokens
@@ -4649,8 +4645,7 @@ EOM
                 # fix for git #63.
                 if ( $last_last_nonblank_token eq '->' ) {
                     if (   $last_nonblank_type eq 'w'
-                        || $last_nonblank_type eq 'i'
-                        && substr( $last_nonblank_token, 0, 1 ) eq '$' )
+                        || $last_nonblank_type eq 'i' )
                     {
                         $last_nonblank_token = '->' . $last_nonblank_token;
                         $last_nonblank_type  = 'i';
@@ -4843,13 +4838,13 @@ EOM
                 $in_attribute_list = 0;
             }
 
-            ###############################################################
+            #--------------------------------------------------------
             # We have the next token, $tok.
             # Now we have to examine this token and decide what it is
             # and define its $type
             #
             # section 1: bare words
-            ###############################################################
+            #--------------------------------------------------------
 
             if ( $pre_type eq 'w' ) {
                 $expecting =
@@ -4858,18 +4853,18 @@ EOM
                 last if ($is_last);
             }
 
-            ###############################################################
+            #-----------------------------
             # section 2: strings of digits
-            ###############################################################
+            #-----------------------------
             elsif ( $pre_type eq 'd' ) {
                 $expecting =
                   operator_expected( [ $prev_type, $tok, $next_type ] );
                 do_DIGITS();
             }
 
-            ###############################################################
+            #----------------------------
             # section 3: all other tokens
-            ###############################################################
+            #----------------------------
             else {
                 my $code = $tokenization_code->{$tok};
                 if ($code) {
@@ -4895,7 +4890,7 @@ EOM
         }
 
         # Remember last nonblank values
-        unless ( ( $type eq 'b' ) || ( $type eq '#' ) ) {
+        if ( $type ne 'b' && $type ne '#' ) {
             $last_last_nonblank_token          = $last_nonblank_token;
             $last_last_nonblank_type           = $last_nonblank_type;
             $last_last_nonblank_block_type     = $last_nonblank_block_type;
@@ -4918,23 +4913,135 @@ EOM
             }
         }
 
-        $tokenizer_self->[_in_attribute_list_] = $in_attribute_list;
-        $tokenizer_self->[_in_quote_]          = $in_quote;
-        $tokenizer_self->[_quote_target_] =
+        $self->[_in_attribute_list_] = $in_attribute_list;
+        $self->[_in_quote_]          = $in_quote;
+        $self->[_quote_target_] =
           $in_quote ? matching_end_token($quote_character) : EMPTY_STRING;
-        $tokenizer_self->[_rhere_target_list_] = $rhere_target_list;
+        $self->[_rhere_target_list_] = $rhere_target_list;
 
         return;
     } ## end sub tokenizer_main_loop
 
     sub tokenizer_wrapup_line {
-        my ($line_of_tokens) = @_;
+        my ( $self, $line_of_tokens ) = @_;
+
+        #---------------------------------------------------------
+        # Package a line of tokens for shipping back to the caller
+        #---------------------------------------------------------
 
-        # We have broken the current line into tokens. Now we have to wrap up
-        # the result for shipping.  Most of the remaining work involves
-        # defining the various indentation parameters that the formatter needs
-        # (indentation level and continuation indentation).  This turns out to
-        # be somewhat complicated.
+        # Most of the remaining work involves defining the two indentation
+        # parameters that the formatter needs for each token:
+        # - $level    = structural indentation level and
+        # - $ci_level = continuation indentation level
+
+        # The method for setting the indentation level is straightforward.
+        # But the method used to define the continuation indentation is
+        # complicated because it has evolved over a long time by trial and
+        # error. It could undoubtedly be simplified but it works okay as is.
+
+        # Here is a brief description of how indentation is computed.
+        # Perl::Tidy computes indentation as the sum of 2 terms:
+        #
+        # (1) structural indentation, such as if/else/elsif blocks
+        # (2) continuation indentation, such as long parameter call lists.
+        #
+        # These are occasionally called primary and secondary indentation.
+        #
+        # Structural indentation is introduced by tokens of type '{',
+        # although the actual tokens might be '{', '(', or '['.  Structural
+        # indentation is of two types: BLOCK and non-BLOCK.  Default
+        # structural indentation is 4 characters if the standard indentation
+        # scheme is used.
+        #
+        # Continuation indentation is introduced whenever a line at BLOCK
+        # level is broken before its termination.  Default continuation
+        # indentation is 2 characters in the standard indentation scheme.
+        #
+        # Both types of indentation may be nested arbitrarily deep and
+        # interlaced.  The distinction between the two is somewhat arbitrary.
+        #
+        # For each token, we will define two variables which would apply if
+        # the current statement were broken just before that token, so that
+        # that token started a new line:
+        #
+        # $level = the structural indentation level,
+        # $ci_level = the continuation indentation level
+        #
+        # The total indentation will be $level * (4 spaces) + $ci_level * (2
+        # spaces), assuming defaults.  However, in some special cases it is
+        # customary to modify $ci_level from this strict value.
+        #
+        # The total structural indentation is easy to compute by adding and
+        # subtracting 1 from a saved value as types '{' and '}' are seen.
+        # The running value of this variable is $level_in_tokenizer.
+        #
+        # The total continuation is much more difficult to compute, and
+        # requires several variables.  These variables are:
+        #
+        # $ci_string_in_tokenizer = a string of 1's and 0's indicating, for
+        #   each indentation level, if there are intervening open secondary
+        #   structures just prior to that level.
+        # $continuation_string_in_tokenizer = a string of 1's and 0's
+        #   indicating if the last token at that level is "continued", meaning
+        #   that it is not the first token of an expression.
+        # $nesting_block_string = a string of 1's and 0's indicating, for each
+        #   indentation level, if the level is of type BLOCK or not.
+        # $nesting_block_flag = the most recent 1 or 0 of $nesting_block_string
+        # $nesting_list_string = a string of 1's and 0's indicating, for each
+        #   indentation level, if it is appropriate for list formatting.
+        #   If so, continuation indentation is used to indent long list items.
+        # $nesting_list_flag = the most recent 1 or 0 of $nesting_list_string
+        # @{$rslevel_stack} = a stack of total nesting depths at each
+        #   structural indentation level, where "total nesting depth" means
+        #   the nesting depth that would occur if every nesting token
+        #   -- '{', '[', #   and '(' -- , regardless of context, is used to
+        #   compute a nesting depth.
+
+        # Notes on the Continuation Indentation
+        #
+        # There is a sort of chicken-and-egg problem with continuation
+        # indentation.  The formatter can't make decisions on line breaks
+        # without knowing what 'ci' will be at arbitrary locations.
+        #
+        # But a problem with setting the continuation indentation (ci) here
+        # in the tokenizer is that we do not know where line breaks will
+        # actually be.  As a result, we don't know if we should propagate
+        # continuation indentation to higher levels of structure.
+        #
+        # For nesting of only structural indentation, we never need to do
+        # this.  For example, in a long if statement, like this
+        #
+        #   if ( !$output_block_type[$i]
+        #     && ($in_statement_continuation) )
+        #   {           <--outdented
+        #       do_something();
+        #   }
+        #
+        # the second line has ci but we do normally give the lines within
+        # the BLOCK any ci.  This would be true if we had blocks nested
+        # arbitrarily deeply.
+        #
+        # But consider something like this, where we have created a break
+        # after an opening paren on line 1, and the paren is not (currently)
+        # a structural indentation token:
+        #
+        # my $file = $menubar->Menubutton(
+        #   qw/-text File -underline 0 -menuitems/ => [
+        #       [
+        #           Cascade    => '~View',
+        #           -menuitems => [
+        #           ...
+        #
+        # The second line has ci, so it would seem reasonable to propagate
+        # it down, giving the third line 1 ci + 1 indentation.  This
+        # suggests the following rule, which is currently used to
+        # propagating ci down: if there are any non-structural opening
+        # parens (or brackets, or braces), before an opening structural
+        # brace, then ci is propagated down, and otherwise
+        # not.  The variable $intervening_secondary_structure contains this
+        # information for the current token, and the string
+        # "$ci_string_in_tokenizer" is a stack of previous values of this
+        # variable.
 
         my @token_type    = ();    # stack of output token types
         my @block_type    = ();    # stack of output code block types
@@ -4942,95 +5049,34 @@ EOM
         my @tokens        = ();    # output tokens
         my @levels        = ();    # structural brace levels of output tokens
         my @ci_string = ();  # string needed to compute continuation indentation
-        my $container_environment = EMPTY_STRING;
-        my $im                    = -1;             # previous $i value
-        my $num;
 
         # Count the number of '1's in the string (previously sub ones_count)
         my $ci_string_sum = ( my $str = $ci_string_in_tokenizer ) =~ tr/1/0/;
 
-# Computing Token Indentation
-#
-#     The final section of the tokenizer forms tokens and also computes
-#     parameters needed to find indentation.  It is much easier to do it
-#     in the tokenizer than elsewhere.  Here is a brief description of how
-#     indentation is computed.  Perl::Tidy computes indentation as the sum
-#     of 2 terms:
-#
-#     (1) structural indentation, such as if/else/elsif blocks
-#     (2) continuation indentation, such as long parameter call lists.
-#
-#     These are occasionally called primary and secondary indentation.
-#
-#     Structural indentation is introduced by tokens of type '{', although
-#     the actual tokens might be '{', '(', or '['.  Structural indentation
-#     is of two types: BLOCK and non-BLOCK.  Default structural indentation
-#     is 4 characters if the standard indentation scheme is used.
-#
-#     Continuation indentation is introduced whenever a line at BLOCK level
-#     is broken before its termination.  Default continuation indentation
-#     is 2 characters in the standard indentation scheme.
-#
-#     Both types of indentation may be nested arbitrarily deep and
-#     interlaced.  The distinction between the two is somewhat arbitrary.
-#
-#     For each token, we will define two variables which would apply if
-#     the current statement were broken just before that token, so that
-#     that token started a new line:
-#
-#     $level = the structural indentation level,
-#     $ci_level = the continuation indentation level
-#
-#     The total indentation will be $level * (4 spaces) + $ci_level * (2 spaces),
-#     assuming defaults.  However, in some special cases it is customary
-#     to modify $ci_level from this strict value.
-#
-#     The total structural indentation is easy to compute by adding and
-#     subtracting 1 from a saved value as types '{' and '}' are seen.  The
-#     running value of this variable is $level_in_tokenizer.
-#
-#     The total continuation is much more difficult to compute, and requires
-#     several variables.  These variables are:
-#
-#     $ci_string_in_tokenizer = a string of 1's and 0's indicating, for
-#       each indentation level, if there are intervening open secondary
-#       structures just prior to that level.
-#     $continuation_string_in_tokenizer = a string of 1's and 0's indicating
-#       if the last token at that level is "continued", meaning that it
-#       is not the first token of an expression.
-#     $nesting_block_string = a string of 1's and 0's indicating, for each
-#       indentation level, if the level is of type BLOCK or not.
-#     $nesting_block_flag = the most recent 1 or 0 of $nesting_block_string
-#     $nesting_list_string = a string of 1's and 0's indicating, for each
-#       indentation level, if it is appropriate for list formatting.
-#       If so, continuation indentation is used to indent long list items.
-#     $nesting_list_flag = the most recent 1 or 0 of $nesting_list_string
-#     @{$rslevel_stack} = a stack of total nesting depths at each
-#       structural indentation level, where "total nesting depth" means
-#       the nesting depth that would occur if every nesting token -- '{', '[',
-#       and '(' -- , regardless of context, is used to compute a nesting
-#       depth.
-
         $line_of_tokens->{_nesting_tokens_0} = $nesting_token_string;
 
         my ( $ci_string_i, $level_i );
 
-        # loop over the list of pre-tokens indexes
+        #-----------------
+        # Loop over tokens
+        #-----------------
+        my $rtoken_map_im;
         foreach my $i ( @{$routput_token_list} ) {
 
-            # Get $tok_i, the PRE-token.  It only equals the token for symbols
             my $type_i = $routput_token_type->[$i];
-            my $tok_i  = $rtokens->[$i];
+            $level_i = $level_in_tokenizer;
 
             # Quick handling of indentation levels for blanks and comments
             if ( $type_i eq 'b' || $type_i eq '#' ) {
                 $ci_string_i = $ci_string_sum + $in_statement_continuation;
-                $level_i     = $level_in_tokenizer;
             }
 
             # All other types
             else {
 
+                # $tok_i is the PRE-token.  It only equals the token for symbols
+                my $tok_i = $rtokens->[$i];
+
                 # Check for an invalid token type..
                 # This can happen by running perltidy on non-scripts although
                 # it could also be bug introduced by programming change.  Perl
@@ -5040,113 +5086,28 @@ EOM
                     warning(
 "unexpected character decimal $val ($type_i) in script\n"
                     );
-                    $tokenizer_self->[_in_error_] = 1;
+                    $self->[_in_error_] = 1;
                 }
 
-             # See if we should undo the $forced_indentation_flag.
-             # Forced indentation after 'if', 'unless', 'while' and 'until'
-             # expressions without trailing parens is optional and doesn't
-             # always look good.  It is usually okay for a trailing logical
-             # expression, but if the expression is a function call, code block,
-             # or some kind of list it puts in an unwanted extra indentation
-             # level which is hard to remove.
-             #
-             # Example where extra indentation looks ok:
-             # return 1
-             #   if $det_a < 0 and $det_b > 0
-             #       or $det_a > 0 and $det_b < 0;
-             #
-             # Example where extra indentation is not needed because
-             # the eval brace also provides indentation:
-             # print "not " if defined eval {
-             #     reduce { die if $b > 2; $a + $b } 0, 1, 2, 3, 4;
-             # };
-             #
-             # The following rule works fairly well:
-             #   Undo the flag if the end of this line, or start of the next
-             #   line, is an opening container token or a comma.
-             # This almost always works, but if not after another pass it will
-             # be stable.
-                my $forced_indentation_flag = $routput_indent_flag->[$i];
-                if ( $forced_indentation_flag && $type_i eq 'k' ) {
-                    my $ixlast  = -1;
-                    my $ilast   = $routput_token_list->[$ixlast];
-                    my $toklast = $routput_token_type->[$ilast];
-                    if ( $toklast eq '#' ) {
-                        $ixlast--;
-                        $ilast   = $routput_token_list->[$ixlast];
-                        $toklast = $routput_token_type->[$ilast];
-                    }
-                    if ( $toklast eq 'b' ) {
-                        $ixlast--;
-                        $ilast   = $routput_token_list->[$ixlast];
-                        $toklast = $routput_token_type->[$ilast];
-                    }
-                    if ( $toklast =~ /^[\{,]$/ ) {
-                        $forced_indentation_flag = 0;
-                    }
-                    else {
-                        ( $toklast, my $i_next ) =
-                          find_next_nonblank_token( $max_token_index, $rtokens,
-                            $max_token_index );
-                        if ( $toklast =~ /^[\{,]$/ ) {
-                            $forced_indentation_flag = 0;
-                        }
-                    }
-                } ## end if ( $forced_indentation_flag...)
-
-                # if we are already in an indented if, see if we should outdent
-                if ($indented_if_level) {
-
-                    # don't try to nest trailing if's - shouldn't happen
-                    if ( $type_i eq 'k' ) {
-                        $forced_indentation_flag = 0;
-                    }
-
-                    # check for the normal case - outdenting at next ';'
-                    elsif ( $type_i eq ';' ) {
-                        if ( $level_in_tokenizer == $indented_if_level ) {
-                            $forced_indentation_flag = -1;
-                            $indented_if_level       = 0;
-                        }
-                    }
-
-                    # handle case of missing semicolon
-                    elsif ( $type_i eq '}' ) {
-                        if ( $level_in_tokenizer == $indented_if_level ) {
-                            $indented_if_level = 0;
-
-                            $level_in_tokenizer--;
-                            if ( @{$rslevel_stack} > 1 ) {
-                                pop( @{$rslevel_stack} );
-                            }
-                            if ( length($nesting_block_string) > 1 )
-                            {    # true for valid script
-                                chop $nesting_block_string;
-                                chop $nesting_list_string;
-                            }
-                        }
-                    }
-                } ## end if ($indented_if_level)
-
-                # Now we have the first approximation to the level
-                $level_i = $level_in_tokenizer;
+                # $ternary_indentation_flag indicates that we need a change
+                # in level at a nested ternary, as follows
+                #     1 => at a nested ternary ?
+                #    -1 => at a nested ternary :
+                #     0 => otherwise
+                my $ternary_indentation_flag = $routput_indent_flag->[$i];
 
+                #-------------------------------------------
+                # Section 1: handle a level-increasing token
+                #-------------------------------------------
                 # set primary indentation levels based on structural braces
                 # Note: these are set so that the leading braces have a HIGHER
                 # level than their CONTENTS, which is convenient for indentation
                 # Also, define continuation indentation for each token.
                 if (   $type_i eq '{'
                     || $type_i eq 'L'
-                    || $forced_indentation_flag > 0 )
+                    || $ternary_indentation_flag > 0 )
                 {
 
-                    # use environment before updating
-                    $container_environment =
-                        $nesting_block_flag ? 'BLOCK'
-                      : $nesting_list_flag  ? 'LIST'
-                      :                       EMPTY_STRING;
-
                     # if the difference between total nesting levels is not 1,
                     # there are intervening non-structural nesting types between
                     # this '{' and the previous unclosed '{'
@@ -5156,85 +5117,23 @@ EOM
                           $slevel_in_tokenizer - $rslevel_stack->[-1];
                     }
 
-     # Continuation Indentation
-     #
-     # Having tried setting continuation indentation both in the formatter and
-     # in the tokenizer, I can say that setting it in the tokenizer is much,
-     # much easier.  The formatter already has too much to do, and can't
-     # make decisions on line breaks without knowing what 'ci' will be at
-     # arbitrary locations.
-     #
-     # But a problem with setting the continuation indentation (ci) here
-     # in the tokenizer is that we do not know where line breaks will actually
-     # be.  As a result, we don't know if we should propagate continuation
-     # indentation to higher levels of structure.
-     #
-     # For nesting of only structural indentation, we never need to do this.
-     # For example, in a long if statement, like this
-     #
-     #   if ( !$output_block_type[$i]
-     #     && ($in_statement_continuation) )
-     #   {           <--outdented
-     #       do_something();
-     #   }
-     #
-     # the second line has ci but we do normally give the lines within the BLOCK
-     # any ci.  This would be true if we had blocks nested arbitrarily deeply.
-     #
-     # But consider something like this, where we have created a break after
-     # an opening paren on line 1, and the paren is not (currently) a
-     # structural indentation token:
-     #
-     # my $file = $menubar->Menubutton(
-     #   qw/-text File -underline 0 -menuitems/ => [
-     #       [
-     #           Cascade    => '~View',
-     #           -menuitems => [
-     #           ...
-     #
-     # The second line has ci, so it would seem reasonable to propagate it
-     # down, giving the third line 1 ci + 1 indentation.  This suggests the
-     # following rule, which is currently used to propagating ci down: if there
-     # are any non-structural opening parens (or brackets, or braces), before
-     # an opening structural brace, then ci is propagated down, and otherwise
-     # not.  The variable $intervening_secondary_structure contains this
-     # information for the current token, and the string
-     # "$ci_string_in_tokenizer" is a stack of previous values of this
-     # variable.
-
                     # save the current states
                     push( @{$rslevel_stack}, 1 + $slevel_in_tokenizer );
                     $level_in_tokenizer++;
 
-                    if ( $level_in_tokenizer >
-                        $tokenizer_self->[_maximum_level_] )
-                    {
-                        $tokenizer_self->[_maximum_level_] =
-                          $level_in_tokenizer;
+                    if ( $level_in_tokenizer > $self->[_maximum_level_] ) {
+                        $self->[_maximum_level_] = $level_in_tokenizer;
                     }
 
-                    if ($forced_indentation_flag) {
+                    if ($ternary_indentation_flag) {
 
-                        # break BEFORE '?' when there is forced indentation
+                        # break BEFORE '?' in a nested ternary
                         if ( $type_i eq '?' ) {
                             $level_i = $level_in_tokenizer;
                         }
-                        if ( $type_i eq 'k' ) {
-                            $indented_if_level = $level_in_tokenizer;
-                        }
-
-                        # do not change container environment here if we are not
-                        # at a real list. Adding this check prevents "blinkers"
-                        # often near 'unless" clauses, such as in the following
-                        # code:
-##          next
-##            unless -e (
-##                    $archive =
-##                      File::Spec->catdir( $_, "auto", $root, "$sub$lib_ext" )
-##            );
 
                         $nesting_block_string .= "$nesting_block_flag";
-                    } ## end if ($forced_indentation_flag)
+                    } ## end if ($ternary_indentation_flag)
                     else {
 
                         if ( $routput_block_type->[$i] ) {
@@ -5276,28 +5175,31 @@ EOM
                     $continuation_string_in_tokenizer .=
                       ( $in_statement_continuation > 0 ) ? '1' : '0';
 
-   #  Sometimes we want to give an opening brace continuation indentation,
-   #  and sometimes not.  For code blocks, we don't do it, so that the leading
-   #  '{' gets outdented, like this:
-   #
-   #   if ( !$output_block_type[$i]
-   #     && ($in_statement_continuation) )
-   #   {           <--outdented
-   #
-   #  For other types, we will give them continuation indentation.  For example,
-   #  here is how a list looks with the opening paren indented:
-   #
-   #     @LoL =
-   #       ( [ "fred", "barney" ], [ "george", "jane", "elroy" ],
-   #         [ "homer", "marge", "bart" ], );
-   #
-   #  This looks best when 'ci' is one-half of the indentation  (i.e., 2 and 4)
+                    #  Sometimes we want to give an opening brace
+                    #  continuation indentation, and sometimes not.  For code
+                    #  blocks, we don't do it, so that the leading '{' gets
+                    #  outdented, like this:
+                    #
+                    #   if ( !$output_block_type[$i]
+                    #     && ($in_statement_continuation) )
+                    #   {           <--outdented
+                    #
+                    #  For other types, we will give them continuation
+                    #  indentation.  For example, here is how a list looks
+                    #  with the opening paren indented:
+                    #
+                    #  @LoL =
+                    #    ( [ "fred", "barney" ], [ "george", "jane", "elroy" ],
+                    #      [ "homer", "marge", "bart" ], );
+                    #
+                    #  This looks best when 'ci' is one-half of the
+                    #  indentation  (i.e., 2 and 4)
 
                     my $total_ci = $ci_string_sum;
                     if (
                         !$routput_block_type->[$i]    # patch: skip for BLOCK
                         && ($in_statement_continuation)
-                        && !( $forced_indentation_flag && $type_i eq ':' )
+                        && !( $ternary_indentation_flag && $type_i eq ':' )
                       )
                     {
                         $total_ci += $in_statement_continuation
@@ -5309,16 +5211,27 @@ EOM
                     $in_statement_continuation = 0;
                 } ## end if ( $type_i eq '{' ||...})
 
+                #-------------------------------------------
+                # Section 2: handle a level-decreasing token
+                #-------------------------------------------
                 elsif ($type_i eq '}'
                     || $type_i eq 'R'
-                    || $forced_indentation_flag < 0 )
+                    || $ternary_indentation_flag < 0 )
                 {
 
-                 # only a nesting error in the script would prevent popping here
+                    # only a nesting error in the script would prevent
+                    # popping here
                     if ( @{$rslevel_stack} > 1 ) { pop( @{$rslevel_stack} ); }
 
                     $level_i = --$level_in_tokenizer;
 
+                    if ( $level_in_tokenizer < 0 ) {
+                        unless ( $self->[_saw_negative_indentation_] ) {
+                            $self->[_saw_negative_indentation_] = 1;
+                            warning("Starting negative indentation\n");
+                        }
+                    }
+
                     # restore previous level values
                     if ( length($nesting_block_string) > 1 )
                     {    # true for valid script
@@ -5343,13 +5256,14 @@ EOM
 
                             # ...These include non-anonymous subs
                             # note: could be sub ::abc { or sub 'abc
-                            if ( $block_type_i =~ m/^sub\s*/gc ) {
+                            if ( substr( $block_type_i, 0, 3 ) eq 'sub'
+                                && $block_type_i =~ m/^sub\s*/gc )
+                            {
 
                                 # note: older versions of perl require the /gc
                                 # modifier here or else the \G does not work.
-                                if ( $block_type_i =~ /\G('|::|\w)/gc ) {
-                                    $in_statement_continuation = 0;
-                                }
+                                $in_statement_continuation = 0
+                                  if ( $block_type_i =~ /\G('|::|\w)/gc );
                             }
 
                             # ...and include all block types except user subs
@@ -5400,42 +5314,36 @@ EOM
                               );
                             ##if $routput_container_type->[$i] =~ /^[;,\{\}]$/;
                         }
-
-                        elsif ( $tok_i eq ';' ) {
-                            $in_statement_continuation = 0;
-                        }
                     } ## end if ( length($nesting_block_string...))
 
-                    # use environment after updating
-                    $container_environment =
-                        $nesting_block_flag ? 'BLOCK'
-                      : $nesting_list_flag  ? 'LIST'
-                      :                       EMPTY_STRING;
                     $ci_string_i = $ci_string_sum + $in_statement_continuation;
                 } ## end elsif ( $type_i eq '}' ||...{)
 
-                # not a structural indentation type..
+                #-----------------------------------------
+                # Section 3: handle a constant level token
+                #-----------------------------------------
                 else {
 
-                    $container_environment =
-                        $nesting_block_flag ? 'BLOCK'
-                      : $nesting_list_flag  ? 'LIST'
-                      :                       EMPTY_STRING;
-
                     # zero the continuation indentation at certain tokens so
                     # that they will be at the same level as its container.  For
                     # commas, this simplifies the -lp indentation logic, which
                     # counts commas.  For ?: it makes them stand out.
-                    if ($nesting_list_flag) {
+                    if (
+                        $nesting_list_flag
                         ##      $type_i =~ /^[,\?\:]$/
-                        if ( $is_comma_question_colon{$type_i} ) {
-                            $in_statement_continuation = 0;
-                        }
+                        && $is_comma_question_colon{$type_i}
+                      )
+                    {
+                        $in_statement_continuation = 0;
                     }
 
-                    # be sure binary operators get continuation indentation
+                    # Be sure binary operators get continuation indentation.
+                    # Note: the check on $nesting_block_flag is only needed
+                    # to add ci to binary operators following a 'try' block,
+                    # or similar extended syntax block operator (see c158).
                     if (
-                        $container_environment
+                           !$in_statement_continuation
+                        && ( $nesting_block_flag || $nesting_list_flag )
                         && (   $type_i eq 'k' && $is_binary_keyword{$tok_i}
                             || $is_binary_type{$type_i} )
                       )
@@ -5449,8 +5357,6 @@ EOM
 
                     # update continuation flag ...
 
-                    ## if ( $type_i ne 'b' && $type_i ne '#' ) {  # moved above
-
                     # if we are in a BLOCK
                     if ($nesting_block_flag) {
 
@@ -5492,16 +5398,11 @@ EOM
                         }
                     } ## end else [ if ($nesting_block_flag)]
 
-                    ##}  ## end if ( $type_i ne 'b' ... # (old moved above)
-
                 } ## end else [ if ( $type_i eq '{' ||...})]
 
-                if ( $level_in_tokenizer < 0 ) {
-                    unless ( $tokenizer_self->[_saw_negative_indentation_] ) {
-                        $tokenizer_self->[_saw_negative_indentation_] = 1;
-                        warning("Starting negative indentation\n");
-                    }
-                }
+                #-------------------------------------------
+                # Section 4: operations common to all levels
+                #-------------------------------------------
 
                 # set secondary nesting levels based on all containment token
                 # types Note: these are set so that the nesting depth is the
@@ -5543,21 +5444,30 @@ EOM
                 }
             } ## end else [ if ( $type_i eq 'b' ||...)]
 
+            #--------------------------------
             # Store the values for this token
+            #--------------------------------
             push( @ci_string,     $ci_string_i );
             push( @levels,        $level_i );
             push( @block_type,    $routput_block_type->[$i] );
             push( @type_sequence, $routput_type_sequence->[$i] );
             push( @token_type,    $type_i );
 
-            # Form and store the previous token
-            if ( $im >= 0 ) {
-                $num =
-                  $rtoken_map->[$i] - $rtoken_map->[$im];  # how many characters
+            # Form and store the PREVIOUS token
+            if ( defined($rtoken_map_im) ) {
+                my $numc =
+                  $rtoken_map->[$i] - $rtoken_map_im;    # how many characters
 
-                if ( $num > 0 ) {
+                if ( $numc > 0 ) {
                     push( @tokens,
-                        substr( $input_line, $rtoken_map->[$im], $num ) );
+                        substr( $input_line, $rtoken_map_im, $numc ) );
+                }
+                else {
+
+                    # Should not happen unless @{$rtoken_map} is corrupted
+                    DEVEL_MODE
+                      && Fault(
+                        "number of characters is '$numc' but should be >0\n");
                 }
             }
 
@@ -5566,15 +5476,31 @@ EOM
                 $line_of_tokens->{_nesting_blocks_0} = $nesting_block_string;
             }
 
-            $im = $i;
+            $rtoken_map_im = $rtoken_map->[$i];
         } ## end foreach my $i ( @{$routput_token_list...})
 
-        # Form and store the final token
-        $num = length($input_line) - $rtoken_map->[$im];   # make the last token
-        if ( $num > 0 ) {
-            push( @tokens, substr( $input_line, $rtoken_map->[$im], $num ) );
+        #------------------------
+        # End loop to over tokens
+        #------------------------
+
+        # Form and store the final token of this line
+        if ( defined($rtoken_map_im) ) {
+            my $numc = length($input_line) - $rtoken_map_im;
+            if ( $numc > 0 ) {
+                push( @tokens, substr( $input_line, $rtoken_map_im, $numc ) );
+            }
+            else {
+
+                # Should not happen unless @{$rtoken_map} is corrupted
+                DEVEL_MODE
+                  && Fault(
+                    "Number of Characters is '$numc' but should be >0\n");
+            }
         }
 
+        #----------------------------------------------------------
+        # Wrap up this line of tokens for shipping to the Formatter
+        #----------------------------------------------------------
         $line_of_tokens->{_rtoken_type}    = \@token_type;
         $line_of_tokens->{_rtokens}        = \@tokens;
         $line_of_tokens->{_rblock_type}    = \@block_type;
@@ -5586,7 +5512,7 @@ EOM
     } ## end sub tokenizer_wrapup_line
 } ## end tokenize_this_line
 
-#########i#############################################################
+#######################################################################
 # Tokenizer routines which assist in identifying token types
 #######################################################################
 
@@ -5611,12 +5537,13 @@ BEGIN {
       ... **= <<= >>= &&= ||= //= <=> !~~ &.= |.= ^.= <<~
     );
     push @q, ',';
-    push @q, '(';    # for completeness, not currently a token type
+    push @q, '(';     # for completeness, not currently a token type
+    push @q, '->';    # was previously in UNKNOWN
     @{op_expected_table}{@q} = (TERM) x scalar(@q);
 
-    # Always UNKNOWN following these types:
-    # Fix for c030: added '->' to this list
-    @q = qw( w -> );
+    # Always UNKNOWN following these types;
+    # previously had '->' in this list for c030
+    @q = qw( w );
     @{op_expected_table}{@q} = (UNKNOWN) x scalar(@q);
 
     # Always expecting OPERATOR ...
@@ -5693,39 +5620,37 @@ sub operator_expected {
 
     my ($rarg) = @_;
 
-    my $msg = EMPTY_STRING;
-
-    ##############
+    #-------------
     # Table lookup
-    ##############
+    #-------------
 
     # Many types are can be obtained by a table lookup given the previous type.
     # This typically handles half or more of the calls.
     my $op_expected = $op_expected_table{$last_nonblank_type};
     if ( defined($op_expected) ) {
-        $msg = "Table lookup";
-        goto RETURN;
+        DEBUG_OPERATOR_EXPECTED
+          && print STDOUT
+"OPERATOR_EXPECTED: Table Lookup; returns $op_expected for last type $last_nonblank_type token $last_nonblank_token\n";
+        return $op_expected;
     }
 
-    ######################
+    #---------------------
     # Handle special cases
-    ######################
+    #---------------------
 
     $op_expected = UNKNOWN;
     my ( $prev_type, $tok, $next_type ) = @{$rarg};
 
     # Types 'k', '}' and 'Z' depend on context
-    # FIXME: Types 'i', 'n', 'v', 'q' currently also temporarily depend on
-    # context but that dependence could eventually be eliminated with better
-    # token type definition
+    # Types 'i', 'n', 'v', 'q' currently also temporarily depend on context.
 
     # identifier...
     if ( $last_nonblank_type eq 'i' ) {
         $op_expected = OPERATOR;
 
-        # FIXME: it would be cleaner to make this a special type
-        # expecting VERSION or {} after package NAMESPACE
-        # TODO: maybe mark these words as type 'Y'?
+        # TODO: it would be cleaner to make this a special type
+        # expecting VERSION or {} after package NAMESPACE;
+        # maybe mark these words as type 'Y'?
         if (   substr( $last_nonblank_token, 0, 7 ) eq 'package'
             && $statement_type      =~ /^package\b/
             && $last_nonblank_token =~ /^package\b/ )
@@ -5790,7 +5715,7 @@ sub operator_expected {
             $op_expected = OPERATOR;    # block mode following }
         }
 
-        ##elsif ( $last_nonblank_token =~ /^(\)|\$|\-\>)/ ) {
+        #       $last_nonblank_token =~ /^(\)|\$|\-\>)/
         elsif ( $is_paren_dollar{ substr( $last_nonblank_token, 0, 1 ) }
             || substr( $last_nonblank_token, 0, 2 ) eq '->' )
         {
@@ -5853,13 +5778,11 @@ sub operator_expected {
     }
 
     # quote...
-    # FIXME: labeled prototype words should probably be given type 'A' or maybe
-    # 'J'; not 'q'; or maybe mark as type 'Y'
+    # TODO: labeled prototype words would better be given type 'A' or maybe
+    # 'J'; not 'q'; or maybe mark as type 'Y'?
     elsif ( $last_nonblank_type eq 'q' ) {
         $op_expected = OPERATOR;
-        if ( $last_nonblank_token eq 'prototype' )
-          ##|| $last_nonblank_token eq 'switch' )
-        {
+        if ( $last_nonblank_token eq 'prototype' ) {
             $op_expected = TERM;
         }
     }
@@ -5926,12 +5849,9 @@ sub operator_expected {
         $op_expected = UNKNOWN;
     }
 
-  RETURN:
-
-    DEBUG_OPERATOR_EXPECTED && do {
-        print STDOUT
-"OPERATOR_EXPECTED: $msg: returns $op_expected for last type $last_nonblank_type token $last_nonblank_token\n";
-    };
+    DEBUG_OPERATOR_EXPECTED
+      && print STDOUT
+"OPERATOR_EXPECTED: returns $op_expected for last type $last_nonblank_type token $last_nonblank_token\n";
 
     return $op_expected;
 
@@ -6030,11 +5950,11 @@ sub code_block_type {
         }
     }
 
-    ################################################################
+    #--------------------------------------------------------------
     # NOTE: braces after type characters start code blocks, but for
     # simplicity these are not identified as such.  See also
     # sub is_non_structural_brace.
-    ################################################################
+    #--------------------------------------------------------------
 
 ##    elsif ( $last_nonblank_type eq 't' ) {
 ##       return $last_nonblank_token;
@@ -6337,11 +6257,11 @@ sub is_non_structural_brace {
     #    return 0;
     # }
 
-    ################################################################
+    #--------------------------------------------------------------
     # NOTE: braces after type characters start code blocks, but for
     # simplicity these are not identified as such.  See also
     # sub code_block_type
-    ################################################################
+    #--------------------------------------------------------------
 
     ##if ($last_nonblank_type eq 't') {return 0}
 
@@ -6363,7 +6283,7 @@ sub is_non_structural_brace {
     );
 } ## end sub is_non_structural_brace
 
-#########i#############################################################
+#######################################################################
 # Tokenizer routines for tracking container nesting depths
 #######################################################################
 
@@ -6423,16 +6343,8 @@ sub increase_nesting_depth {
     # a unique set of numbers but still allows the relative location
     # of any type to be determined.
 
-    ########################################################################
-    # OLD SEQNO METHOD for incrementing sequence numbers.
-    # Keep this coding awhile for possible testing.
-    ## $nesting_sequence_number[$aa] += scalar(@closing_brace_names);
-    ## my $seqno = $nesting_sequence_number[$aa];
-
-    # NEW SEQNO METHOD, continuous sequence numbers. This allows sequence
-    # numbers to be used as array indexes, and allows them to be compared.
+    # make a new unique sequence number
     my $seqno = $next_sequence_number++;
-    ########################################################################
 
     $current_sequence_number[$aa][ $current_depth[$aa] ] = $seqno;
 
@@ -6623,7 +6535,7 @@ EOM
     return;
 } ## end sub check_final_nesting_depths
 
-#########i#############################################################
+#######################################################################
 # Tokenizer routines for looking ahead in input stream
 #######################################################################
 
@@ -6678,7 +6590,7 @@ sub peek_ahead_for_nonblank_token {
     return;
 } ## end sub peek_ahead_for_nonblank_token
 
-#########i#############################################################
+#######################################################################
 # Tokenizer guessing routines for ambiguous situations
 #######################################################################
 
@@ -6712,11 +6624,25 @@ sub guess_if_pattern_or_conditional {
         my $quote_pos       = 0;
         my $quoted_string;
         (
-            $i, $in_quote, $quote_character, $quote_pos, $quote_depth,
-            $quoted_string
-          )
-          = follow_quoted_string( $ibeg, $in_quote, $rtokens, $quote_character,
-            $quote_pos, $quote_depth, $max_token_index );
+
+            $i,
+            $in_quote,
+            $quote_character,
+            $quote_pos,
+            $quote_depth,
+            $quoted_string,
+
+        ) = follow_quoted_string(
+
+            $ibeg,
+            $in_quote,
+            $rtokens,
+            $quote_character,
+            $quote_pos,
+            $quote_depth,
+            $max_token_index,
+
+        );
 
         if ($in_quote) {
 
@@ -6796,7 +6722,7 @@ sub guess_if_pattern_or_division {
         if ( $divide_possible < 0 ) {
             $msg        = "pattern (division not possible here)\n";
             $is_pattern = 1;
-            goto RETURN;
+            return ( $is_pattern, $msg );
         }
 
         $i = $ibeg + 1;
@@ -6927,8 +6853,6 @@ sub guess_if_pattern_or_division {
             }
         }
     }
-
-  RETURN:
     return ( $is_pattern, $msg );
 } ## end sub guess_if_pattern_or_division
 
@@ -6987,7 +6911,7 @@ sub guess_if_here_doc {
     return $here_doc_expected;
 } ## end sub guess_if_here_doc
 
-#########i#############################################################
+#######################################################################
 # Tokenizer Routines for scanning identifiers and related items
 #######################################################################
 
@@ -7097,7 +7021,7 @@ sub scan_bare_identifier_do {
             #    $tok='eval'; # patch to do braces like eval  - doesn't work
             #    $type = 'k';
             #}
-            # FIXME: This could become a separate type to allow for different
+            # TODO: This could become a separate type to allow for different
             # future behavior:
             elsif ( $is_block_function{$package}{$sub_name} ) {
                 $type = 'G';
@@ -7278,7 +7202,7 @@ sub scan_id_do {
                     rtokens         => $rtokens,
                     rtoken_map      => $rtoken_map,
                     id_scan_state   => $id_scan_state,
-                    max_token_index => $max_token_index
+                    max_token_index => $max_token_index,
                 }
             );
         }
@@ -7527,7 +7451,6 @@ BEGIN {
     sub do_id_scan_state_dollar {
 
         # We saw a sigil, now looking to start a variable name
-
         if ( $tok eq '$' ) {
 
             $identifier .= $tok;
@@ -7608,7 +7531,7 @@ BEGIN {
 
         elsif ( $tok eq '{' ) {
 
-            # check for something like ${#} or ${©}
+            # check for something like ${#} or ${?}, where ? is a special char
             if (
                 (
                        $identifier eq '$'
@@ -8053,15 +7976,15 @@ BEGIN {
         # return flag telling caller to split the pretoken
         my $split_pretoken_flag;
 
-        ####################
+        #-------------------
         # Initialize my vars
-        ####################
+        #-------------------
 
         initialize_my_scan_id_vars();
 
-        #########################################################
+        #--------------------------------------------------------
         # get started by defining a type and a state if necessary
-        #########################################################
+        #--------------------------------------------------------
 
         if ( !$id_scan_state ) {
             $context = UNKNOWN_CONTEXT;
@@ -8073,7 +7996,11 @@ BEGIN {
             }
             $identifier = $tok;
 
-            if ( $tok eq '$' || $tok eq '*' ) {
+            if ( $last_nonblank_token eq '->' ) {
+                $identifier    = '->' . $identifier;
+                $id_scan_state = $scan_state_SIGIL;
+            }
+            elsif ( $tok eq '$' || $tok eq '*' ) {
                 $id_scan_state = $scan_state_SIGIL;
                 $context       = SCALAR_CONTEXT;
             }
@@ -8111,6 +8038,8 @@ BEGIN {
                     $tokenizer_self->[_in_error_] = 1;
                 }
                 $id_scan_state = EMPTY_STRING;
+
+                # emergency return
                 goto RETURN;
             }
             $saw_type = !$saw_alpha;
@@ -8128,9 +8057,9 @@ EOM
             }
         }
 
-        ###############################
+        #------------------------------
         # loop to gather the identifier
-        ###############################
+        #------------------------------
 
         $i_save = $i;
 
@@ -8181,9 +8110,9 @@ EOM
 
         } ## end of main loop
 
-        ##############
+        #-------------
         # Check result
-        ##############
+        #-------------
 
         # Be sure a valid state is returned
         if ($id_scan_state) {
@@ -8631,7 +8560,7 @@ EOM
     } ## end sub do_scan_sub
 }
 
-#########i###############################################################
+#########################################################################
 # Tokenizer utility routines which may use CONSTANTS but no other GLOBALS
 #########################################################################
 
@@ -8650,12 +8579,31 @@ sub find_next_nonblank_token {
     }
 
     my $next_nonblank_token = $rtokens->[ ++$i ];
-    return ( SPACE, $i ) unless defined($next_nonblank_token);
+    return ( SPACE, $i )
+      unless ( defined($next_nonblank_token) && length($next_nonblank_token) );
+
+    # Quick test for nonblank ascii char. Note that we just have to
+    # examine the first character here.
+    my $ord = ord( substr( $next_nonblank_token, 0, 1 ) );
+    if (   $ord >= ORD_PRINTABLE_MIN
+        && $ord <= ORD_PRINTABLE_MAX )
+    {
+        return ( $next_nonblank_token, $i );
+    }
 
-    if ( $next_nonblank_token =~ /^\s*$/ ) {
+    # Quick test to skip over an ascii space or tab
+    elsif ( $ord == ORD_SPACE || $ord == ORD_TAB ) {
         $next_nonblank_token = $rtokens->[ ++$i ];
         return ( SPACE, $i ) unless defined($next_nonblank_token);
     }
+
+    # Slow test to skip over something else identified as whitespace
+    elsif ( $next_nonblank_token =~ /^\s*$/ ) {
+        $next_nonblank_token = $rtokens->[ ++$i ];
+        return ( SPACE, $i ) unless defined($next_nonblank_token);
+    }
+
+    # We should be at a nonblank now
     return ( $next_nonblank_token, $i );
 } ## end sub find_next_nonblank_token
 
@@ -8675,23 +8623,27 @@ sub find_next_noncomment_type {
           find_next_nonblank_token( $i_next, $rtokens, $max_token_index );
     }
 
-    goto RETURN if ( !$next_nonblank_token || $next_nonblank_token eq SPACE );
-
-    # check for possible a digraph
-    goto RETURN if ( !defined( $rtokens->[ $i_next + 1 ] ) );
-    my $test2 = $next_nonblank_token . $rtokens->[ $i_next + 1 ];
-    goto RETURN if ( !$is_digraph{$test2} );
-    $next_nonblank_token = $test2;
-    $i_next              = $i_next + 1;
-
-    # check for possible a trigraph
-    goto RETURN if ( !defined( $rtokens->[ $i_next + 1 ] ) );
-    my $test3 = $next_nonblank_token . $rtokens->[ $i_next + 1 ];
-    goto RETURN if ( !$is_trigraph{$test3} );
-    $next_nonblank_token = $test3;
-    $i_next              = $i_next + 1;
+    # check for a digraph
+    if (   $next_nonblank_token
+        && $next_nonblank_token ne SPACE
+        && defined( $rtokens->[ $i_next + 1 ] ) )
+    {
+        my $test2 = $next_nonblank_token . $rtokens->[ $i_next + 1 ];
+        if ( $is_digraph{$test2} ) {
+            $next_nonblank_token = $test2;
+            $i_next              = $i_next + 1;
+
+            # check for a trigraph
+            if ( defined( $rtokens->[ $i_next + 1 ] ) ) {
+                my $test3 = $next_nonblank_token . $rtokens->[ $i_next + 1 ];
+                if ( $is_trigraph{$test3} ) {
+                    $next_nonblank_token = $test3;
+                    $i_next              = $i_next + 1;
+                }
+            }
+        }
+    }
 
-  RETURN:
     return ( $next_nonblank_token, $i_next );
 } ## end sub find_next_noncomment_type
 
@@ -9269,10 +9221,18 @@ sub do_quote {
     #  $quoted_string_1 = quoted string seen while in_quote=1
     #  $quoted_string_2 = quoted string seen while in_quote=2
     my (
-        $i,               $in_quote,    $quote_character,
-        $quote_pos,       $quote_depth, $quoted_string_1,
-        $quoted_string_2, $rtokens,     $rtoken_map,
-        $max_token_index
+
+        $i,
+        $in_quote,
+        $quote_character,
+        $quote_pos,
+        $quote_depth,
+        $quoted_string_1,
+        $quoted_string_2,
+        $rtokens,
+        $rtoken_map,
+        $max_token_index,
+
     ) = @_;
 
     my $in_quote_starting = $in_quote;
@@ -9309,8 +9269,17 @@ sub do_quote {
             $quoted_string_1 .= "\n";
         }
     }
-    return ( $i, $in_quote, $quote_character, $quote_pos, $quote_depth,
-        $quoted_string_1, $quoted_string_2 );
+    return (
+
+        $i,
+        $in_quote,
+        $quote_character,
+        $quote_pos,
+        $quote_depth,
+        $quoted_string_1,
+        $quoted_string_2,
+
+    );
 } ## end sub do_quote
 
 sub follow_quoted_string {
@@ -9330,9 +9299,18 @@ sub follow_quoted_string {
     #   $quote_pos = index to check next for alphanumeric delimiter
     #   $quote_depth = nesting depth, since delimiters '{ ( [ <' can be nested.
     #   $quoted_string = the text of the quote (without quotation tokens)
-    my ( $i_beg, $in_quote, $rtokens, $beginning_tok, $quote_pos, $quote_depth,
-        $max_token_index )
-      = @_;
+    my (
+
+        $i_beg,
+        $in_quote,
+        $rtokens,
+        $beginning_tok,
+        $quote_pos,
+        $quote_depth,
+        $max_token_index,
+
+    ) = @_;
+
     my ( $tok, $end_tok );
     my $i             = $i_beg - 1;
     my $quoted_string = EMPTY_STRING;
@@ -9387,10 +9365,10 @@ sub follow_quoted_string {
     # characters, whereas for a non-alphanumeric delimiter, only tokens of
     # length 1 can match.
 
-    ###################################################################
+    #----------------------------------------------------------------
     # Case 1 (rare): loop for case of alphanumeric quote delimiter..
     # "quote_pos" is the position the current word to begin searching
-    ###################################################################
+    #----------------------------------------------------------------
     if ( $beginning_tok =~ /\w/ ) {
 
         # Note this because it is not recommended practice except
@@ -9449,9 +9427,9 @@ sub follow_quoted_string {
         }
     }
 
-    ########################################################################
+    #-----------------------------------------------------------------------
     # Case 2 (normal): loop for case of a non-alphanumeric quote delimiter..
-    ########################################################################
+    #-----------------------------------------------------------------------
     else {
 
         while ( $i < $max_token_index ) {
@@ -9479,8 +9457,16 @@ sub follow_quoted_string {
         }
     }
     if ( $i > $max_token_index ) { $i = $max_token_index }
-    return ( $i, $in_quote, $beginning_tok, $quote_pos, $quote_depth,
-        $quoted_string );
+    return (
+
+        $i,
+        $in_quote,
+        $beginning_tok,
+        $quote_pos,
+        $quote_depth,
+        $quoted_string,
+
+    );
 } ## end sub follow_quoted_string
 
 sub indicate_error {
@@ -9598,6 +9584,12 @@ sub write_on_underline {
 
 sub pre_tokenize {
 
+    my ( $str, $max_tokens_wanted ) = @_;
+
+    # Input parameter:
+    #  $max_tokens_wanted > 0  to stop on reaching this many tokens.
+    #                     = 0 means get all tokens
+
     # Break a string, $str, into a sequence of preliminary tokens.  We
     # are interested in these types of tokens:
     #   words       (type='w'),            example: 'max_tokens_wanted'
@@ -9611,9 +9603,8 @@ sub pre_tokenize {
     # An advantage of doing this pre-tokenization step is that it keeps almost
     # all of the regex work highly localized.  A disadvantage is that in some
     # very rare instances we will have to go back and split a pre-token.
-    my ( $str, $max_tokens_wanted ) = @_;
 
-    # we return references to these 3 arrays:
+    # Return parameters:
     my @tokens    = ();     # array of the tokens themselves
     my @token_map = (0);    # string position of start of each token
     my @type      = ();     # 'b'=whitespace, 'd'=digits, 'w'=alpha, or punct
index a5b2245a8a0f3ec6c73f79b662a603ca36e9e196..a1fcc1e68aaf7858059ea2651ad184f1f4929c0f 100644 (file)
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use Carp;
 use English qw( -no_match_vars );
-our $VERSION = '20220613';
+our $VERSION = '20221112';
 use Perl::Tidy::VerticalAligner::Alignment;
 use Perl::Tidy::VerticalAligner::Line;
 
@@ -120,6 +120,40 @@ EOM
     return;
 }
 
+my %valid_LINE_keys;
+
+BEGIN {
+
+    # define valid keys in a line object
+    my @q = qw(
+      jmax
+      rtokens
+      rfields
+      rfield_lengths
+      rpatterns
+      indentation
+      leading_space_count
+      outdent_long_lines
+      list_type
+      list_seqno
+      is_hanging_side_comment
+      maximum_line_length
+      rvertical_tightness_flags
+      is_terminal_ternary
+      j_terminal_match
+      end_group
+      Kend
+      ci_level
+      level
+      level_end
+      imax_pair
+
+      ralignments
+    );
+
+    @valid_LINE_keys{@q} = (1) x scalar(@q);
+}
+
 BEGIN {
 
     # Define the fixed indexes for variables in $self, which is an array
@@ -239,6 +273,40 @@ sub check_options {
     return;
 }
 
+sub check_keys {
+    my ( $rtest, $rvalid, $msg, $exact_match ) = @_;
+
+    # Check the keys of a hash:
+    # $rtest   = ref to hash to test
+    # $rvalid  = ref to hash with valid keys
+
+    # $msg = a message to write in case of error
+    # $exact_match defines the type of check:
+    #     = false: test hash must not have unknown key
+    #     = true:  test hash must have exactly same keys as known hash
+    my @unknown_keys =
+      grep { !exists $rvalid->{$_} } keys %{$rtest};
+    my @missing_keys =
+      grep { !exists $rtest->{$_} } keys %{$rvalid};
+    my $error = @unknown_keys;
+    if ($exact_match) { $error ||= @missing_keys }
+    if ($error) {
+        local $LIST_SEPARATOR = ')(';
+        my @expected_keys = sort keys %{$rvalid};
+        @unknown_keys = sort @unknown_keys;
+        Fault(<<EOM);
+------------------------------------------------------------------------
+Program error detected checking hash keys
+Message is: '$msg'
+Expected keys: (@expected_keys)
+Unknown key(s): (@unknown_keys)
+Missing key(s): (@missing_keys)
+------------------------------------------------------------------------
+EOM
+    }
+    return;
+} ## end sub check_keys
+
 sub new {
 
     my ( $class, @args ) = @_;
@@ -462,7 +530,13 @@ BEGIN {
 
 sub valign_input {
 
-    # Place one line in the current vertical group.
+    #---------------------------------------------------------------------
+    # This is the front door of the vertical aligner.  On each call
+    # we receive one line of specially marked text for vertical alignment.
+    # We compare the line with the current group, and either:
+    # - the line joins the current group if alignments match, or
+    # - the current group is flushed and a new group is started otherwise
+    #---------------------------------------------------------------------
     #
     # The key input parameters describing each line are:
     #     $level          = indentation level of this line
@@ -514,22 +588,47 @@ sub valign_input {
     # side comments.  Tabs in these fields can mess up the column counting.
     # The log file warns the user if there are any such tabs.
 
-    my ( $self, $rline_hash ) = @_;
-
-    my $level                     = $rline_hash->{level};
-    my $level_end                 = $rline_hash->{level_end};
-    my $indentation               = $rline_hash->{indentation};
-    my $list_seqno                = $rline_hash->{list_seqno};
-    my $outdent_long_lines        = $rline_hash->{outdent_long_lines};
-    my $is_terminal_ternary       = $rline_hash->{is_terminal_ternary};
-    my $rvertical_tightness_flags = $rline_hash->{rvertical_tightness_flags};
-    my $break_alignment_before    = $rline_hash->{break_alignment_before};
-    my $break_alignment_after     = $rline_hash->{break_alignment_after};
-    my $Kend                      = $rline_hash->{Kend};
-    my $ci_level                  = $rline_hash->{ci_level};
-    my $maximum_line_length       = $rline_hash->{maximum_line_length};
-    my $forget_side_comment       = $rline_hash->{forget_side_comment};
-    my $rline_alignment           = $rline_hash->{rline_alignment};
+    my ( $self, $rcall_hash ) = @_;
+
+    # Unpack the call args. This form is significantly faster than getting them
+    # one-by-one.
+    my (
+
+        $Kend,
+        $break_alignment_after,
+        $break_alignment_before,
+        $ci_level,
+        $forget_side_comment,
+        $indentation,
+        $is_terminal_ternary,
+        $level,
+        $level_end,
+        $list_seqno,
+        $maximum_line_length,
+        $outdent_long_lines,
+        $rline_alignment,
+        $rvertical_tightness_flags,
+
+      ) =
+
+      @{$rcall_hash}{
+        qw(
+          Kend
+          break_alignment_after
+          break_alignment_before
+          ci_level
+          forget_side_comment
+          indentation
+          is_terminal_ternary
+          level
+          level_end
+          list_seqno
+          maximum_line_length
+          outdent_long_lines
+          rline_alignment
+          rvertical_tightness_flags
+        )
+      };
 
     my ( $rtokens, $rfields, $rpatterns, $rfield_lengths ) =
       @{$rline_alignment};
@@ -670,17 +769,10 @@ sub valign_input {
     # --------------------------------------------------------------------
     # Collect outdentable block COMMENTS
     # --------------------------------------------------------------------
-    my $is_blank_line = EMPTY_STRING;
     if ( $self->[_group_type_] eq 'COMMENT' ) {
-        if (
-            (
-                   $is_block_comment
-                && $outdent_long_lines
-                && $leading_space_count ==
-                $self->[_comment_leading_space_count_]
-            )
-            || $is_blank_line
-          )
+        if (   $is_block_comment
+            && $outdent_long_lines
+            && $leading_space_count == $self->[_comment_leading_space_count_] )
         {
 
             # Note that for a comment group we are not storing a line
@@ -696,7 +788,7 @@ sub valign_input {
 
     my $rgroup_lines = $self->[_rgroup_lines_];
     if ( $break_alignment_before && @{$rgroup_lines} ) {
-        $rgroup_lines->[-1]->set_end_group(1);
+        $rgroup_lines->[-1]->{'end_group'} = 1;
     }
 
     # --------------------------------------------------------------------
@@ -736,23 +828,26 @@ sub valign_input {
         $self->[_zero_count_]++;
 
         if ( @{$rgroup_lines}
-            && !get_recoverable_spaces( $rgroup_lines->[0]->get_indentation() )
-          )
+            && !get_recoverable_spaces( $rgroup_lines->[0]->{'indentation'} ) )
         {
 
             # flush the current group if it has some aligned columns..
             # or we haven't seen a comment lately
-            if (   $rgroup_lines->[0]->get_jmax() > 1
+            if (   $rgroup_lines->[0]->{'jmax'} > 1
                 || $self->[_zero_count_] > 3 )
             {
                 $self->_flush_group_lines();
+
+                # Update '$rgroup_lines' - it will become a ref to empty array.
+                # This allows avoiding a call to get_group_line_count below.
+                $rgroup_lines = $self->[_rgroup_lines_];
             }
         }
 
         # start new COMMENT group if this comment may be outdented
         if (   $is_block_comment
             && $outdent_long_lines
-            && !$self->group_line_count() )
+            && !@{$rgroup_lines} )
         {
             $self->[_group_type_]                  = 'COMMENT';
             $self->[_comment_leading_space_count_] = $leading_space_count;
@@ -764,7 +859,7 @@ sub valign_input {
 
         # just write this line directly if no current group, no side comment,
         # and no space recovery is needed.
-        if (   !$self->group_line_count()
+        if (   !@{$rgroup_lines}
             && !get_recoverable_spaces($indentation) )
         {
 
@@ -804,6 +899,10 @@ sub valign_input {
     # --------------------------------------------------------------------
     # create an object to hold this line
     # --------------------------------------------------------------------
+
+    # The hash keys below must match the list of keys in %valid_LINE_keys.
+    # Values in this hash are accessed directly, except for 'ralignments',
+    # rather than with get/set calls for efficiency.
     my $new_line = Perl::Tidy::VerticalAligner::Line->new(
         {
             jmax                      => $jmax,
@@ -827,9 +926,15 @@ sub valign_input {
             level_end                 => $level_end,
             imax_pair                 => -1,
             maximum_line_length       => $maximum_line_length,
+
+            ralignments => [],
         }
     );
 
+    DEVEL_MODE
+      && check_keys( $new_line, \%valid_LINE_keys,
+        "Checking line keys at line definition", 1 );
+
     # --------------------------------------------------------------------
     # Decide if this is a simple list of items.
     # We use this to be less restrictive in deciding what to align.
@@ -877,35 +982,37 @@ sub join_hanging_comment {
     # the coding.
     my ( $new_line, $old_line ) = @_;
 
-    my $jmax = $new_line->get_jmax();
+    my $jmax = $new_line->{'jmax'};
 
     # must be 2 fields
     return 0 unless $jmax == 1;
-    my $rtokens = $new_line->get_rtokens();
+    my $rtokens = $new_line->{'rtokens'};
 
     # the second field must be a comment
     return 0 unless $rtokens->[0] eq '#';
-    my $rfields = $new_line->get_rfields();
+    my $rfields = $new_line->{'rfields'};
 
     # the first field must be empty
     return 0 unless $rfields->[0] =~ /^\s*$/;
 
     # the current line must have fewer fields
-    my $maximum_field_index = $old_line->get_jmax();
+    my $maximum_field_index = $old_line->{'jmax'};
     return 0
       unless $maximum_field_index > $jmax;
 
     # looks ok..
-    my $rpatterns      = $new_line->get_rpatterns();
-    my $rfield_lengths = $new_line->get_rfield_lengths();
+    my $rpatterns      = $new_line->{'rpatterns'};
+    my $rfield_lengths = $new_line->{'rfield_lengths'};
 
-    $new_line->set_is_hanging_side_comment(1);
-    $jmax = $maximum_field_index;
-    $new_line->set_jmax($jmax);
+    $new_line->{'is_hanging_side_comment'} = 1;
+
+    $jmax                     = $maximum_field_index;
+    $new_line->{'jmax'}       = $jmax;
     $rfields->[$jmax]         = $rfields->[1];
     $rfield_lengths->[$jmax]  = $rfield_lengths->[1];
     $rtokens->[ $jmax - 1 ]   = $rtokens->[0];
     $rpatterns->[ $jmax - 1 ] = $rpatterns->[0];
+
     foreach my $j ( 1 .. $jmax - 1 ) {
         $rfields->[$j]         = EMPTY_STRING;
         $rfield_lengths->[$j]  = 0;
@@ -934,13 +1041,13 @@ sub join_hanging_comment {
         # of the field separators are commas or comma-arrows (except for the
         # trailing #)
 
-        my $rtokens    = $line->get_rtokens();
+        my $rtokens    = $line->{'rtokens'};
         my $test_token = $rtokens->[0];
         my ( $raw_tok, $lev, $tag, $tok_count ) =
           decode_alignment_token($test_token);
         if ( $is_comma_token{$raw_tok} ) {
             my $list_type = $test_token;
-            my $jmax      = $line->get_jmax();
+            my $jmax      = $line->{'jmax'};
 
             foreach ( 1 .. $jmax - 2 ) {
                 ( $raw_tok, $lev, $tag, $tok_count ) =
@@ -950,7 +1057,7 @@ sub join_hanging_comment {
                     last;
                 }
             }
-            $line->set_list_type($list_type);
+            $line->{'list_type'} = $list_type;
         }
         return;
     }
@@ -983,11 +1090,11 @@ sub fix_terminal_ternary {
     }
 
     my $jmax        = @{$rfields} - 1;
-    my $rfields_old = $old_line->get_rfields();
+    my $rfields_old = $old_line->{'rfields'};
 
-    my $rpatterns_old       = $old_line->get_rpatterns();
-    my $rtokens_old         = $old_line->get_rtokens();
-    my $maximum_field_index = $old_line->get_jmax();
+    my $rpatterns_old       = $old_line->{'rpatterns'};
+    my $rtokens_old         = $old_line->{'rtokens'};
+    my $maximum_field_index = $old_line->{'jmax'};
 
     # look for the question mark after the :
     my ($jquestion);
@@ -1158,7 +1265,7 @@ sub fix_terminal_else {
     }
 
     # check for balanced else block following if/elsif/unless
-    my $rfields_old = $old_line->get_rfields();
+    my $rfields_old = $old_line->{'rfields'};
 
     # TBD: add handling for 'case'
     return unless ( $rfields_old->[0] =~ /^(if|elsif|unless)\s*$/ );
@@ -1171,9 +1278,9 @@ sub fix_terminal_else {
     # probably:  "else # side_comment"
     else { return }
 
-    my $rpatterns_old       = $old_line->get_rpatterns();
-    my $rtokens_old         = $old_line->get_rtokens();
-    my $maximum_field_index = $old_line->get_jmax();
+    my $rpatterns_old       = $old_line->{'rpatterns'};
+    my $rtokens_old         = $old_line->{'rtokens'};
+    my $maximum_field_index = $old_line->{'jmax'};
 
     # be sure the previous if/elsif is followed by an opening paren
     my $jparen    = 0;
@@ -1209,10 +1316,17 @@ sub fix_terminal_else {
 my %is_closing_block_type;
 
 BEGIN {
-    @_ = qw< } ] >;
-    @is_closing_block_type{@_} = (1) x scalar(@_);
+    my @q = qw< } ] >;
+    @is_closing_block_type{@q} = (1) x scalar(@q);
 }
 
+# This is a flag for testing alignment by sub sweep_left_to_right only.
+# This test can help find problems with the alignment logic.
+# This flag should normally be zero.
+use constant TEST_SWEEP_ONLY => 0;
+
+use constant EXPLAIN_CHECK_MATCH => 0;
+
 sub check_match {
 
     # See if the current line matches the current vertical alignment group.
@@ -1225,9 +1339,15 @@ sub check_match {
     #  $prev_line = the line just before $new_line
 
     # returns a flag and a value as follows:
-    #    return (0, $imax_align)     if the line does not match
-    #    return (1, $imax_align)     if the line matches but does not fit
-    #    return (2, $imax_align)     if the line matches and fits
+    #    return (0, $imax_align)   if the line does not match
+    #    return (1, $imax_align)   if the line matches but does not fit
+    #    return (2, $imax_align)   if the line matches and fits
+
+    use constant NO_MATCH      => 0;
+    use constant MATCH_NO_FIT  => 1;
+    use constant MATCH_AND_FIT => 2;
+
+    my $return_value;
 
     # Returns '$imax_align' which is the index of the maximum matching token.
     # It will be used in the subsequent left-to-right sweep to align as many
@@ -1236,22 +1356,16 @@ sub check_match {
 
     # variable $GoToMsg explains reason for no match, for debugging
     my $GoToMsg = EMPTY_STRING;
-    use constant EXPLAIN_CHECK_MATCH => 0;
 
-    # This is a flag for testing alignment by sub sweep_left_to_right only.
-    # This test can help find problems with the alignment logic.
-    # This flag should normally be zero.
-    use constant TEST_SWEEP_ONLY => 0;
-
-    my $jmax                = $new_line->get_jmax();
-    my $maximum_field_index = $base_line->get_jmax();
+    my $jmax                = $new_line->{'jmax'};
+    my $maximum_field_index = $base_line->{'jmax'};
 
     my $jlimit = $jmax - 2;
     if ( $jmax > $maximum_field_index ) {
         $jlimit = $maximum_field_index - 2;
     }
 
-    if ( $new_line->get_is_hanging_side_comment() ) {
+    if ( $new_line->{'is_hanging_side_comment'} ) {
 
         # HSC's can join the group if they fit
     }
@@ -1261,52 +1375,54 @@ sub check_match {
 
         # A group with hanging side comments ends with the first non hanging
         # side comment.
-        if ( $base_line->get_is_hanging_side_comment() ) {
-            $GoToMsg = "end of hanging side comments";
-            goto NO_MATCH;
+        if ( $base_line->{'is_hanging_side_comment'} ) {
+            $GoToMsg      = "end of hanging side comments";
+            $return_value = NO_MATCH;
         }
+        else {
 
-        # The number of tokens that this line shares with the previous line
-        # has been stored with the previous line.  This value was calculated
-        # and stored by sub 'match_line_pair'.
-        $imax_align = $prev_line->get_imax_pair();
+            # The number of tokens that this line shares with the previous
+            # line has been stored with the previous line.  This value was
+            # calculated and stored by sub 'match_line_pair'.
+            $imax_align = $prev_line->{'imax_pair'};
 
-        if ( $imax_align != $jlimit ) {
-            $GoToMsg = "Not all tokens match: $imax_align != $jlimit\n";
-            goto NO_MATCH;
+            if ( $imax_align != $jlimit ) {
+                $GoToMsg = "Not all tokens match: $imax_align != $jlimit\n";
+                $return_value = NO_MATCH;
+            }
         }
-
     }
 
-    # The tokens match, but the lines must have identical number of
-    # tokens to join the group.
-    if ( $maximum_field_index != $jmax ) {
-        $GoToMsg = "token count differs";
-        goto NO_MATCH;
-    }
+    if ( !defined($return_value) ) {
 
-    # The tokens match. Now See if there is space for this line in the
-    # current group.
-    if ( $self->check_fit( $new_line, $base_line ) && !TEST_SWEEP_ONLY ) {
+        # The tokens match, but the lines must have identical number of
+        # tokens to join the group.
+        if ( $maximum_field_index != $jmax ) {
+            $GoToMsg      = "token count differs";
+            $return_value = NO_MATCH;
+        }
 
-        EXPLAIN_CHECK_MATCH
-          && print "match and fit, imax_align=$imax_align, jmax=$jmax\n";
-        return ( 2, $jlimit );
-    }
-    else {
+        # The tokens match. Now See if there is space for this line in the
+        # current group.
+        elsif ( $self->check_fit( $new_line, $base_line ) && !TEST_SWEEP_ONLY )
+        {
 
-        EXPLAIN_CHECK_MATCH
-          && print "match but no fit, imax_align=$imax_align, jmax=$jmax\n";
-        return ( 1, $jlimit );
+            $GoToMsg = "match and fit, imax_align=$imax_align, jmax=$jmax\n";
+            $return_value = MATCH_AND_FIT;
+            $imax_align   = $jlimit;
+        }
+        else {
+            $GoToMsg = "match but no fit, imax_align=$imax_align, jmax=$jmax\n";
+            $return_value = MATCH_NO_FIT;
+            $imax_align   = $jlimit;
+        }
     }
 
-  NO_MATCH:
-
     EXPLAIN_CHECK_MATCH
       && print
-      "no match because $GoToMsg, max match index =i $imax_align, jmax=$jmax\n";
+"returning $return_value because $GoToMsg, max match index =i $imax_align, jmax=$jmax\n";
 
-    return ( 0, $imax_align );
+    return ( $return_value, $imax_align );
 }
 
 sub check_fit {
@@ -1319,12 +1435,12 @@ sub check_fit {
     #   return true if successful
     #   return false if not successful
 
-    my $jmax                = $new_line->get_jmax();
-    my $leading_space_count = $new_line->get_leading_space_count();
-    my $rfield_lengths      = $new_line->get_rfield_lengths();
+    my $jmax                = $new_line->{'jmax'};
+    my $leading_space_count = $new_line->{'leading_space_count'};
+    my $rfield_lengths      = $new_line->{'rfield_lengths'};
     my $padding_available   = $old_line->get_available_space_on_right();
-    my $jmax_old            = $old_line->get_jmax();
-    my $rtokens_old         = $old_line->get_rtokens();
+    my $jmax_old            = $old_line->{'jmax'};
+    my $rtokens_old         = $old_line->{'rtokens'};
 
     # Safety check ... only lines with equal array sizes should arrive here
     # from sub check_match.  So if this error occurs, look at recent changes in
@@ -1340,15 +1456,15 @@ EOM
     }
 
     # Save current columns in case this line does not fit.
-    my @alignments = $old_line->get_alignments();
+    my @alignments = @{ $old_line->{'ralignments'} };
     foreach my $alignment (@alignments) {
         $alignment->save_column();
     }
 
-    my $is_hanging_side_comment = $new_line->get_is_hanging_side_comment();
+    my $is_hanging_side_comment = $new_line->{'is_hanging_side_comment'};
 
     # Loop over all alignments ...
-    my $maximum_field_index = $old_line->get_jmax();
+    my $maximum_field_index = $old_line->{'jmax'};
     for my $j ( 0 .. $jmax ) {
 
         my $pad = $rfield_lengths->[$j] - $old_line->current_field_width($j);
@@ -1363,9 +1479,9 @@ EOM
         # Revert to the starting state if does not fit
         if ( $pad > $padding_available ) {
 
-            ################################################
+            #----------------------------------------------
             # Line does not fit -- revert to starting state
-            ################################################
+            #----------------------------------------------
             foreach my $alignment (@alignments) {
                 $alignment->restore_column();
             }
@@ -1377,9 +1493,9 @@ EOM
         $padding_available -= $pad;
     }
 
-    ######################################
+    #-------------------------------------
     # The line fits, the match is accepted
-    ######################################
+    #-------------------------------------
     return 1;
 
 }
@@ -1388,25 +1504,27 @@ sub install_new_alignments {
 
     my ($new_line) = @_;
 
-    my $jmax           = $new_line->get_jmax();
-    my $rfield_lengths = $new_line->get_rfield_lengths();
-    my $col            = $new_line->get_leading_space_count();
+    my $jmax           = $new_line->{'jmax'};
+    my $rfield_lengths = $new_line->{'rfield_lengths'};
+    my $col            = $new_line->{'leading_space_count'};
 
+    my @alignments;
     for my $j ( 0 .. $jmax ) {
         $col += $rfield_lengths->[$j];
 
         # create initial alignments for the new group
         my $alignment =
           Perl::Tidy::VerticalAligner::Alignment->new( { column => $col } );
-        $new_line->set_alignment( $j, $alignment );
+        push @alignments, $alignment;
     }
+    $new_line->{'ralignments'} = \@alignments;
     return;
 }
 
 sub copy_old_alignments {
     my ( $new_line, $old_line ) = @_;
-    my @new_alignments = $old_line->get_alignments();
-    $new_line->set_alignments(@new_alignments);
+    my @new_alignments = @{ $old_line->{'ralignments'} };
+    $new_line->{'ralignments'} = \@new_alignments;
     return;
 }
 
@@ -1533,18 +1651,18 @@ sub _flush_group_lines {
 "APPEND0: _flush_group_lines called from $a $b $c lines=$nlines, type=$group_type \n";
     };
 
-    ############################################
+    #-------------------------------------------
     # Section 1: Handle a group of COMMENT lines
-    ############################################
+    #-------------------------------------------
     if ( $group_type eq 'COMMENT' ) {
         $self->_flush_comment_lines();
         return;
     }
 
-    #########################################################################
+    #------------------------------------------------------------------------
     # Section 2: Handle line(s) of CODE.  Most of the actual work of vertical
     # aligning happens here in the following steps:
-    #########################################################################
+    #------------------------------------------------------------------------
 
     # STEP 1: Remove most unmatched tokens. They block good alignments.
     my ( $max_lev_diff, $saw_side_comment ) =
@@ -1583,7 +1701,7 @@ sub _flush_group_lines {
         # Otherwise, assume the next line has the level of the end of last line.
         # This fixes case c008.
         else {
-            my $level_end = $rgroup_lines->[-1]->get_level_end();
+            my $level_end = $rgroup_lines->[-1]->{'level_end'};
             $extra_indent_ok = $group_level > $level_end;
         }
     }
@@ -1596,9 +1714,8 @@ sub _flush_group_lines {
     # STEP 6: Output the lines.
     # All lines in this group have the same leading spacing and maximum line
     # length
-    my $group_leader_length = $rgroup_lines->[0]->get_leading_space_count();
-    my $group_maximum_line_length =
-      $rgroup_lines->[0]->get_maximum_line_length();
+    my $group_leader_length       = $rgroup_lines->[0]->{'leading_space_count'};
+    my $group_maximum_line_length = $rgroup_lines->[0]->{'maximum_line_length'};
 
     foreach my $line ( @{$rgroup_lines} ) {
         $self->valign_output_step_A(
@@ -1617,7 +1734,7 @@ sub _flush_group_lines {
     # Let the formatter know that this object has been processed and any
     # recoverable spaces have been handled.  This is needed for setting the
     # closing paren location in -lp mode.
-    my $object = $rgroup_lines->[0]->get_indentation();
+    my $object = $rgroup_lines->[0]->{'indentation'};
     if ( ref($object) ) { $object->set_recoverable_spaces(0) }
 
     $self->initialize_for_new_group();
@@ -1680,16 +1797,16 @@ sub _flush_group_lines {
             my $line_0 = $rall_lines->[$jbeg];
             my $line_1 = $rall_lines->[$jend];
 
-            my $imax_pair = $line_1->get_imax_pair();
+            my $imax_pair = $line_1->{'imax_pair'};
             if ( $imax_pair > $imax_align ) { $imax_align = $imax_pair }
 
             ## flag for possible future use:
             ## my $is_isolated_pair = $imax_pair < 0
             ##  && ( $jbeg == 0
-            ##    || $rall_lines->[ $jbeg - 1 ]->get_imax_pair() < 0 );
+            ##    || $rall_lines->[ $jbeg - 1 ]->{'imax_pair'} < 0 );
 
             my $imax_prev =
-              $jbeg > 0 ? $rall_lines->[ $jbeg - 1 ]->get_imax_pair() : -1;
+              $jbeg > 0 ? $rall_lines->[ $jbeg - 1 ]->{'imax_pair'} : -1;
 
             my ( $is_marginal, $imax_align_fix ) =
               is_marginal_match( $line_0, $line_1, $grp_level, $imax_align,
@@ -1732,7 +1849,7 @@ sub _flush_group_lines {
 
         # Unset the _end_group flag for the last line if it it set because it
         # is not needed and can causes problems for -lp formatting
-        $rall_lines->[-1]->set_end_group(0);
+        $rall_lines->[-1]->{'end_group'} = 0;
 
         # Loop over all lines ...
         my $jline = -1;
@@ -1742,13 +1859,13 @@ sub _flush_group_lines {
             # Start a new subgroup if necessary
             if ( !$group_line_count ) {
                 add_to_rgroup($jline);
-                if ( $new_line->get_end_group() ) {
+                if ( $new_line->{'end_group'} ) {
                     end_rgroup(-1);
                 }
                 next;
             }
 
-            my $j_terminal_match = $new_line->get_j_terminal_match();
+            my $j_terminal_match = $new_line->{'j_terminal_match'};
             my ( $jbeg, $jend ) = get_rgroup_jrange();
             if ( !defined($jbeg) ) {
 
@@ -1797,7 +1914,7 @@ EOM
             #
             # If this were not desired, the next step could be skipped.
             # -------------------------------------------------------------
-            if ( $new_line->get_is_hanging_side_comment() ) {
+            if ( $new_line->{'is_hanging_side_comment'} ) {
                 join_hanging_comment( $new_line, $base_line );
             }
 
@@ -1805,15 +1922,15 @@ EOM
             # BEFORE this line unless both it and the previous line have side
             # comments.  This prevents this line from pushing side comments out
             # to the right.
-            elsif ( $new_line->get_jmax() == 1 ) {
+            elsif ( $new_line->{'jmax'} == 1 ) {
 
                 # There are no matching tokens, so now check side comments.
                 # Programming note: accessing arrays with index -1 is
                 # risky in Perl, but we have verified there is at least one
                 # line in the group and that there is at least one field.
                 my $prev_comment =
-                  $rall_lines->[ $jline - 1 ]->get_rfields()->[-1];
-                my $side_comment = $new_line->get_rfields()->[-1];
+                  $rall_lines->[ $jline - 1 ]->{'rfields'}->[-1];
+                my $side_comment = $new_line->{'rfields'}->[-1];
                 end_rgroup(-1) unless ( $side_comment && $prev_comment );
             }
 
@@ -1869,7 +1986,7 @@ EOM
                     }
 
                     # do not let sweep_left_to_right change an isolated 'else'
-                    if ( !$new_line->get_is_terminal_ternary() ) {
+                    if ( !$new_line->{'is_terminal_ternary'} ) {
                         block_penultimate_match();
                     }
                 }
@@ -1877,7 +1994,7 @@ EOM
             }
 
             # end the group if we know we cannot match next line.
-            elsif ( $new_line->get_end_group() ) {
+            elsif ( $new_line->{'end_group'} ) {
                 end_rgroup(-1);
             }
         } ## end loop over lines
@@ -1909,8 +2026,8 @@ sub two_line_pad {
     #       'VARCHAR', DBI::SQL_VARCHAR, undef, "'", "'", undef, 0, 1,
     #       1, 0, 0, 0, undef, 0, 0
     #   ];
-    my $rfield_lengths   = $line->get_rfield_lengths();
-    my $rfield_lengths_m = $line_m->get_rfield_lengths();
+    my $rfield_lengths   = $line->{'rfield_lengths'};
+    my $rfield_lengths_m = $line_m->{'rfield_lengths'};
 
     # Safety check - shouldn't happen
     return 0
@@ -1927,10 +2044,10 @@ sub two_line_pad {
       $lensum >= $lensum_m ? ( $lensum_m, $lensum ) : ( $lensum, $lensum_m );
 
     my $patterns_match;
-    if ( $line_m->get_list_type() && $line->get_list_type() ) {
+    if ( $line_m->{'list_type'} && $line->{'list_type'} ) {
         $patterns_match = 1;
-        my $rpatterns_m = $line_m->get_rpatterns();
-        my $rpatterns   = $line->get_rpatterns();
+        my $rpatterns_m = $line_m->{'rpatterns'};
+        my $rpatterns   = $line->{'rpatterns'};
         foreach my $i ( 0 .. $imax_min ) {
             my $pat   = $rpatterns->[$i];
             my $pat_m = $rpatterns_m->[$i];
@@ -1964,9 +2081,9 @@ sub sweep_left_to_right {
     my $ng_max = @{$rgroups} - 1;
     return unless ( $ng_max > 0 );
 
-    ############################################################################
+    #---------------------------------------------------------------------
     # Step 1: Loop over groups to find all common leading alignment tokens
-    ############################################################################
+    #---------------------------------------------------------------------
 
     my $line;
     my $rtokens;
@@ -2017,8 +2134,8 @@ sub sweep_left_to_right {
         ( $jbeg, $jend, $istop ) = @{$item};
 
         $line    = $rlines->[$jbeg];
-        $rtokens = $line->get_rtokens();
-        $imax    = $line->get_jmax() - 2;
+        $rtokens = $line->{'rtokens'};
+        $imax    = $line->{'jmax'} - 2;
         $istop   = -1 unless ( defined($istop) );
         $istop   = $imax if ( $istop > $imax );
 
@@ -2036,13 +2153,13 @@ sub sweep_left_to_right {
         # Special treatment of two one-line groups isolated from other lines,
         # unless they form a simple list or a terminal match.  Otherwise the
         # alignment can look strange in some cases.
-        my $list_type = $rlines->[$jbeg]->get_list_type();
+        my $list_type = $rlines->[$jbeg]->{'list_type'};
         if (
                $jend == $jbeg
             && $jend_m == $jbeg_m
             && ( $ng == 1 || $istop_mm < 0 )
             && ( $ng == $ng_max || $istop < 0 )
-            && !$line->get_j_terminal_match()
+            && !$line->{'j_terminal_match'}
 
             # Only do this for imperfect matches. This is normally true except
             # when two perfect matches cannot form a group because the line
@@ -2081,9 +2198,9 @@ sub sweep_left_to_right {
     }
     return unless @icommon;
 
-    ###########################################################
+    #----------------------------------------------------------
     # Step 2: Reorder and consolidate the list into a task list
-    ###########################################################
+    #----------------------------------------------------------
 
     # We have to work first from lowest token index to highest, then by group,
     # sort our list first on token index then group number
@@ -2109,9 +2226,9 @@ sub sweep_left_to_right {
         push @todo, [ $i, $ng_beg, $ng_end, $raw_tok, $lev ];
     }
 
-    ###############################
+    #------------------------------
     # Step 3: Execute the task list
-    ###############################
+    #------------------------------
     do_left_to_right_sweep( $rlines, $rgroups, \@todo, \%max_move, $short_pad,
         $group_level );
     return;
@@ -2164,7 +2281,7 @@ sub sweep_left_to_right {
         # $blocking_level[$nj is the level at a match failure between groups
         # $ng-1 and $ng
         my @blocking_level;
-        my $group_list_type = $rlines->[0]->get_list_type();
+        my $group_list_type = $rlines->[0]->{'list_type'};
 
         my $move_to_common_column = sub {
 
@@ -2226,7 +2343,7 @@ sub sweep_left_to_right {
                 # (the first line).  All of the rest will be changed
                 # automatically.
                 my $line = $rlines->[$ix_beg];
-                my $jmax = $line->get_jmax();
+                my $jmax = $line->{'jmax'};
 
                 # the maximum space without exceeding the line length:
                 my $avail   = $line->get_available_space_on_right();
@@ -2385,12 +2502,12 @@ sub delete_selected_tokens {
 
     return unless ( defined($line_obj) && defined($ridel) && @{$ridel} );
 
-    my $jmax_old           = $line_obj->get_jmax();
-    my $rfields_old        = $line_obj->get_rfields();
-    my $rfield_lengths_old = $line_obj->get_rfield_lengths();
-    my $rpatterns_old      = $line_obj->get_rpatterns();
-    my $rtokens_old        = $line_obj->get_rtokens();
-    my $j_terminal_match   = $line_obj->get_j_terminal_match();
+    my $jmax_old           = $line_obj->{'jmax'};
+    my $rfields_old        = $line_obj->{'rfields'};
+    my $rfield_lengths_old = $line_obj->{'rfield_lengths'};
+    my $rpatterns_old      = $line_obj->{'rpatterns'};
+    my $rtokens_old        = $line_obj->{'rtokens'};
+    my $j_terminal_match   = $line_obj->{'j_terminal_match'};
 
     use constant EXPLAIN_DELETE_SELECTED => 0;
 
@@ -2447,28 +2564,28 @@ EOM
     #f   0      1        2        3    <- field and pattern
 
     my $jmax_new = @{$rfields_new} - 1;
-    $line_obj->set_rtokens($rtokens_new);
-    $line_obj->set_rpatterns($rpatterns_new);
-    $line_obj->set_rfields($rfields_new);
-    $line_obj->set_rfield_lengths($rfield_lengths_new);
-    $line_obj->set_jmax($jmax_new);
+    $line_obj->{'rtokens'}        = $rtokens_new;
+    $line_obj->{'rpatterns'}      = $rpatterns_new;
+    $line_obj->{'rfields'}        = $rfields_new;
+    $line_obj->{'rfield_lengths'} = $rfield_lengths_new;
+    $line_obj->{'jmax'}           = $jmax_new;
 
     # The value of j_terminal_match will be incorrect if we delete tokens prior
     # to it. We will have to give up on aligning the terminal tokens if this
     # happens.
     if ( defined($j_terminal_match) && $jmin_del <= $j_terminal_match ) {
-        $line_obj->set_j_terminal_match(undef);
+        $line_obj->{'j_terminal_match'} = undef;
     }
 
     # update list type -
-    if ( $line_obj->get_list_seqno() ) {
+    if ( $line_obj->{'list_seqno'} ) {
 
         ## This works, but for efficiency see if we need to make a change:
         ## decide_if_list($line_obj);
 
         # An existing list will still be a list but with possibly different
         # leading token
-        my $old_list_type = $line_obj->get_list_type();
+        my $old_list_type = $line_obj->{'list_type'};
         my $new_list_type = EMPTY_STRING;
         if ( $rtokens_new->[0] =~ /^(=>|,)/ ) {
             $new_list_type = $rtokens_new->[0];
@@ -2568,17 +2685,14 @@ EOM
 
     }
 
-    # This flag is for testing only and should normally be zero.
-    use constant TEST_DELETE_NULL => 0;
-
     sub delete_unmatched_tokens {
         my ( $rlines, $group_level ) = @_;
 
-        # This is a preliminary step in vertical alignment in which we remove
-        # as many obviously un-needed alignment tokens as possible.  This will
-        # prevent them from interfering with the final alignment.
+        # This is a important first step in vertical alignment in which
+        # we remove as many obviously un-needed alignment tokens as possible.
+        # This will prevent them from interfering with the final alignment.
 
-        # These are the return values
+        # Returns:
         my $max_lev_diff     = 0;    # used to avoid a call to prune_tree
         my $saw_side_comment = 0;    # used to avoid a call for side comments
 
@@ -2588,16 +2702,16 @@ EOM
         # Handle a single line
         if ( @{$rlines} == 1 ) {
             my $line   = $rlines->[0];
-            my $jmax   = $line->get_jmax();
-            my $length = $line->get_rfield_lengths()->[$jmax];
+            my $jmax   = $line->{'jmax'};
+            my $length = $line->{'rfield_lengths'}->[$jmax];
             $saw_side_comment = $length > 0;
             return ( $max_lev_diff, $saw_side_comment );
         }
 
-        my $has_terminal_match = $rlines->[-1]->get_j_terminal_match();
+        my $has_terminal_match = $rlines->[-1]->{'j_terminal_match'};
 
         # ignore hanging side comments in these operations
-        my @filtered   = grep { !$_->get_is_hanging_side_comment() } @{$rlines};
+        my @filtered   = grep { !$_->{'is_hanging_side_comment'} } @{$rlines};
         my $rnew_lines = \@filtered;
 
         $saw_side_comment = @filtered != @{$rlines};
@@ -2610,12 +2724,14 @@ EOM
         my @equals_info;
         my @line_info;
 
-        # create a hash of tokens for each line
+        #------------------------------------------------------------
+        # Loop to create a hash of alignment token info for each line
+        #------------------------------------------------------------
         my $rline_hashes = [];
         foreach my $line ( @{$rnew_lines} ) {
             my $rhash     = {};
-            my $rtokens   = $line->get_rtokens();
-            my $rpatterns = $line->get_rpatterns();
+            my $rtokens   = $line->{'rtokens'};
+            my $rpatterns = $line->{'rpatterns'};
             my $i         = 0;
             my ( $i_eq, $tok_eq, $pat_eq );
             my ( $lev_min, $lev_max );
@@ -2635,7 +2751,7 @@ EOM
                 }
                 else {
                     if ( !$saw_side_comment ) {
-                        my $length = $line->get_rfield_lengths()->[ $i + 1 ];
+                        my $length = $line->{'rfield_lengths'}->[ $i + 1 ];
                         $saw_side_comment ||= $length;
                     }
                 }
@@ -2665,7 +2781,9 @@ EOM
             }
         }
 
-        # compare each line pair and record matches
+        #----------------------------------------------------
+        # Loop to compare each line pair and remember matches
+        #----------------------------------------------------
         my $rtok_hash = {};
         my $nr        = 0;
         foreach my $jl ( 0 .. $jmax - 1 ) {
@@ -2674,12 +2792,8 @@ EOM
             my $jr      = $jl + 1;
             my $rhash_l = $rline_hashes->[$jl];
             my $rhash_r = $rline_hashes->[$jr];
-            my $count   = 0;                      # UNUSED NOW?
-            my $ntoks   = 0;
             foreach my $tok ( keys %{$rhash_l} ) {
-                $ntoks++;
                 if ( defined( $rhash_r->{$tok} ) ) {
-                    if ( $tok ne '#' ) { $count++; }
                     my $il = $rhash_l->{$tok}->[0];
                     my $ir = $rhash_r->{$tok}->[0];
                     $rhash_l->{$tok}->[2] = $ir;
@@ -2694,7 +2808,7 @@ EOM
             # Set a line break if no matching tokens between these lines
             # (this is not strictly necessary now but does not hurt)
             if ( $nr == 0 && $nl > 0 ) {
-                $rnew_lines->[$jl]->set_end_group(1);
+                $rnew_lines->[$jl]->{'end_group'} = 1;
             }
 
             # Also set a line break if both lines have simple equals but with
@@ -2717,8 +2831,8 @@ EOM
             if ( defined($i_eq_l) && defined($i_eq_r) ) {
 
                 # Also, do not align equals across a change in ci level
-                my $ci_jump = $rnew_lines->[$jl]->get_ci_level() !=
-                  $rnew_lines->[$jr]->get_ci_level();
+                my $ci_jump = $rnew_lines->[$jl]->{'ci_level'} !=
+                  $rnew_lines->[$jr]->{'ci_level'};
 
                 if (
                        $tok_eq_l eq $tok_eq_r
@@ -2728,16 +2842,19 @@ EOM
                         || $ci_jump )
                   )
                 {
-                    $rnew_lines->[$jl]->set_end_group(1);
+                    $rnew_lines->[$jl]->{'end_group'} = 1;
                 }
             }
         }
 
-        # find subgroups
+        #------------------------------------------------------------
+        # Find independent subgroups of lines.  Neighboring subgroups
+        # do not have a common alignment token.
+        #------------------------------------------------------------
         my @subgroups;
         push @subgroups, [ 0, $jmax ];
         foreach my $jl ( 0 .. $jmax - 1 ) {
-            if ( $rnew_lines->[$jl]->get_end_group() ) {
+            if ( $rnew_lines->[$jl]->{'end_group'} ) {
                 $subgroups[-1]->[1] = $jl;
                 push @subgroups, [ $jl + 1, $jmax ];
             }
@@ -2746,17 +2863,17 @@ EOM
         # flag to allow skipping pass 2
         my $saw_large_group;
 
-        ############################################################
+        #-----------------------------------------------------------
         # PASS 1 over subgroups to remove unmatched alignment tokens
-        ############################################################
+        #-----------------------------------------------------------
         foreach my $item (@subgroups) {
             my ( $jbeg, $jend ) = @{$item};
 
             my $nlines = $jend - $jbeg + 1;
 
-            ####################################################
+            #---------------------------------------------------
             # Look for complete if/elsif/else and ternary blocks
-            ####################################################
+            #---------------------------------------------------
 
             # We are looking for a common '$dividing_token' like these:
 
@@ -2787,7 +2904,7 @@ EOM
                 foreach my $jj ( $jbeg .. $jend ) {
                     my %seen;
                     my $line    = $rnew_lines->[$jj];
-                    my $rtokens = $line->get_rtokens();
+                    my $rtokens = $line->{'rtokens'};
                     foreach my $tok ( @{$rtokens} ) {
                         if ( !$seen{$tok} ) {
                             $seen{$tok}++;
@@ -2809,12 +2926,12 @@ EOM
                 }
             }
 
-            #####################################################
-            # Loop over lines to remove unwanted alignment tokens
-            #####################################################
+            #-------------------------------------------------------------
+            # Loop over subgroup lines to remove unwanted alignment tokens
+            #-------------------------------------------------------------
             foreach my $jj ( $jbeg .. $jend ) {
                 my $line    = $rnew_lines->[$jj];
-                my $rtokens = $line->get_rtokens();
+                my $rtokens = $line->{'rtokens'};
                 my $rhash   = $rline_hashes->[$jj];
                 my $i_eq    = $equals_info[$jj]->[0];
                 my @idel;
@@ -2832,10 +2949,10 @@ EOM
                     my ( $iii, $il, $ir, $raw_tok, $lev, $tag, $tok_count ) =
                       @{ $rhash->{$tok} };
 
-                    #######################################################
+                    #------------------------------------------------------
                     # Here is the basic RULE: remove an unmatched alignment
                     # which does not occur in the surrounding lines.
-                    #######################################################
+                    #------------------------------------------------------
                     my $delete_me = !defined($il) && !defined($ir);
 
                     # Apply any user controls. Note that not all lines pass
@@ -2881,7 +2998,7 @@ EOM
                     #    );
                     if ( defined($delete_above_level) ) {
                         if ( $lev > $delete_above_level ) {
-                            $delete_me ||= 1;    #$tag;
+                            $delete_me ||= 1;
                         }
                         else { $delete_above_level = undef }
                     }
@@ -2909,47 +3026,45 @@ EOM
                         && $nlines == 2 );
 
                     # EXCEPTION 5: misc additional rules for commas and equals
-                    if ($delete_me) {
+                    if ( $delete_me && $tok_count == 1 ) {
 
                         # okay to delete second and higher copies of a token
-                        if ( $tok_count == 1 ) {
-
-                            # for a comma...
-                            if ( $raw_tok eq ',' ) {
 
-                                # Do not delete commas before an equals
-                                $delete_me = 0
-                                  if ( defined($i_eq) && $i < $i_eq );
+                        # for a comma...
+                        if ( $raw_tok eq ',' ) {
 
-                                # Do not delete line-level commas
-                                $delete_me = 0 if ( $lev <= $group_level );
-                            }
+                            # Do not delete commas before an equals
+                            $delete_me = 0
+                              if ( defined($i_eq) && $i < $i_eq );
 
-                            # For an assignment at group level..
-                            if (   $is_assignment{$raw_tok}
-                                && $lev == $group_level )
-                            {
+                            # Do not delete line-level commas
+                            $delete_me = 0 if ( $lev <= $group_level );
+                        }
 
-                                # Do not delete if it is the last alignment of
-                                # multiple tokens; this will prevent some
-                                # undesirable alignments
-                                if ( $imax > 0 && $i == $imax ) {
-                                    $delete_me = 0;
-                                }
+                        # For an assignment at group level..
+                        if (   $is_assignment{$raw_tok}
+                            && $lev == $group_level )
+                        {
 
-                                # Otherwise, set a flag to delete most
-                                # remaining tokens
-                                else { $deleted_assignment_token = $raw_tok }
+                            # Do not delete if it is the last alignment of
+                            # multiple tokens; this will prevent some
+                            # undesirable alignments
+                            if ( $imax > 0 && $i == $imax ) {
+                                $delete_me = 0;
                             }
+
+                            # Otherwise, set a flag to delete most
+                            # remaining tokens
+                            else { $deleted_assignment_token = $raw_tok }
                         }
                     }
 
                     # Do not let a user exclusion be reactivated by above rules
                     $delete_me ||= !$align_ok;
 
-                    #####################################
+                    #------------------------------------
                     # Add this token to the deletion list
-                    #####################################
+                    #------------------------------------
                     if ($delete_me) {
                         push @idel, $i;
 
@@ -2977,212 +3092,23 @@ EOM
             }    # End loopover lines
         }    # End loop over subgroups
 
-        #################################################
-        # PASS 2 over subgroups to remove null alignments
-        #################################################
+        # End PASS 1
 
-        # This pass is only used for testing. It is helping to identify
-        # alignment situations which might be improved with a future more
-        # general algorithm which adds a tail matching capability.
-        if (TEST_DELETE_NULL) {
-            delete_null_alignments( $rnew_lines, $rline_hashes, \@subgroups )
-              if ($saw_large_group);
-        }
-
-        # PASS 3: Construct a tree of matched lines and delete some small deeper
-        # levels of tokens.  They also block good alignments.
+        #----------------------------------------------------------------
+        # PASS 2: Construct a tree of matched lines and delete some small
+        # deeper levels of tokens.  They also block good alignments.
+        #----------------------------------------------------------------
         prune_alignment_tree($rnew_lines) if ($max_lev_diff);
 
-        # PASS 4: compare all lines for common tokens
+        #--------------------------------------------
+        # PASS 3: compare all lines for common tokens
+        #--------------------------------------------
         match_line_pairs( $rlines, $rnew_lines, \@subgroups, $group_level );
 
         return ( $max_lev_diff, $saw_side_comment );
     }
 }
 
-sub delete_null_alignments {
-    my ( $rnew_lines, $rline_hashes, $rsubgroups ) = @_;
-
-    # This is an optional second pass for deleting alignment tokens which can
-    # occasionally improve alignment.  We look for and remove 'null
-    # alignments', which are alignments that require no padding.  So we can
-    # 'cheat' and delete them. For example, notice the '=~' alignment in the
-    # first two lines of the following code:
-
-    #    $sysname .= 'del' if $self->label =~ /deletion/;
-    #    $sysname .= 'ins' if $self->label =~ /insertion/;
-    #    $sysname .= uc $self->allele_ori->seq if $self->allele_ori->seq;
-
-    # These '=~' tokens are already aligned because they are both the same
-    # distance from the previous alignment token, the 'if'.  So we can
-    # eliminate them as alignments.  The advantage is that in some cases, such
-    # as this one, this will allow other tokens to be aligned. In this case we
-    # then get the 'if' tokens to align:
-
-    #   $sysname .= 'del'                     if $self->label =~ /deletion/;
-    #   $sysname .= 'ins'                     if $self->label =~ /insertion/;
-    #   $sysname .= uc $self->allele_ori->seq if $self->allele_ori->seq;
-
-    # The following rules for limiting this operation have been found to
-    # work well and avoid problems:
-
-    # Rule 1. We only consider a sequence of lines which have the same
-    # sequence of alignment tokens.
-
-    # Rule 2. We never eliminate the first alignment token.  One reason is that
-    # lines may have different leading indentation spaces, so keeping the
-    # first alignment token insures that our length measurements start at
-    # a well-defined point.  Another reason is that nothing is gained because
-    # the left-to-right sweep can always handle alignment of this token.
-
-    # Rule 3. We require that the first alignment token exist in either
-    # a previous line or a subsequent line.  The reason is that this avoids
-    # changing two-line matches which go through special logic.
-
-    # Rule 4. Do not delete a token which occurs in a previous or subsequent
-    # line. For example, in the above example, it was ok to eliminate the '=~'
-    # token from two lines because it did not occur in a surrounding line.
-    # If it did occur in a surrounding line, the result could be confusing
-    # or even incorrectly aligned.
-
-    # A consequence of these rules is that we only need to consider subgroups
-    # with at least 3 lines and 2 alignment tokens.
-
-    # The subgroup line index range
-    my ( $jbeg, $jend );
-
-    # Vars to keep track of the start of a current sequence of matching
-    # lines.
-    my $rtokens_match;
-    my $rfield_lengths_match;
-    my $j_match_beg;
-    my $j_match_end;
-    my $imax_match;
-    my $rneed_pad;
-
-    # Vars for a line being tested
-    my $rtokens;
-    my $rfield_lengths;
-    my $imax;
-
-    my $start_match = sub {
-        my ($jj) = @_;
-        $rtokens_match        = $rtokens;
-        $rfield_lengths_match = $rfield_lengths;
-        $j_match_beg          = $jj;
-        $j_match_end          = $jj;
-        $imax_match           = $imax;
-        $rneed_pad            = [];
-        return;
-    };
-
-    my $add_to_match = sub {
-        my ($jj) = @_;
-        $j_match_end = $jj;
-
-        # Keep track of any padding that would be needed for each token
-        foreach my $i ( 0 .. $imax ) {
-            next if ( $rneed_pad->[$i] );
-            my $length       = $rfield_lengths->[$i];
-            my $length_match = $rfield_lengths_match->[$i];
-            if ( $length ne $length_match ) { $rneed_pad->[$i] = 1 }
-        }
-        return;
-    };
-
-    my $end_match = sub {
-        return unless ( $j_match_end > $j_match_beg );
-        my $nlines    = $j_match_end - $j_match_beg + 1;
-        my $rhash_beg = $rline_hashes->[$j_match_beg];
-        my $rhash_end = $rline_hashes->[$j_match_end];
-        my @idel;
-
-        # Do not delete unless the first token also occurs in a surrounding line
-        my $tok0 = $rtokens_match->[0];
-        return
-          unless (
-            (
-                   $j_match_beg > $jbeg
-                && $rnew_lines->[ $j_match_beg - 1 ]->get_rtokens()->[0] eq
-                $tok0
-            )
-            || (   $j_match_end < $jend
-                && $rnew_lines->[ $j_match_end + 1 ]->get_rtokens()->[0] eq
-                $tok0 )
-          );
-
-        # Note that we are skipping the token at i=0
-        foreach my $i ( 1 .. $imax_match ) {
-
-            # do not delete a token which requires padding to align
-            next if ( $rneed_pad->[$i] );
-
-            my $tok = $rtokens_match->[$i];
-
-            # Do not delete a token which occurs in a surrounding line
-            next
-              if ( $j_match_beg > $jbeg
-                && defined( $rline_hashes->[ $j_match_beg - 1 ]->{$tok} ) );
-            next
-              if ( $j_match_end < $jend
-                && defined( $rline_hashes->[ $j_match_end + 1 ]->{$tok} ) );
-
-            # ok to delete
-            push @idel, $i;
-            ##print "ok to delete tok=$tok\n";
-        }
-        if (@idel) {
-            foreach my $j ( $j_match_beg .. $j_match_end ) {
-                delete_selected_tokens( $rnew_lines->[$j], \@idel );
-            }
-        }
-        return;
-    };
-
-    foreach my $item ( @{$rsubgroups} ) {
-        ( $jbeg, $jend ) = @{$item};
-        my $nlines = $jend - $jbeg + 1;
-        next unless ( $nlines > 2 );
-
-        foreach my $jj ( $jbeg .. $jend ) {
-            my $line = $rnew_lines->[$jj];
-            $rtokens        = $line->get_rtokens();
-            $rfield_lengths = $line->get_rfield_lengths();
-            $imax           = @{$rtokens} - 2;
-
-            # start a new match group
-            if ( $jj == $jbeg ) {
-                $start_match->($jj);
-                next;
-            }
-
-            # see if all tokens of this line match the current group
-            my $match;
-            if ( $imax == $imax_match ) {
-                foreach my $i ( 0 .. $imax ) {
-                    my $tok       = $rtokens->[$i];
-                    my $tok_match = $rtokens_match->[$i];
-                    last if ( $tok ne $tok_match );
-                }
-                $match = 1;
-            }
-
-            # yes, they all match
-            if ($match) {
-                $add_to_match->($jj);
-            }
-
-            # now, this line does not match
-            else {
-                $end_match->();
-                $start_match->($jj);
-            }
-        }    # End loopover lines
-        $end_match->();
-    }    # End loop over subgroups
-    return;
-} ## end sub delete_null_alignments
-
 sub match_line_pairs {
     my ( $rlines, $rnew_lines, $rsubgroups, $group_level ) = @_;
 
@@ -3216,7 +3142,7 @@ sub match_line_pairs {
 
         my ( $tok, $tok_m, $pat, $pat_m, $pad ) = @_;
         my $GoToMsg     = EMPTY_STRING;
-        my $return_code = 1;
+        my $return_code = 0;
 
         my ( $alignment_token, $lev, $tag, $tok_count ) =
           decode_alignment_token($tok);
@@ -3234,8 +3160,13 @@ sub match_line_pairs {
 
             # do not align commas unless they are in named
             # containers
-            $GoToMsg = "do not align commas in unnamed containers";
-            goto NO_MATCH unless ( $tok =~ /[A-Za-z]/ );
+            if ( $tok !~ /[A-Za-z]/ ) {
+                $return_code = 1;
+                $GoToMsg     = "do not align commas in unnamed containers";
+            }
+            else {
+                $return_code = 0;
+            }
         }
 
         # do not align parens unless patterns match;
@@ -3244,8 +3175,13 @@ sub match_line_pairs {
 
             # But we can allow a match if the parens don't
             # require any padding.
-            $GoToMsg = "do not align '(' unless patterns match or pad=0";
-            if ( $pad != 0 ) { goto NO_MATCH }
+            if ( $pad != 0 ) {
+                $return_code = 1;
+                $GoToMsg = "do not align '(' unless patterns match or pad=0";
+            }
+            else {
+                $return_code = 0;
+            }
         }
 
         # Handle an '=' alignment with different patterns to
@@ -3263,8 +3199,8 @@ sub match_line_pairs {
             # letter of the pattern.  This is crude, but works
             # well enough.
             if ( substr( $pat_m, 0, 1 ) ne substr( $pat, 0, 1 ) ) {
-                $GoToMsg = "first character before equals differ";
-                goto NO_MATCH;
+                $GoToMsg     = "first character before equals differ";
+                $return_code = 1;
             }
 
             # The introduction of sub 'prune_alignment_tree'
@@ -3287,20 +3223,22 @@ sub match_line_pairs {
             elsif (
                 ( index( $pat_m, ',' ) >= 0 ) ne ( index( $pat, ',' ) >= 0 ) )
             {
-                $GoToMsg = "mixed commas/no-commas before equals";
+                $GoToMsg     = "mixed commas/no-commas before equals";
+                $return_code = 1;
                 if ( $lev eq $group_level ) {
                     $return_code = 2;
                 }
-                goto NO_MATCH;
+            }
+            else {
+                $return_code = 0;
             }
         }
-
-      MATCH:
-        return ( 0, \$GoToMsg );
-
-      NO_MATCH:
+        else {
+            $return_code = 0;
+        }
 
         EXPLAIN_COMPARE_PATTERNS
+          && $return_code
           && print STDERR "no match because $GoToMsg\n";
 
         return ( $return_code, \$GoToMsg );
@@ -3325,12 +3263,12 @@ sub match_line_pairs {
             $ci_level_m       = $ci_level;
 
             $line           = $rnew_lines->[$jj];
-            $rtokens        = $line->get_rtokens();
-            $rpatterns      = $line->get_rpatterns();
-            $rfield_lengths = $line->get_rfield_lengths();
+            $rtokens        = $line->{'rtokens'};
+            $rpatterns      = $line->{'rpatterns'};
+            $rfield_lengths = $line->{'rfield_lengths'};
             $imax           = @{$rtokens} - 2;
-            $list_type      = $line->get_list_type();
-            $ci_level       = $line->get_ci_level();
+            $list_type      = $line->{'list_type'};
+            $ci_level       = $line->{'ci_level'};
 
             # nothing to do for first line
             next if ( $jj == $jbeg );
@@ -3343,18 +3281,18 @@ sub match_line_pairs {
 
             # find number of leading common tokens
 
-            #################################
+            #---------------------------------
             # No match to hanging side comment
-            #################################
-            if ( $line->get_is_hanging_side_comment() ) {
+            #---------------------------------
+            if ( $line->{'is_hanging_side_comment'} ) {
 
                 # Should not get here; HSC's have been filtered out
                 $imax_align = -1;
             }
 
-            ##############################
+            #-----------------------------
             # Handle comma-separated lists
-            ##############################
+            #-----------------------------
             elsif ( $list_type && $list_type eq $list_type_m ) {
 
                 # do not align lists across a ci jump with new list method
@@ -3373,9 +3311,9 @@ sub match_line_pairs {
                 $imax_align = $i_nomatch - 1;
             }
 
-            ##################
+            #-----------------
             # Handle non-lists
-            ##################
+            #-----------------
             else {
                 my $i_nomatch = $imax_min + 1;
                 foreach my $i ( 0 .. $imax_min ) {
@@ -3406,12 +3344,12 @@ sub match_line_pairs {
                 $imax_align = $i_nomatch - 1;
             }
 
-            $line_m->set_imax_pair($imax_align);
+            $line_m->{'imax_pair'} = $imax_align;
 
         } ## end loop over lines
 
         # Put fence at end of subgroup
-        $line->set_imax_pair(-1);
+        $line->{'imax_pair'} = -1;
 
     } ## end loop over subgroups
 
@@ -3420,11 +3358,11 @@ sub match_line_pairs {
     if ( @{$rlines} > @{$rnew_lines} ) {
         my $last_pair_info = -1;
         foreach my $line ( @{$rlines} ) {
-            if ( $line->get_is_hanging_side_comment() ) {
-                $line->set_imax_pair($last_pair_info);
+            if ( $line->{'is_hanging_side_comment'} ) {
+                $line->{'imax_pair'} = $last_pair_info;
             }
             else {
-                $last_pair_info = $line->get_imax_pair();
+                $last_pair_info = $line->{'imax_pair'};
             }
         }
     }
@@ -3457,7 +3395,7 @@ sub get_line_token_info {
     my $all_monotonic = 1;
     foreach my $jj ( 0 .. @{$rlines} - 1 ) {
         my ($line) = $rlines->[$jj];
-        my $rtokens = $line->get_rtokens();
+        my $rtokens = $line->{'rtokens'};
         my $last_lev;
         my $is_monotonic = 1;
         my $i            = -1;
@@ -3478,7 +3416,7 @@ sub get_line_token_info {
     foreach my $jj ( 0 .. @{$rlines} - 1 ) {
         my ($line) = $rlines->[$jj];
 
-        my $rtokens = $line->get_rtokens();
+        my $rtokens = $line->{'rtokens'};
         my $i       = -1;
         my ( $lev_min, $lev_max );
         my $token_pattern_max = EMPTY_STRING;
@@ -3651,9 +3589,9 @@ sub prune_alignment_tree {
 
     use constant EXPLAIN_PRUNE => 0;
 
-    ####################################################################
+    #-------------------------------------------------------------------
     # Prune Tree Step 1. Start by scanning the lines and collecting info
-    ####################################################################
+    #-------------------------------------------------------------------
 
     # Note that the caller had this info but we have to redo this now because
     # alignment tokens may have been deleted.
@@ -3699,9 +3637,9 @@ sub prune_alignment_tree {
     # the patterns and levels of the next line being tested at each depth
     my ( @token_patterns_next, @levels_next, @token_indexes_next );
 
-    #########################################################
+    #-----------------------------------------------------------
     # define a recursive worker subroutine for tree construction
-    #########################################################
+    #-----------------------------------------------------------
 
     # This is a recursive routine which is called if a match condition changes
     # at any depth when a new line is encountered.  It ends the match node
@@ -3760,9 +3698,9 @@ sub prune_alignment_tree {
         return;
     };    ## end sub end_node
 
-    ######################################################
+    #-----------------------------------------------------
     # Prune Tree Step 2. Loop to form the tree of matches.
-    ######################################################
+    #-----------------------------------------------------
     foreach my $jp ( 0 .. $jmax ) {
 
         # working with two adjacent line indexes, 'm'=minus, 'p'=plus
@@ -3799,13 +3737,13 @@ sub prune_alignment_tree {
         }
 
         # End groups if a hard flag has been set
-        elsif ( $rlines->[$jm]->get_end_group() ) {
+        elsif ( $rlines->[$jm]->{'end_group'} ) {
             my $n_parent;
             $end_node->( 0, $jm, $n_parent );
         }
 
         # Continue at hanging side comment
-        elsif ( $rlines->[$jp]->get_is_hanging_side_comment() ) {
+        elsif ( $rlines->[$jp]->{'is_hanging_side_comment'} ) {
             next;
         }
 
@@ -3832,9 +3770,9 @@ sub prune_alignment_tree {
         }
     } ## end loop to form tree of matches
 
-    ##########################################################
+    #---------------------------------------------------------
     # Prune Tree Step 3. Make links from parent to child nodes
-    ##########################################################
+    #---------------------------------------------------------
 
     # It seemed cleaner to do this as a separate step rather than during tree
     # construction.  The children nodes have links up to the parent node which
@@ -3869,9 +3807,9 @@ sub prune_alignment_tree {
         }
     };
 
-    #######################################################
+    #------------------------------------------------------
     # Prune Tree Step 4. Make a list of nodes to be deleted
-    #######################################################
+    #------------------------------------------------------
 
     #  list of lines with tokens to be deleted:
     #  [$jbeg, $jend, $level_keep]
@@ -3950,15 +3888,15 @@ sub prune_alignment_tree {
         @todo_list = @todo_next;
     } ## end loop to mark nodes to delete
 
-    #############################################################
+    #------------------------------------------------------------
     # Prune Tree Step 5. Loop to delete selected alignment tokens
-    #############################################################
+    #------------------------------------------------------------
     foreach my $item (@delete_list) {
         my ( $jbeg, $jend, $level_keep ) = @{$item};
         foreach my $jj ( $jbeg .. $jend ) {
             my $line = $rlines->[$jj];
             my @idel;
-            my $rtokens = $line->get_rtokens();
+            my $rtokens = $line->{'rtokens'};
             my $imax    = @{$rtokens} - 2;
             foreach my $i ( 0 .. $imax ) {
                 my $tok = $rtokens->[$i];
@@ -4050,26 +3988,34 @@ sub Dump_tree_groups {
 
         my $is_marginal = 0;
 
-        # always keep alignments of a terminal else or ternary
-        goto RETURN if ( defined( $line_1->get_j_terminal_match() ) );
+        #---------------------------------------
+        # Always align certain special cases ...
+        #---------------------------------------
+        if (
 
-        # always align lists
-        my $group_list_type = $line_0->get_list_type();
-        goto RETURN if ($group_list_type);
+            # always keep alignments of a terminal else or ternary
+            defined( $line_1->{'j_terminal_match'} )
 
-        # always align hanging side comments
-        my $is_hanging_side_comment = $line_1->get_is_hanging_side_comment();
-        goto RETURN if ($is_hanging_side_comment);
+            # always align lists
+            || $line_0->{'list_type'}
 
-        my $jmax_0           = $line_0->get_jmax();
-        my $jmax_1           = $line_1->get_jmax();
-        my $rtokens_1        = $line_1->get_rtokens();
-        my $rtokens_0        = $line_0->get_rtokens();
-        my $rfield_lengths_0 = $line_0->get_rfield_lengths();
-        my $rfield_lengths_1 = $line_1->get_rfield_lengths();
-        my $rpatterns_0      = $line_0->get_rpatterns();
-        my $rpatterns_1      = $line_1->get_rpatterns();
-        my $imax_next        = $line_1->get_imax_pair();
+            # always align hanging side comments
+            || $line_1->{'is_hanging_side_comment'}
+
+          )
+        {
+            return ( $is_marginal, $imax_align );
+        }
+
+        my $jmax_0           = $line_0->{'jmax'};
+        my $jmax_1           = $line_1->{'jmax'};
+        my $rtokens_1        = $line_1->{'rtokens'};
+        my $rtokens_0        = $line_0->{'rtokens'};
+        my $rfield_lengths_0 = $line_0->{'rfield_lengths'};
+        my $rfield_lengths_1 = $line_1->{'rfield_lengths'};
+        my $rpatterns_0      = $line_0->{'rpatterns'};
+        my $rpatterns_1      = $line_1->{'rpatterns'};
+        my $imax_next        = $line_1->{'imax_pair'};
 
         # We will scan the alignment tokens and set a flag '$is_marginal' if
         # it seems that the an alignment would look bad.
@@ -4100,8 +4046,8 @@ sub Dump_tree_groups {
 
             my $pad = $rfield_lengths_1->[$j] - $rfield_lengths_0->[$j];
             if ( $j == 0 ) {
-                $pad += $line_1->get_leading_space_count() -
-                  $line_0->get_leading_space_count();
+                $pad += $line_1->{'leading_space_count'} -
+                  $line_0->{'leading_space_count'};
 
                 # Remember the pad at a leading equals
                 if ( $raw_tok eq '=' && $lev == $group_level ) {
@@ -4197,10 +4143,12 @@ sub Dump_tree_groups {
           && $jmax_1 == 2
           && $sc_term0 ne $sc_term1;
 
-        ########################################
-        # return unless this is a marginal match
-        ########################################
-        goto RETURN if ( !$is_marginal );
+        #---------------------------------------
+        # return if this is not a marginal match
+        #---------------------------------------
+        if ( !$is_marginal ) {
+            return ( $is_marginal, $imax_align );
+        }
 
         # Undo the marginal match flag in certain cases,
 
@@ -4226,9 +4174,9 @@ sub Dump_tree_groups {
         my $pat0 = $rpatterns_0->[0];
         my $pat1 = $rpatterns_1->[0];
 
-        ##########################################################
+        #---------------------------------------------------------
         # Turn off the marginal flag for some types of assignments
-        ##########################################################
+        #---------------------------------------------------------
         if ( $is_assignment{$raw_tokb} ) {
 
             # undo marginal flag if first line is semicolon terminated
@@ -4251,9 +4199,9 @@ sub Dump_tree_groups {
             }
         }
 
-        ######################################################
+        #-----------------------------------------------------
         # Turn off the marginal flag if we saw an 'if' or 'or'
-        ######################################################
+        #-----------------------------------------------------
 
         # A trailing 'if' and 'or' often gives a good alignment
         # For example, we can align these:
@@ -4280,9 +4228,9 @@ sub Dump_tree_groups {
             $imax_align = $jfirst_bad - 1;
         }
 
-        ###########################################################
+        #----------------------------------------------------------
         # Allow sweep to match lines with leading '=' in some cases
-        ###########################################################
+        #----------------------------------------------------------
         if ( $imax_align < 0 && defined($j0_eq_pad) ) {
 
             if (
@@ -4331,10 +4279,9 @@ sub Dump_tree_groups {
             }
         }
 
-      RETURN:
         return ( $is_marginal, $imax_align );
     }
-}
+} ## end closure for sub is_marginal_match
 
 sub get_extra_leading_spaces {
 
@@ -4353,7 +4300,7 @@ sub get_extra_leading_spaces {
 
     return 0 unless ( @{$rlines} && @{$rgroups} );
 
-    my $object = $rlines->[0]->get_indentation();
+    my $object = $rlines->[0]->{'indentation'};
     return 0 unless ( ref($object) );
     my $extra_leading_spaces            = 0;
     my $extra_indentation_spaces_wanted = get_recoverable_spaces($object);
@@ -4372,7 +4319,7 @@ sub get_extra_leading_spaces {
             next if ( $j == 0 );
 
             # all indentation objects must be the same
-            if ( $object != $rlines->[$j]->get_indentation() ) {
+            if ( $object != $rlines->[$j]->{'indentation'} ) {
                 return 0;
             }
         }
@@ -4384,12 +4331,12 @@ sub get_extra_leading_spaces {
           ? $extra_indentation_spaces_wanted
           : $avail;
 
-        #########################################################
+        #--------------------------------------------------------
         # Note: min spaces can be negative; for example with -gnu
         # f(
         #   do { 1; !!(my $x = bless []); }
         #  );
-        #########################################################
+        #--------------------------------------------------------
         # The following rule is needed to match older formatting:
         # For multiple groups, we will keep spaces non-negative.
         # For a single group, we will allow a negative space.
@@ -4420,14 +4367,16 @@ sub is_good_side_comment_column {
     # a previous side comment should be forgotten.  This involves
     # checking several rules.
 
-    # Return true to keep old comment location
-    # Return false to forget old comment location
+    # Return true to KEEP old comment location
+    # Return false to FORGET old comment location
+    my $KEEP   = 1;
+    my $FORGET = 0;
 
-    my $rfields                 = $line->get_rfields();
-    my $is_hanging_side_comment = $line->get_is_hanging_side_comment();
+    my $rfields                 = $line->{'rfields'};
+    my $is_hanging_side_comment = $line->{'is_hanging_side_comment'};
 
     # RULE1: Never forget comment before a hanging side comment
-    goto KEEP if ($is_hanging_side_comment);
+    return $KEEP if ($is_hanging_side_comment);
 
     # RULE2: Forget a side comment after a short line difference,
     # where 'short line difference' is computed from a formula.
@@ -4456,14 +4405,14 @@ sub is_good_side_comment_column {
 
     my $short_diff = SC_LONG_LINE_DIFF / ( 1 + $alev_diff * $num5 );
 
-    goto FORGET
+    return $FORGET
       if ( $line_diff > $short_diff
         || !$self->[_rOpts_valign_side_comments_] );
 
     # RULE3: Forget a side comment if this line is at lower level and
     # ends a block
     my $last_sc_level = $self->[_last_side_comment_level_];
-    goto FORGET
+    return $FORGET
       if ( $level < $last_sc_level
         && $is_closing_block_type{ substr( $rfields->[0], 0, 1 ) } );
 
@@ -4482,18 +4431,12 @@ sub is_good_side_comment_column {
         #    [0, 3, 6], [1, 4, 7], [2, 5, 8],    # columns
         #    [0, 4, 8], [2, 4, 6]
         #  )                                     # diagonals
-        goto FORGET
+        return $FORGET
           if ( $cached_line_type == 2 || $cached_line_type == 4 );
     }
 
     # Otherwise, keep it alive
-    goto KEEP;
-
-  FORGET:
-    return 0;
-
-  KEEP:
-    return 1;
+    return $KEEP;
 }
 
 sub align_side_comments {
@@ -4531,8 +4474,8 @@ sub align_side_comments {
         my ( $jbeg, $jend ) = @{$item};
         foreach my $j ( $jbeg .. $jend ) {
             my $line = $rlines->[$j];
-            my $jmax = $line->get_jmax();
-            if ( $line->get_rfield_lengths()->[$jmax] ) {
+            my $jmax = $line->{'jmax'};
+            if ( $line->{'rfield_lengths'}->[$jmax] ) {
 
                 # this group has a line with a side comment
                 push @todo, $ng;
@@ -4554,8 +4497,8 @@ sub align_side_comments {
         my $ldiff = $jj - $j_sc_beg;
         last if ( $ldiff > 5 );
         my $line   = $rlines->[$jj];
-        my $jmax   = $line->get_jmax();
-        my $sc_len = $line->get_rfield_lengths()->[$jmax];
+        my $jmax   = $line->{'jmax'};
+        my $sc_len = $line->{'rfield_lengths'}->[$jmax];
         next unless ($sc_len);
         $num5++;
     }
@@ -4592,8 +4535,8 @@ sub align_side_comments {
             # Note that since all lines in a group have common alignments, we
             # just have to work on one of the lines (the first line).
             my $line                    = $rlines->[$jbeg];
-            my $jmax                    = $line->get_jmax();
-            my $is_hanging_side_comment = $line->get_is_hanging_side_comment();
+            my $jmax                    = $line->{'jmax'};
+            my $is_hanging_side_comment = $line->{'is_hanging_side_comment'};
             last
               if ( $PASS < $MAX_PASS && $is_hanging_side_comment );
 
@@ -4665,8 +4608,8 @@ sub align_side_comments {
     my ( $jbeg, $jend ) = @{ $rgroups->[$ng_last] };
     foreach my $jj ( reverse( $jbeg .. $jend ) ) {
         my $line = $rlines->[$jj];
-        my $jmax = $line->get_jmax();
-        if ( $line->get_rfield_lengths()->[$jmax] ) {
+        my $jmax = $line->{'jmax'};
+        if ( $line->{'rfield_lengths'}->[$jmax] ) {
             $j_sc_last = $jj;
             last;
         }
@@ -4689,11 +4632,11 @@ sub align_side_comments {
 
 sub valign_output_step_A {
 
-    ###############################################################
+    #------------------------------------------------------------
     # This is Step A in writing vertically aligned lines.
     # The line is prepared according to the alignments which have
     # been found. Then it is shipped to the next step.
-    ###############################################################
+    #------------------------------------------------------------
 
     my ( $self, $rinput_hash ) = @_;
 
@@ -4705,14 +4648,19 @@ sub valign_output_step_A {
     my $level                = $rinput_hash->{level};
     my $maximum_line_length  = $rinput_hash->{maximum_line_length};
 
-    my $rfields                   = $line->get_rfields();
-    my $rfield_lengths            = $line->get_rfield_lengths();
-    my $leading_space_count       = $line->get_leading_space_count();
-    my $outdent_long_lines        = $line->get_outdent_long_lines();
-    my $maximum_field_index       = $line->get_jmax();
-    my $rvertical_tightness_flags = $line->get_rvertical_tightness_flags();
-    my $Kend                      = $line->get_Kend();
-    my $level_end                 = $line->get_level_end();
+    my $rfields                   = $line->{'rfields'};
+    my $rfield_lengths            = $line->{'rfield_lengths'};
+    my $leading_space_count       = $line->{'leading_space_count'};
+    my $outdent_long_lines        = $line->{'outdent_long_lines'};
+    my $maximum_field_index       = $line->{'jmax'};
+    my $rvertical_tightness_flags = $line->{'rvertical_tightness_flags'};
+    my $Kend                      = $line->{'Kend'};
+    my $level_end                 = $line->{'level_end'};
+
+    # Check for valid hash keys at end of lifetime of $line during development
+    DEVEL_MODE
+      && check_keys( $line, \%valid_LINE_keys,
+        "Checking line keys at valign_output_step_A", 1 );
 
     # add any extra spaces
     if ( $leading_space_count > $group_leader_length ) {
@@ -4722,6 +4670,19 @@ sub valign_output_step_A {
     my $str     = $rfields->[0];
     my $str_len = $rfield_lengths->[0];
 
+    my @alignments = @{ $line->{'ralignments'} };
+    if ( @alignments != $maximum_field_index + 1 ) {
+
+        # Shouldn't happen: sub install_new_alignments makes jmax alignments
+        my $jmax_alignments = @alignments - 1;
+        if (DEVEL_MODE) {
+            Fault(
+"alignment jmax=$jmax_alignments should equal $maximum_field_index\n"
+            );
+        }
+        $do_not_align = 1;
+    }
+
     # loop to concatenate all fields of this line and needed padding
     my $total_pad_count = 0;
     for my $j ( 1 .. $maximum_field_index ) {
@@ -4735,7 +4696,7 @@ sub valign_output_step_A {
           );
 
         # compute spaces of padding before this field
-        my $col = $line->get_column( $j - 1 );
+        my $col = $alignments[ $j - 1 ]->{'column'};
         my $pad = $col - ( $str_len + $leading_space_count );
 
         if ($do_not_align) {
@@ -4805,12 +4766,13 @@ sub combine_fields {
     if ( !defined($imax_align) ) { $imax_align = -1 }
 
     # First delete the unwanted tokens
-    my $jmax_old       = $line_0->get_jmax();
-    my @old_alignments = $line_0->get_alignments();
-    my @idel           = ( $imax_align + 1 .. $jmax_old - 2 );
-
+    my $jmax_old = $line_0->{'jmax'};
+    my @idel     = ( $imax_align + 1 .. $jmax_old - 2 );
     return unless (@idel);
 
+    # Get old alignments before any changes are made
+    my @old_alignments = @{ $line_0->{'ralignments'} };
+
     foreach my $line ( $line_0, $line_1 ) {
         delete_selected_tokens( $line, \@idel );
     }
@@ -4823,12 +4785,12 @@ sub combine_fields {
           @old_alignments[ 0 .. $imax_align ];
     }
 
-    my $jmax_new = $line_0->get_jmax();
+    my $jmax_new = $line_0->{'jmax'};
 
     $new_alignments[ $jmax_new - 1 ] = $old_alignments[ $jmax_old - 1 ];
-    $new_alignments[$jmax_new] = $old_alignments[$jmax_old];
-    $line_0->set_alignments(@new_alignments);
-    $line_1->set_alignments(@new_alignments);
+    $new_alignments[$jmax_new]       = $old_alignments[$jmax_old];
+    $line_0->{'ralignments'}         = \@new_alignments;
+    $line_1->{'ralignments'}         = \@new_alignments;
     return;
 }
 
@@ -4933,14 +4895,262 @@ sub get_output_line_number {
         return;
     }
 
+    sub handle_cached_line {
+
+        my ( $self, $rinput, $leading_string, $leading_string_length ) = @_;
+
+        # The cached line will either be:
+        # - written out, or
+        # - or combined with the current line
+
+        my $last_level_written = $self->[_last_level_written_];
+
+        my $leading_space_count       = $rinput->{leading_space_count};
+        my $str                       = $rinput->{line};
+        my $str_length                = $rinput->{line_length};
+        my $rvertical_tightness_flags = $rinput->{rvertical_tightness_flags};
+        my $level                     = $rinput->{level};
+        my $level_end                 = $rinput->{level_end};
+        my $maximum_line_length       = $rinput->{maximum_line_length};
+
+        my ( $open_or_close, $opening_flag, $closing_flag, $seqno, $valid,
+            $seqno_beg, $seqno_end );
+        if ($rvertical_tightness_flags) {
+
+            $open_or_close = $rvertical_tightness_flags->{_vt_type};
+            $seqno_beg     = $rvertical_tightness_flags->{_vt_seqno_beg};
+        }
+
+        # Dump an invalid cached line
+        if ( !$cached_line_valid ) {
+            $self->valign_output_step_C(
+                $seqno_string,
+                $last_nonblank_seqno_string,
+
+                $cached_line_text,
+                $cached_line_leading_space_count,
+                $last_level_written,
+                $cached_line_Kend,
+            );
+        }
+
+        # Handle cached line ending in OPENING tokens
+        elsif ( $cached_line_type == 1 || $cached_line_type == 3 ) {
+
+            my $gap = $leading_space_count - $cached_line_text_length;
+
+            # handle option of just one tight opening per line:
+            if ( $cached_line_opening_flag == 1 ) {
+                if ( defined($open_or_close) && $open_or_close == 1 ) {
+                    $gap = -1;
+                }
+            }
+
+            # Do not join the lines if this might produce a one-line
+            # container which exceeds the maximum line length.  This is
+            # necessary prevent blinking, particularly with the combination
+            # -xci -pvt=2.  In that case a one-line block alternately forms
+            # and breaks, causing -xci to alternately turn on and off (case
+            # b765).
+            # Patched to fix cases b656 b862 b971 b972: always do the check
+            # if the maximum line length changes (due to -vmll).
+            if (
+                $gap >= 0
+                && ( $maximum_line_length != $cached_line_maximum_length
+                    || ( defined($level_end) && $level > $level_end ) )
+              )
+            {
+                my $test_line_length =
+                  $cached_line_text_length + $gap + $str_length;
+
+                # Add a small tolerance in the length test (fixes case b862)
+                if ( $test_line_length > $cached_line_maximum_length - 2 ) {
+                    $gap = -1;
+                }
+            }
+
+            if ( $gap >= 0 && defined($seqno_beg) ) {
+                $maximum_line_length   = $cached_line_maximum_length;
+                $leading_string        = $cached_line_text . SPACE x $gap;
+                $leading_string_length = $cached_line_text_length + $gap;
+                $leading_space_count   = $cached_line_leading_space_count;
+                $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
+                $level        = $last_level_written;
+            }
+            else {
+                $self->valign_output_step_C(
+                    $seqno_string,
+                    $last_nonblank_seqno_string,
+
+                    $cached_line_text,
+                    $cached_line_leading_space_count,
+                    $last_level_written,
+                    $cached_line_Kend,
+                );
+            }
+        }
+
+        # Handle cached line ending in CLOSING tokens
+        else {
+            my $test_line =
+              $cached_line_text . SPACE x $cached_line_closing_flag . $str;
+            my $test_line_length =
+              $cached_line_text_length +
+              $cached_line_closing_flag +
+              $str_length;
+            if (
+
+                # The new line must start with container
+                $seqno_beg
+
+                # The container combination must be okay..
+                && (
+
+                    # okay to combine like types
+                    ( $open_or_close == $cached_line_type )
+
+                    # closing block brace may append to non-block
+                    || ( $cached_line_type == 2 && $open_or_close == 4 )
+
+                    # something like ');'
+                    || ( !$open_or_close && $cached_line_type == 2 )
+
+                )
+
+                # The combined line must fit
+                && ( $test_line_length <= $cached_line_maximum_length )
+              )
+            {
+
+                $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
+
+                # Patch to outdent closing tokens ending # in ');' If we
+                # are joining a line like ');' to a previous stacked set of
+                # closing tokens, then decide if we may outdent the
+                # combined stack to the indentation of the ');'.  Since we
+                # should not normally outdent any of the other tokens more
+                # than the indentation of the lines that contained them, we
+                # will only do this if all of the corresponding opening
+                # tokens were on the same line.  This can happen with -sot
+                # and -sct.
+
+                # For example, it is ok here:
+                #   __PACKAGE__->load_components( qw(
+                #         PK::Auto
+                #         Core
+                #   ));
+                #
+                # But, for example, we do not outdent in this example
+                # because that would put the closing sub brace out farther
+                # than the opening sub brace:
+                #
+                #   perltidy -sot -sct
+                #   $c->Tk::bind(
+                #       '<Control-f>' => sub {
+                #           my ($c) = @_;
+                #           my $e = $c->XEvent;
+                #           itemsUnderArea $c;
+                #       } );
+                #
+                if (   $str =~ /^\);/
+                    && $cached_line_text =~ /^[\)\}\]\s]*$/ )
+                {
+
+                    # The way to tell this is if the stacked sequence
+                    # numbers of this output line are the reverse of the
+                    # stacked sequence numbers of the previous non-blank
+                    # line of sequence numbers.  So we can join if the
+                    # previous nonblank string of tokens is the mirror
+                    # image.  For example if stack )}] is 13:8:6 then we
+                    # are looking for a leading stack like [{( which
+                    # is 6:8:13. We only need to check the two ends,
+                    # because the intermediate tokens must fall in order.
+                    # Note on speed: having to split on colons and
+                    # eliminate multiple colons might appear to be slow,
+                    # but it's not an issue because we almost never come
+                    # through here.  In a typical file we don't.
+
+                    $seqno_string               =~ s/^:+//;
+                    $last_nonblank_seqno_string =~ s/^:+//;
+                    $seqno_string               =~ s/:+/:/g;
+                    $last_nonblank_seqno_string =~ s/:+/:/g;
+
+                    # how many spaces can we outdent?
+                    my $diff =
+                      $cached_line_leading_space_count - $leading_space_count;
+                    if (   $diff > 0
+                        && length($seqno_string)
+                        && length($last_nonblank_seqno_string) ==
+                        length($seqno_string) )
+                    {
+                        my @seqno_last =
+                          ( split /:/, $last_nonblank_seqno_string );
+                        my @seqno_now = ( split /:/, $seqno_string );
+                        if (   @seqno_now
+                            && @seqno_last
+                            && $seqno_now[-1] == $seqno_last[0]
+                            && $seqno_now[0] == $seqno_last[-1] )
+                        {
+
+                            # OK to outdent ..
+                            # for absolute safety, be sure we only remove
+                            # whitespace
+                            my $ws = substr( $test_line, 0, $diff );
+                            if ( ( length($ws) == $diff )
+                                && $ws =~ /^\s+$/ )
+                            {
+
+                                $test_line = substr( $test_line, $diff );
+                                $cached_line_leading_space_count -= $diff;
+                                $last_level_written =
+                                  $self->level_change(
+                                    $cached_line_leading_space_count,
+                                    $diff, $last_level_written );
+                                $self->reduce_valign_buffer_indentation($diff);
+                            }
+
+                            # shouldn't happen, but not critical:
+                            ##else {
+                            ## ERROR transferring indentation here
+                            ##}
+                        }
+                    }
+                }
+
+                # Change the args to look like we received the combined line
+                $str                   = $test_line;
+                $str_length            = $test_line_length;
+                $leading_string        = EMPTY_STRING;
+                $leading_string_length = 0;
+                $leading_space_count   = $cached_line_leading_space_count;
+                $level                 = $last_level_written;
+                $maximum_line_length   = $cached_line_maximum_length;
+            }
+            else {
+                $self->valign_output_step_C(
+                    $seqno_string,
+                    $last_nonblank_seqno_string,
+
+                    $cached_line_text,
+                    $cached_line_leading_space_count,
+                    $last_level_written,
+                    $cached_line_Kend,
+                );
+            }
+        }
+        return ( $str, $str_length, $leading_string, $leading_string_length,
+            $leading_space_count, $level, $maximum_line_length, );
+
+    } ## end sub handle_cached_line
+
     sub valign_output_step_B {
 
-        ###############################################################
+        #---------------------------------------------------------
         # This is Step B in writing vertically aligned lines.
         # Vertical tightness is applied according to preset flags.
         # In particular this routine handles stacking of opening
         # and closing tokens.
-        ###############################################################
+        #---------------------------------------------------------
 
         my ( $self, $rinput ) = @_;
 
@@ -4955,8 +5165,6 @@ sub get_output_line_number {
         my $Kend                      = $rinput->{Kend};
         my $maximum_line_length       = $rinput->{maximum_line_length};
 
-        my $last_level_written = $self->[_last_level_written_];
-
         # Useful -gcs test cases for wide characters are
         # perl527/(method.t.2, reg_mesg.t, mime-header.t)
 
@@ -5040,231 +5248,25 @@ sub get_output_line_number {
         # would be a disaster.
         if ( length($cached_line_text) ) {
 
-            # Dump an invalid cached line
-            if ( !$cached_line_valid ) {
-                $self->valign_output_step_C(
-                    $seqno_string,
-                    $last_nonblank_seqno_string,
-
-                    $cached_line_text,
-                    $cached_line_leading_space_count,
-                    $last_level_written,
-                    $cached_line_Kend
-                );
-            }
-
-            # Handle cached line ending in OPENING tokens
-            elsif ( $cached_line_type == 1 || $cached_line_type == 3 ) {
-
-                my $gap = $leading_space_count - $cached_line_text_length;
-
-                # handle option of just one tight opening per line:
-                if ( $cached_line_opening_flag == 1 ) {
-                    if ( defined($open_or_close) && $open_or_close == 1 ) {
-                        $gap = -1;
-                    }
-                }
-
-                # Do not join the lines if this might produce a one-line
-                # container which exceeds the maximum line length.  This is
-                # necessary prevent blinking, particularly with the combination
-                # -xci -pvt=2.  In that case a one-line block alternately forms
-                # and breaks, causing -xci to alternately turn on and off (case
-                # b765).
-                # Patched to fix cases b656 b862 b971 b972: always do the check
-                # if the maximum line length changes (due to -vmll).
-                if (
-                    $gap >= 0
-                    && ( $maximum_line_length != $cached_line_maximum_length
-                        || ( defined($level_end) && $level > $level_end ) )
-                  )
-                {
-                    my $test_line_length =
-                      $cached_line_text_length + $gap + $str_length;
-
-                    # Add a small tolerance in the length test (fixes case b862)
-                    if ( $test_line_length > $cached_line_maximum_length - 2 ) {
-                        $gap = -1;
-                    }
-                }
-
-                if ( $gap >= 0 && defined($seqno_beg) ) {
-                    $maximum_line_length   = $cached_line_maximum_length;
-                    $leading_string        = $cached_line_text . SPACE x $gap;
-                    $leading_string_length = $cached_line_text_length + $gap;
-                    $leading_space_count   = $cached_line_leading_space_count;
-                    $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
-                    $level        = $last_level_written;
-                }
-                else {
-                    $self->valign_output_step_C(
-                        $seqno_string,
-                        $last_nonblank_seqno_string,
-
-                        $cached_line_text,
-                        $cached_line_leading_space_count,
-                        $last_level_written,
-                        $cached_line_Kend
-                    );
-                }
-            }
-
-            # Handle cached line ending in CLOSING tokens
-            else {
-                my $test_line =
-                  $cached_line_text . SPACE x $cached_line_closing_flag . $str;
-                my $test_line_length =
-                  $cached_line_text_length +
-                  $cached_line_closing_flag +
-                  $str_length;
-                if (
-
-                    # The new line must start with container
-                    $seqno_beg
-
-                    # The container combination must be okay..
-                    && (
-
-                        # okay to combine like types
-                        ( $open_or_close == $cached_line_type )
-
-                        # closing block brace may append to non-block
-                        || ( $cached_line_type == 2 && $open_or_close == 4 )
-
-                        # something like ');'
-                        || ( !$open_or_close && $cached_line_type == 2 )
-
-                    )
-
-                    # The combined line must fit
-                    && ( $test_line_length <= $cached_line_maximum_length )
-                  )
-                {
-
-                    $seqno_string = $cached_seqno_string . ':' . $seqno_beg;
-
-                    # Patch to outdent closing tokens ending # in ');' If we
-                    # are joining a line like ');' to a previous stacked set of
-                    # closing tokens, then decide if we may outdent the
-                    # combined stack to the indentation of the ');'.  Since we
-                    # should not normally outdent any of the other tokens more
-                    # than the indentation of the lines that contained them, we
-                    # will only do this if all of the corresponding opening
-                    # tokens were on the same line.  This can happen with -sot
-                    # and -sct.
-
-                    # For example, it is ok here:
-                    #   __PACKAGE__->load_components( qw(
-                    #         PK::Auto
-                    #         Core
-                    #   ));
-                    #
-                    # But, for example, we do not outdent in this example
-                    # because that would put the closing sub brace out farther
-                    # than the opening sub brace:
-                    #
-                    #   perltidy -sot -sct
-                    #   $c->Tk::bind(
-                    #       '<Control-f>' => sub {
-                    #           my ($c) = @_;
-                    #           my $e = $c->XEvent;
-                    #           itemsUnderArea $c;
-                    #       } );
-                    #
-                    if (   $str =~ /^\);/
-                        && $cached_line_text =~ /^[\)\}\]\s]*$/ )
-                    {
+            (
+                $str,
+                $str_length,
+                $leading_string,
+                $leading_string_length,
+                $leading_space_count,
+                $level,
+                $maximum_line_length
 
-                        # The way to tell this is if the stacked sequence
-                        # numbers of this output line are the reverse of the
-                        # stacked sequence numbers of the previous non-blank
-                        # line of sequence numbers.  So we can join if the
-                        # previous nonblank string of tokens is the mirror
-                        # image.  For example if stack )}] is 13:8:6 then we
-                        # are looking for a leading stack like [{( which
-                        # is 6:8:13. We only need to check the two ends,
-                        # because the intermediate tokens must fall in order.
-                        # Note on speed: having to split on colons and
-                        # eliminate multiple colons might appear to be slow,
-                        # but it's not an issue because we almost never come
-                        # through here.  In a typical file we don't.
-
-                        $seqno_string               =~ s/^:+//;
-                        $last_nonblank_seqno_string =~ s/^:+//;
-                        $seqno_string               =~ s/:+/:/g;
-                        $last_nonblank_seqno_string =~ s/:+/:/g;
-
-                        # how many spaces can we outdent?
-                        my $diff =
-                          $cached_line_leading_space_count -
-                          $leading_space_count;
-                        if (   $diff > 0
-                            && length($seqno_string)
-                            && length($last_nonblank_seqno_string) ==
-                            length($seqno_string) )
-                        {
-                            my @seqno_last =
-                              ( split /:/, $last_nonblank_seqno_string );
-                            my @seqno_now = ( split /:/, $seqno_string );
-                            if (   @seqno_now
-                                && @seqno_last
-                                && $seqno_now[-1] == $seqno_last[0]
-                                && $seqno_now[0] == $seqno_last[-1] )
-                            {
+            ) = $self->handle_cached_line( $rinput, $leading_string,
+                $leading_string_length );
 
-                                # OK to outdent ..
-                                # for absolute safety, be sure we only remove
-                                # whitespace
-                                my $ws = substr( $test_line, 0, $diff );
-                                if ( ( length($ws) == $diff )
-                                    && $ws =~ /^\s+$/ )
-                                {
-
-                                    $test_line = substr( $test_line, $diff );
-                                    $cached_line_leading_space_count -= $diff;
-                                    $last_level_written =
-                                      $self->level_change(
-                                        $cached_line_leading_space_count,
-                                        $diff, $last_level_written );
-                                    $self->reduce_valign_buffer_indentation(
-                                        $diff);
-                                }
-
-                                # shouldn't happen, but not critical:
-                                ##else {
-                                ## ERROR transferring indentation here
-                                ##}
-                            }
-                        }
-                    }
+            $cached_line_type           = 0;
+            $cached_line_text           = EMPTY_STRING;
+            $cached_line_text_length    = 0;
+            $cached_line_Kend           = undef;
+            $cached_line_maximum_length = undef;
 
-                    # Change the args to look like we received the combined line
-                    $str                   = $test_line;
-                    $str_length            = $test_line_length;
-                    $leading_string        = EMPTY_STRING;
-                    $leading_string_length = 0;
-                    $leading_space_count   = $cached_line_leading_space_count;
-                    $level                 = $last_level_written;
-                    $maximum_line_length   = $cached_line_maximum_length;
-                }
-                else {
-                    $self->valign_output_step_C(
-                        $seqno_string,
-                        $last_nonblank_seqno_string,
-
-                        $cached_line_text,
-                        $cached_line_leading_space_count,
-                        $last_level_written,
-                        $cached_line_Kend
-                    );
-                }
-            }
         }
-        $cached_line_type           = 0;
-        $cached_line_text           = EMPTY_STRING;
-        $cached_line_text_length    = 0;
-        $cached_line_Kend           = undef;
-        $cached_line_maximum_length = undef;
 
         # make the line to be written
         my $line        = $leading_string . $str;
@@ -5284,7 +5286,11 @@ sub get_output_line_number {
 
         # write or cache this line ...
         # fix for case b999: do not cache an outdented line
-        if ( !$open_or_close || $side_comment_length > 0 || $is_outdented_line )
+        # fix for b1378: do not cache an empty line
+        if (  !$open_or_close
+            || $side_comment_length > 0
+            || $is_outdented_line
+            || !$line_length )
         {
             $self->valign_output_step_C(
                 $seqno_string,
@@ -5293,7 +5299,7 @@ sub get_output_line_number {
                 $line,
                 $leading_space_count,
                 $level,
-                $Kend
+                $Kend,
             );
         }
         else {
@@ -5371,18 +5377,18 @@ sub get_output_line_number {
 
     sub valign_output_step_C {
 
-        ###############################################################
+        #-----------------------------------------------------------------------
         # This is Step C in writing vertically aligned lines.
         # Lines are either stored in a buffer or passed along to the next step.
         # The reason for storing lines is that we may later want to reduce their
         # indentation when -sot and -sct are both used.
-        ###############################################################
+        #-----------------------------------------------------------------------
         my (
             $self,
             $seqno_string,
             $last_nonblank_seqno_string,
 
-            @args_to_D
+            @args_to_D,
         ) = @_;
 
         # Dump any saved lines if we see a line with an unbalanced opening or
@@ -5406,7 +5412,8 @@ sub get_output_line_number {
             # Start storing lines when we see a line with multiple stacked
             # opening tokens.
             # patch for RT #94354, requested by Colin Williams
-            if (   $seqno_string =~ /^\d+(\:+\d+)+$/
+            if (   index( $seqno_string, ':' ) >= 0
+                && $seqno_string =~ /^\d+(\:+\d+)+$/
                 && $args_to_D[0] !~ /^[\}\)\]\:\?]/ )
             {
 
@@ -5450,11 +5457,11 @@ sub get_output_line_number {
 
 sub valign_output_step_D {
 
-    ###############################################################
+    #----------------------------------------------------------------
     # This is Step D in writing vertically aligned lines.
     # It is the end of the vertical alignment pipeline.
     # Write one vertically aligned line of code to the output object.
-    ###############################################################
+    #----------------------------------------------------------------
 
     my ( $self, $line, $leading_space_count, $level, $Kend ) = @_;
 
index 078689bb312166ba32caa1a6fd83c64831024ec9..209b2e50b8b7ed61fb38d048072d9c56b8e2704a 100644 (file)
@@ -10,26 +10,11 @@ use warnings;
 
 { #<<< A non-indenting brace
 
-our $VERSION = '20220613';
-
-BEGIN {
-
-    # Indexes for variables in $self.
-    # Do not combine with other BEGIN blocks (c101).
-    #    _column_          # the current column number
-    #    _saved_column_    # a place for temporary storage
-    my $i = 0;
-    use constant {
-        _column_       => $i++,
-        _saved_column_ => $i++,
-    };
-}
+our $VERSION = '20221112';
 
 sub new {
     my ( $class, $rarg ) = @_;
-    my $self = bless [], $class;
-    $self->[_column_]       = $rarg->{column};
-    $self->[_saved_column_] = $rarg->{saved_column};
+    my $self = bless $rarg, $class;
     return $self;
 }
 
@@ -60,23 +45,23 @@ sub DESTROY {
 }
 
 sub get_column {
-    return $_[0]->[_column_];
+    return $_[0]->{'column'};
 }
 
 sub increment_column {
-    $_[0]->[_column_] += $_[1];
+    $_[0]->{'column'} += $_[1];
+
     return;
 }
 
 sub save_column {
-    $_[0]->[_saved_column_] = $_[0]->[_column_];
+    $_[0]->{'saved_column'} = $_[0]->{'column'};
     return;
 }
 
 sub restore_column {
-    $_[0]->[_column_] = $_[0]->[_saved_column_];
+    $_[0]->{'column'} = $_[0]->{'saved_column'};
     return;
 }
 } ## end of package VerticalAligner::Alignment
 1;
-
index ab679de33a15ff3cd66e9fe05fd127529d118918..75fa3ac377221d91fd0625e4823016af78768051 100644 (file)
@@ -1,45 +1,16 @@
 #####################################################################
 #
-# the Perl::Tidy::VerticalAligner::Line class supplies an object to
-# contain a single output line
+# The Perl::Tidy::VerticalAligner::Line class supplies an object to
+# contain a single output line.  It allows manipulation of the
+# alignment columns on that line.
 #
 #####################################################################
 
 package Perl::Tidy::VerticalAligner::Line;
 use strict;
 use warnings;
-our $VERSION = '20220613';
-
-BEGIN {
-
-    # Indexes for variables in $self.
-    # Do not combine with other BEGIN blocks (c101).
-    my $i = 0;
-    use constant {
-        _jmax_                      => $i++,
-        _rtokens_                   => $i++,
-        _rfields_                   => $i++,
-        _rfield_lengths_            => $i++,
-        _rpatterns_                 => $i++,
-        _indentation_               => $i++,
-        _leading_space_count_       => $i++,
-        _outdent_long_lines_        => $i++,
-        _list_seqno_                => $i++,
-        _list_type_                 => $i++,
-        _is_hanging_side_comment_   => $i++,
-        _ralignments_               => $i++,
-        _maximum_line_length_       => $i++,
-        _rvertical_tightness_flags_ => $i++,
-        _is_terminal_ternary_       => $i++,
-        _j_terminal_match_          => $i++,
-        _end_group_                 => $i++,
-        _Kend_                      => $i++,
-        _ci_level_                  => $i++,
-        _level_                     => $i++,
-        _level_end_                 => $i++,
-        _imax_pair_                 => $i++,
-    };
-}
+use English qw( -no_match_vars );
+our $VERSION = '20221112';
 
 sub AUTOLOAD {
 
@@ -64,125 +35,30 @@ EOM
 
 {
 
-    ##use Carp;
-
     # Constructor may be called as a class method
     sub new {
         my ( $class, $ri ) = @_;
-        my $self = bless [], $class;
-
-        $self->[_jmax_]                      = $ri->{jmax};
-        $self->[_rtokens_]                   = $ri->{rtokens};
-        $self->[_rfields_]                   = $ri->{rfields};
-        $self->[_rfield_lengths_]            = $ri->{rfield_lengths};
-        $self->[_rpatterns_]                 = $ri->{rpatterns};
-        $self->[_indentation_]               = $ri->{indentation};
-        $self->[_leading_space_count_]       = $ri->{leading_space_count};
-        $self->[_outdent_long_lines_]        = $ri->{outdent_long_lines};
-        $self->[_list_type_]                 = $ri->{list_type};
-        $self->[_list_seqno_]                = $ri->{list_seqno};
-        $self->[_is_hanging_side_comment_]   = $ri->{is_hanging_side_comment};
-        $self->[_maximum_line_length_]       = $ri->{maximum_line_length};
-        $self->[_rvertical_tightness_flags_] = $ri->{rvertical_tightness_flags};
-        $self->[_is_terminal_ternary_]       = $ri->{is_terminal_ternary};
-        $self->[_j_terminal_match_]          = $ri->{j_terminal_match};
-        $self->[_end_group_]                 = $ri->{end_group};
-        $self->[_Kend_]                      = $ri->{Kend};
-        $self->[_ci_level_]                  = $ri->{ci_level};
-        $self->[_level_]                     = $ri->{level};
-        $self->[_level_end_]                 = $ri->{level_end};
-        $self->[_imax_pair_]                 = $ri->{imax_pair};
-
-        $self->[_ralignments_] = [];
-
+        my $self = bless $ri, $class;
         return $self;
     }
 
-    sub get_jmax { return $_[0]->[_jmax_] }
-
-    sub get_rtokens        { return $_[0]->[_rtokens_] }
-    sub get_rfields        { return $_[0]->[_rfields_] }
-    sub get_rfield_lengths { return $_[0]->[_rfield_lengths_] }
-    sub get_rpatterns      { return $_[0]->[_rpatterns_] }
-    sub get_indentation    { return $_[0]->[_indentation_] }
-    sub get_Kend           { return $_[0]->[_Kend_] }
-    sub get_ci_level       { return $_[0]->[_ci_level_] }
-    sub get_level          { return $_[0]->[_level_] }
-    sub get_level_end      { return $_[0]->[_level_end_] }
-    sub get_list_seqno     { return $_[0]->[_list_seqno_] }
-
-    sub get_imax_pair { return $_[0]->[_imax_pair_] }
-
-    sub set_imax_pair {
-        my ( $self, $val ) = @_;
-        $self->[_imax_pair_] = $val;
-        return;
-    }
-
-    sub get_j_terminal_match {
-        return $_[0]->[_j_terminal_match_];
-    }
-
-    sub set_j_terminal_match {
-        my ( $self, $val ) = @_;
-        $self->[_j_terminal_match_] = $val;
-        return;
-    }
-
-    sub get_is_terminal_ternary {
-        return $_[0]->[_is_terminal_ternary_];
-    }
-
-    sub get_leading_space_count {
-        return $_[0]->[_leading_space_count_];
-    }
-
-    sub get_outdent_long_lines {
-        return $_[0]->[_outdent_long_lines_];
-    }
-    sub get_list_type { return $_[0]->[_list_type_] }
-
-    sub get_is_hanging_side_comment {
-        return $_[0]->[_is_hanging_side_comment_];
-    }
-
-    sub get_maximum_line_length {
-        return $_[0]->[_maximum_line_length_];
-    }
-
-    sub get_rvertical_tightness_flags {
-        return $_[0]->[_rvertical_tightness_flags_];
-    }
-
-    sub get_alignment {
-        my ( $self, $j ) = @_;
-        return $self->[_ralignments_]->[$j];
-    }
-    sub get_alignments { return @{ $_[0]->[_ralignments_] } }
-
     sub get_column {
-        ##my ( $self, $j ) = @_;
-        my $alignment = $_[0]->[_ralignments_]->[ $_[1] ];
+        my ( $self, $j ) = @_;
+        my $alignment = $self->{ralignments}->[$j];
         return unless defined($alignment);
         return $alignment->get_column();
     }
 
-    sub set_alignments {
-        my ( $self, @args ) = @_;
-        @{ $self->[_ralignments_] } = @args;
-        return;
-    }
-
     sub current_field_width {
         my ( $self, $j ) = @_;
         my $col_j  = 0;
         my $col_jm = 0;
 
-        my $alignment_j = $self->[_ralignments_]->[$j];
+        my $alignment_j = $self->{ralignments}->[$j];
         $col_j = $alignment_j->get_column() if defined($alignment_j);
 
         if ( $j > 0 ) {
-            my $alignment_jm = $self->[_ralignments_]->[ $j - 1 ];
+            my $alignment_jm = $self->{ralignments}->[ $j - 1 ];
             $col_jm = $alignment_jm->get_column() if defined($alignment_jm);
         }
         return $col_j - $col_jm;
@@ -191,9 +67,9 @@ EOM
     sub increase_field_width {
 
         my ( $self, $j, $pad ) = @_;
-        my $jmax = $self->[_jmax_];
+        my $jmax = $self->{jmax};
         foreach ( $j .. $jmax ) {
-            my $alignment = $self->[_ralignments_]->[$_];
+            my $alignment = $self->{ralignments}->[$_];
             if ( defined($alignment) ) {
                 $alignment->increment_column($pad);
             }
@@ -202,60 +78,8 @@ EOM
     }
 
     sub get_available_space_on_right {
-        my $jmax = $_[0]->[_jmax_];
-        return $_[0]->[_maximum_line_length_] - $_[0]->get_column($jmax);
-    }
-
-    sub set_jmax { my ( $self, $val ) = @_; $self->[_jmax_] = $val; return }
-
-    sub set_rtokens {
-        my ( $self, $val ) = @_;
-        $self->[_rtokens_] = $val;
-        return;
-    }
-
-    sub set_rfields {
-        my ( $self, $val ) = @_;
-        $self->[_rfields_] = $val;
-        return;
-    }
-
-    sub set_rfield_lengths {
-        my ( $self, $val ) = @_;
-        $self->[_rfield_lengths_] = $val;
-        return;
-    }
-
-    sub set_rpatterns {
-        my ( $self, $val ) = @_;
-        $self->[_rpatterns_] = $val;
-        return;
-    }
-
-    sub set_list_type {
-        my ( $self, $val ) = @_;
-        $self->[_list_type_] = $val;
-        return;
-    }
-
-    sub set_is_hanging_side_comment {
-        my ( $self, $val ) = @_;
-        $self->[_is_hanging_side_comment_] = $val;
-        return;
-    }
-
-    sub set_alignment {
-        my ( $self, $j, $val ) = @_;
-        $self->[_ralignments_]->[$j] = $val;
-        return;
-    }
-
-    sub get_end_group { return $_[0]->[_end_group_] }
-
-    sub set_end_group {
-        my ( $self, $val ) = @_;
-        $self->[_end_group_] = $val;
-        return;
+        my $jmax = $_[0]->{jmax};
+        return $_[0]->{maximum_line_length} - $_[0]->get_column($jmax);
     }
 }
 
diff --git a/pm2pl b/pm2pl
index 699293639bf78381ecd44e2bdc93eb4fe50adcd2..3e3af2c667d0c27869fc198701bbb543edfa8672 100755 (executable)
--- a/pm2pl
+++ b/pm2pl
@@ -73,7 +73,7 @@ my $VERSION = get_version("lib/Perl/Tidy.pm");
 my $outfile = "perltidy-$VERSION.pl";
 if ( $Opts{o} ) { $outfile = $Opts{o} }
 my $fh_out;
-open( $fh_out,, ">", $outfile ) or die "can't open file '$outfile' : $!\n";
+open( $fh_out, ">", $outfile ) or die "can't open file '$outfile' : $!\n";
 print "Creating standalone perltidy script '$outfile' ....";
 
 # first, open the script and copy the first (hash-bang) line
index f79abe8dbf34506e1b4cae6962bc68ab2620520c..b51c0c5fc7f3e85d78378c8d9c6b5cb631ae17f4 100644 (file)
@@ -407,11 +407,11 @@ elsif ( $i > $depth )  { $_ = 0; }
 # no one-line block for first map with -ce -cbl=map,sort,grep
 @sorted = map {
     $_->[0]
-} sort {
+  } sort {
     $a->[1] <=> $b->[1] or $a->[0] cmp $b->[0]
-} map {
+  } map {
     [ $_, length($_) ]
-} @unsorted;
+  } @unsorted;
 #14...........
         },
 
index 7322a8b5cbb886c6efca7b7ca4662b208205eda9..9e8f75c05b7afb3564a602ac2db0a2a271339f12 100644 (file)
@@ -236,11 +236,11 @@ my %Structure = $Self->PackageParse( String => $Package );
 # perltidy -wn -ce -cbl=sort,map,grep
 @sorted = map {
     $_->[0]
-} sort {
+  } sort {
     $a->[1] <=> $b->[1] or $a->[0] cmp $b->[0]
-} map {
+  } map {
     [ $_, length($_) ]
-} @unsorted;
+  } @unsorted;
 #5...........
         },
 
index 5cf8a6cb677f8893bd04018142bad63fa21e1aed..c0516fcf1128890cc665e6152d0606f2a1ae74d3 100644 (file)
@@ -9,6 +9,17 @@
 #6 git93.def
 #7 git93.git93
 #8 c139.def
+#9 drc.def
+#10 drc.drc
+#11 git105.def
+#12 git106.def
+#13 git106.git106
+#14 c154.def
+#15 code_skipping.code_skipping
+#16 c158.def
+#17 git108.def
+#18 git108.git108
+#19 wtc.def
 
 # To locate test #13 you can search for its name or the string '#13'
 
@@ -26,10 +37,19 @@ BEGIN {
     # BEGIN SECTION 1: Parameter combinations #
     ###########################################
     $rparams = {
-        'bal2'  => "-bal=2",
-        'c133'  => "-boc",
-        'def'   => "",
-        'git93' => <<'----------',
+        'bal2'          => "-bal=2",
+        'c133'          => "-boc",
+        'code_skipping' => <<'----------',
+# same as the default but tests -cs -csb and -cse
+--code-skipping
+--code-skipping-begin='#<<V'
+--code-skipping-end='#>>V'
+----------
+        'def'    => "",
+        'drc'    => "-drc",
+        'git106' => "-xlp -gnu -xci",
+        'git108' => "-wn -wfc",
+        'git93'  => <<'----------',
 -vxl='q'
 ----------
         'lpxl6' => <<'----------',
@@ -95,6 +115,158 @@ _
 $r = $c->         
 
 sql_set_env_attr( $evh, $SQL_ATTR_ODBC_VERSION, $SQL_OV_ODBC2, 0 );
+----------
+
+        'c154' => <<'----------',
+{{{{
+for (
+    $order =
+    $start_order * $nbSubOrderByOrder + $start_suborder ;
+    !exists $level_hash{$level}->{$order}
+    and $order <=
+    $stop_order * $nbSubOrderByOrder + $stop_suborder ;
+    $order++
+  )
+{
+}
+
+# has comma
+for (
+    $q = 201 ;
+    print '-' x 79,
+    "\n" ;
+    $g = (
+       $f ^ ( $w = ( $z = $m . $e ) ^ substr $e, $q )
+         ^ ( $n = $b ^ $d | $a ^ $l )
+    ) & ( $w | $z ^ $f ^ $n ) & ( $l | $g )
+  )
+{
+    ...;
+}
+
+for (
+    $j = 0, $match_j = -1 ;
+    $j < $sub_len
+      &&
+
+      # changed from naive_string_matcher
+      $sub->[$j] eq $big->[ $i + $j ] ; $j++
+  )
+{
+    ...;
+}
+}}}}
+----------
+
+        'c158' => <<'----------',
+my $meta = try { $package->meta }
+or die "$package does not have a ->meta method\n";
+
+my ($curr) = current();
+err(@_);
+----------
+
+        'code_skipping' => <<'----------',
+%Hdr=%U2E=%E2U=%Fallback=();
+$in_charmap=$nerror=$nwarning=0;
+$.=0;
+#<<V  code skipping: perltidy will pass this verbatim without error checking
+
+    }}} {{{
+
+#>>V
+my $self=shift;
+my $cloning=shift;
+----------
+
+        'drc' => <<'----------',
+ignoreSpec( $file, "file",, \%spec,,, \%Rspec );
+----------
+
+        'git105' => <<'----------',
+use v5.36;
+
+use experimental 'for_list';
+
+for my ( $k, $v ) ( 1, 2, 3, 4 ) {
+    say "$k:$v";
+}
+say 'end';
+
+----------
+
+        'git106' => <<'----------',
+is( $module->VERSION, $expected,
+    "$main_module->VERSION matches $module->VERSION ($expected)" );
+
+ok( ( $@ eq "" && "@b" eq "1 4 5 9" ),
+    'redefinition should not take effect during the sort' );
+
+&$f(
+    ( map { $points->slice($_) } @sls1 ),
+    ( map { $n->slice($_) } @sls1 ),
+    ( map { $this->{Colors}->slice($_) } @sls1 )
+);
+
+AA(
+    "0123456789012345678901234567890123456789",
+    "0123456789012345678901234567890123456789"
+);
+
+AAAAAA(
+    "0123456789012345678901234567890123456789",
+    "0123456789012345678901234567890123456789"
+);
+
+# padded
+return !( $elem->isa('PPI::Statement::End')
+    || $elem->isa('PPI::Statement::Data') );
+
+for (
+    $s = $dbobj->seq( $k, $v, R_LAST ) ;
+    $s == 0 ;
+    $s = $dbobj->seq( $k, $v, R_PREV )
+  )
+{
+    print "$k: $v\n";
+}
+
+# excess without -xci
+fresh_perl_is( '-C-',
+    <<'abcdefghijklmnopq', {}, "ambiguous unary operator check doesn't crash" );
+Warning: Use of "-C-" without parentheses is ambiguous at - line 1.
+abcdefghijklmnopq
+
+# excess with -xci
+{
+    {
+        {
+            $self->privmsg( $to,
+                "One moment please, I shall display the groups with agendas:" );
+        }
+    }
+}
+----------
+
+        'git108' => <<'----------',
+elf->call_method(
+    method_name_foo => {
+        some_arg1       => $foo,
+        some_other_arg3 => $bar->{'baz'},
+    }
+);
+
+# leading dash
+my $species = new Bio::Species(
+    -classification => [
+        qw(
+          sapiens Homo Hominidae
+          Catarrhini Primates Eutheria
+          Mammalia Vertebrata
+          Chordata Metazoa Eukaryota
+        )
+    ]
+);
 ----------
 
         'git93' => <<'----------',
@@ -182,6 +354,56 @@ $behaviour = {
               dog   => {prowl  => "growl", pool => "drool"},
               mouse => {nibble => "kibble"},
              };
+----------
+
+        'wtc' => <<'----------',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney", ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart", ],
+);
+
+# single line
+( $name, $body ) = ( $2, $3, );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow', );
+
+# this will pass for 'h'
+my $new = {
+      %$item,
+      text => $leaf,
+      color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle'
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create  => 1, );
+    },
+)->pack( -side => 'left', );
+
+my $no_index_1_1 =
+  { 'map' =>
+      { ':key' => { name => \&string, list => { value => \&string }, }, }, };
+
+
 ----------
     };
 
@@ -405,6 +627,322 @@ $r = $c->
   sql_set_env_attr( $evh, $SQL_ATTR_ODBC_VERSION, $SQL_OV_ODBC2, 0 );
 #8...........
         },
+
+        'drc.def' => {
+            source => "drc",
+            params => "def",
+            expect => <<'#9...........',
+ignoreSpec( $file, "file",, \%spec,,, \%Rspec );
+#9...........
+        },
+
+        'drc.drc' => {
+            source => "drc",
+            params => "drc",
+            expect => <<'#10...........',
+ignoreSpec( $file, "file", \%spec, \%Rspec );
+#10...........
+        },
+
+        'git105.def' => {
+            source => "git105",
+            params => "def",
+            expect => <<'#11...........',
+use v5.36;
+
+use experimental 'for_list';
+
+for my ( $k, $v ) ( 1, 2, 3, 4 ) {
+    say "$k:$v";
+}
+say 'end';
+
+#11...........
+        },
+
+        'git106.def' => {
+            source => "git106",
+            params => "def",
+            expect => <<'#12...........',
+is( $module->VERSION, $expected,
+    "$main_module->VERSION matches $module->VERSION ($expected)" );
+
+ok( ( $@ eq "" && "@b" eq "1 4 5 9" ),
+    'redefinition should not take effect during the sort' );
+
+&$f(
+    ( map { $points->slice($_) } @sls1 ),
+    ( map { $n->slice($_) } @sls1 ),
+    ( map { $this->{Colors}->slice($_) } @sls1 )
+);
+
+AA(
+    "0123456789012345678901234567890123456789",
+    "0123456789012345678901234567890123456789"
+);
+
+AAAAAA(
+    "0123456789012345678901234567890123456789",
+    "0123456789012345678901234567890123456789"
+);
+
+# padded
+return !( $elem->isa('PPI::Statement::End')
+    || $elem->isa('PPI::Statement::Data') );
+
+for (
+    $s = $dbobj->seq( $k, $v, R_LAST ) ;
+    $s == 0 ;
+    $s = $dbobj->seq( $k, $v, R_PREV )
+  )
+{
+    print "$k: $v\n";
+}
+
+# excess without -xci
+fresh_perl_is( '-C-',
+    <<'abcdefghijklmnopq', {}, "ambiguous unary operator check doesn't crash" );
+Warning: Use of "-C-" without parentheses is ambiguous at - line 1.
+abcdefghijklmnopq
+
+# excess with -xci
+{
+    {
+        {
+            $self->privmsg( $to,
+                "One moment please, I shall display the groups with agendas:" );
+        }
+    }
+}
+#12...........
+        },
+
+        'git106.git106' => {
+            source => "git106",
+            params => "git106",
+            expect => <<'#13...........',
+is($module->VERSION, $expected,
+   "$main_module->VERSION matches $module->VERSION ($expected)");
+
+ok(($@ eq "" && "@b" eq "1 4 5 9"),
+   'redefinition should not take effect during the sort');
+
+&$f((map { $points->slice($_) } @sls1),
+    (map { $n->slice($_) } @sls1),
+    (map { $this->{Colors}->slice($_) } @sls1));
+
+AA("0123456789012345678901234567890123456789",
+   "0123456789012345678901234567890123456789");
+
+AAAAAA("0123456789012345678901234567890123456789",
+       "0123456789012345678901234567890123456789");
+
+# padded
+return !(   $elem->isa('PPI::Statement::End')
+         || $elem->isa('PPI::Statement::Data'));
+
+for ($s = $dbobj->seq($k, $v, R_LAST) ;
+     $s == 0 ;
+     $s = $dbobj->seq($k, $v, R_PREV))
+{
+    print "$k: $v\n";
+}
+
+# excess without -xci
+fresh_perl_is('-C-',
+     <<'abcdefghijklmnopq', {}, "ambiguous unary operator check doesn't crash");
+Warning: Use of "-C-" without parentheses is ambiguous at - line 1.
+abcdefghijklmnopq
+
+# excess with -xci
+{
+    {
+        {
+            $self->privmsg($to,
+                   "One moment please, I shall display the groups with agendas:"
+            );
+        }
+    }
+}
+#13...........
+        },
+
+        'c154.def' => {
+            source => "c154",
+            params => "def",
+            expect => <<'#14...........',
+{
+    {
+        {
+            {
+                for (
+                    $order =
+                      $start_order * $nbSubOrderByOrder + $start_suborder ;
+                    !exists $level_hash{$level}->{$order}
+                      and $order <=
+                      $stop_order * $nbSubOrderByOrder + $stop_suborder ;
+                    $order++
+                  )
+                {
+                }
+
+                # has comma
+                for (
+                    $q = 201 ;
+                    print '-' x 79, "\n" ;
+                    $g = (
+                          $f ^ ( $w = ( $z = $m . $e ) ^ substr $e, $q )
+                          ^ ( $n = $b ^ $d | $a ^ $l )
+                    ) & ( $w | $z ^ $f ^ $n ) & ( $l | $g )
+                  )
+                {
+                    ...;
+                }
+
+                for (
+                    $j = 0, $match_j = -1 ;
+                    $j < $sub_len
+                      &&
+
+                      # changed from naive_string_matcher
+                      $sub->[$j] eq $big->[ $i + $j ] ;
+                    $j++
+                  )
+                {
+                    ...;
+                }
+            }
+        }
+    }
+}
+#14...........
+        },
+
+        'code_skipping.code_skipping' => {
+            source => "code_skipping",
+            params => "code_skipping",
+            expect => <<'#15...........',
+%Hdr        = %U2E    = %E2U      = %Fallback = ();
+$in_charmap = $nerror = $nwarning = 0;
+$.          = 0;
+#<<V  code skipping: perltidy will pass this verbatim without error checking
+
+    }}} {{{
+
+#>>V
+my $self    = shift;
+my $cloning = shift;
+#15...........
+        },
+
+        'c158.def' => {
+            source => "c158",
+            params => "def",
+            expect => <<'#16...........',
+my $meta = try { $package->meta }
+  or die "$package does not have a ->meta method\n";
+
+my ($curr) = current();
+err(@_);
+#16...........
+        },
+
+        'git108.def' => {
+            source => "git108",
+            params => "def",
+            expect => <<'#17...........',
+elf->call_method(
+    method_name_foo => {
+        some_arg1       => $foo,
+        some_other_arg3 => $bar->{'baz'},
+    }
+);
+
+# leading dash
+my $species = new Bio::Species(
+    -classification => [
+        qw(
+          sapiens Homo Hominidae
+          Catarrhini Primates Eutheria
+          Mammalia Vertebrata
+          Chordata Metazoa Eukaryota
+        )
+    ]
+);
+#17...........
+        },
+
+        'git108.git108' => {
+            source => "git108",
+            params => "git108",
+            expect => <<'#18...........',
+elf->call_method( method_name_foo => {
+    some_arg1       => $foo,
+    some_other_arg3 => $bar->{'baz'},
+} );
+
+# leading dash
+my $species = new Bio::Species( -classification => [ qw(
+    sapiens Homo Hominidae
+    Catarrhini Primates Eutheria
+    Mammalia Vertebrata
+    Chordata Metazoa Eukaryota
+) ] );
+#18...........
+        },
+
+        'wtc.def' => {
+            source => "wtc",
+            params => "def",
+            expect => <<'#19...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney", ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart", ],
+);
+
+# single line
+( $name, $body ) = ( $2, $3, );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow', );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle'
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1, );
+    },
+)->pack( -side => 'left', );
+
+my $no_index_1_1 =
+  { 'map' =>
+      { ':key' => { name => \&string, list => { value => \&string }, }, }, };
+
+#19...........
+        },
     };
 
     my $ntests = 0 + keys %{$rtests};
diff --git a/t/snippets27.t b/t/snippets27.t
new file mode 100644 (file)
index 0000000..beb1ef5
--- /dev/null
@@ -0,0 +1,708 @@
+# Created with: ./make_t.pl
+
+# Contents:
+#1 wtc.wtc1
+#2 wtc.wtc2
+#3 wtc.wtc3
+#4 wtc.wtc4
+#5 wtc.wtc5
+#6 wtc.wtc6
+#7 dwic.def
+#8 dwic.dwic
+#9 wtc.wtc7
+#10 rt144979.def
+#11 rt144979.rt144979
+
+# To locate test #13 you can search for its name or the string '#13'
+
+use strict;
+use Test::More;
+use Carp;
+use Perl::Tidy;
+my $rparams;
+my $rsources;
+my $rtests;
+
+BEGIN {
+
+    ###########################################
+    # BEGIN SECTION 1: Parameter combinations #
+    ###########################################
+    $rparams = {
+        'def'      => "",
+        'dwic'     => "-wn -dwic",
+        'rt144979' => "-xci -ce -lp",
+        'wtc1'     => "-wtc=0 -dtc",
+        'wtc2'     => "-wtc=1 -atc",
+        'wtc3'     => "-wtc=m -atc",
+        'wtc4'     => "-wtc=m -atc -dtc",
+        'wtc5'     => "-wtc=b -atc -dtc -vtc=2",
+        'wtc6'     => "-wtc=i -atc -dtc -vtc=2",
+        'wtc7'     => "-wtc=h -atc -dtc -vtc=2",
+    };
+
+    ############################
+    # BEGIN SECTION 2: Sources #
+    ############################
+    $rsources = {
+
+        'dwic' => <<'----------',
+    skip_symbols(
+        [ qw(
+            Perl_dump_fds
+            Perl_ErrorNo
+            Perl_GetVars
+            PL_sys_intern
+        ) ],
+    );
+----------
+
+        'rt144979' => <<'----------',
+# part 1
+GetOptions(
+      "format|f=s" => sub {
+          my ( $n, $v ) = @_;
+          if ( ( my $k = $formats{$v} ) ) {
+              $format = $k;
+      } else {
+              die("--format must be 'system' or 'user'\n");
+          }
+          return;
+      },
+); 
+
+# part 2
+{{{
+            my $desc =
+              $access
+              ? "for -$op under use filetest 'access' $desc_tail"
+              : "for -$op $desc_tail";
+            {
+                local $SIG{__WARN__} = sub {
+                    my $w = shift;
+                    if ($w =~ /^File::stat ignores VMS ACLs/)
+                    {
+                        ++$vwarn;
+                      } elsif (
+                              $w =~ /^File::stat ignores use filetest 'access'/)
+                    {
+                        ++$awarn;
+                    } else
+                    {
+                        $warnings .= $w;
+                    }
+                };
+                $rv = eval "$access; -$op \$stat";
+            }
+}}}
+
+----------
+
+        'wtc' => <<'----------',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney", ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart", ],
+);
+
+# single line
+( $name, $body ) = ( $2, $3, );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow', );
+
+# this will pass for 'h'
+my $new = {
+      %$item,
+      text => $leaf,
+      color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle'
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create  => 1, );
+    },
+)->pack( -side => 'left', );
+
+my $no_index_1_1 =
+  { 'map' =>
+      { ':key' => { name => \&string, list => { value => \&string }, }, }, };
+
+
+----------
+    };
+
+    ####################################
+    # BEGIN SECTION 3: Expected output #
+    ####################################
+    $rtests = {
+
+        'wtc.wtc1' => {
+            source => "wtc",
+            params => "wtc1",
+            expect => <<'#1...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney" ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart" ]
+);
+
+# single line
+( $name, $body ) = ( $2, $3 );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow' );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green'
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle'
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1 );
+    }
+)->pack( -side => 'left' );
+
+my $no_index_1_1 =
+  { 'map' => { ':key' => { name => \&string, list => { value => \&string } } }
+  };
+
+#1...........
+        },
+
+        'wtc.wtc2' => {
+            source => "wtc",
+            params => "wtc2",
+            expect => <<'#2...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney", ],
+    [ "george", "jane",  "elroy", ],
+    [ "homer",  "marge", "bart", ],
+);
+
+# single line
+( $name, $body, ) = ( $2, $3, );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow', );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy,
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle',
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1, );
+    },
+)->pack( -side => 'left', );
+
+my $no_index_1_1 =
+  { 'map' =>
+      { ':key' => { name => \&string, list => { value => \&string }, }, }, };
+
+#2...........
+        },
+
+        'wtc.wtc3' => {
+            source => "wtc",
+            params => "wtc3",
+            expect => <<'#3...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney", ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart", ],
+);
+
+# single line
+( $name, $body ) = ( $2, $3, );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow', );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy,
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle',
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1, );
+    },
+)->pack( -side => 'left', );
+
+my $no_index_1_1 =
+  { 'map' =>
+      { ':key' => { name => \&string, list => { value => \&string }, }, }, };
+
+#3...........
+        },
+
+        'wtc.wtc4' => {
+            source => "wtc",
+            params => "wtc4",
+            expect => <<'#4...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney" ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart" ],
+);
+
+# single line
+( $name, $body ) = ( $2, $3 );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow', );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy,
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle',
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1 );
+    },
+)->pack( -side => 'left' );
+
+my $no_index_1_1 =
+  { 'map' => { ':key' => { name => \&string, list => { value => \&string } } },
+  };
+
+#4...........
+        },
+
+        'wtc.wtc5' => {
+            source => "wtc",
+            params => "wtc5",
+            expect => <<'#5...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney" ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart" ],
+);
+
+# single line
+( $name, $body ) = ( $2, $3 );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow' );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy,
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle',
+);
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1 );
+    },
+)->pack( -side => 'left' );
+
+my $no_index_1_1 =
+  { 'map' => { ':key' => { name => \&string, list => { value => \&string } } }
+  };
+
+#5...........
+        },
+
+        'wtc.wtc6' => {
+            source => "wtc",
+            params => "wtc6",
+            expect => <<'#6...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney" ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart" ] );
+
+# single line
+( $name, $body ) = ( $2, $3 );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow' );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy,
+);
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle' );
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1 );
+    },
+)->pack( -side => 'left' );
+
+my $no_index_1_1 =
+  { 'map' => { ':key' => { name => \&string, list => { value => \&string } } }
+  };
+
+#6...........
+        },
+
+        'dwic.def' => {
+            source => "dwic",
+            params => "def",
+            expect => <<'#7...........',
+    skip_symbols(
+        [
+            qw(
+              Perl_dump_fds
+              Perl_ErrorNo
+              Perl_GetVars
+              PL_sys_intern
+            )
+        ],
+    );
+#7...........
+        },
+
+        'dwic.dwic' => {
+            source => "dwic",
+            params => "dwic",
+            expect => <<'#8...........',
+    skip_symbols( [ qw(
+        Perl_dump_fds
+        Perl_ErrorNo
+        Perl_GetVars
+        PL_sys_intern
+    ) ] );
+#8...........
+        },
+
+        'wtc.wtc7' => {
+            source => "wtc",
+            params => "wtc7",
+            expect => <<'#9...........',
+# both single and multiple line lists:
+@LoL = (
+    [ "fred",   "barney" ],
+    [ "george", "jane",  "elroy" ],
+    [ "homer",  "marge", "bart" ] );
+
+# single line
+( $name, $body ) = ( $2, $3 );
+
+# multiline, but not bare
+$text = $main->Scrolled( TextUndo, $yyy, $zzz, $wwwww,
+    selectbackgroundxxxxx => 'yellow' );
+
+# this will pass for 'h'
+my $new = {
+    %$item,
+    text  => $leaf,
+    color => 'green',
+};
+
+# matches 'i'
+my @list = (
+
+    $xx,
+    $yy );
+
+# does not match 'h'
+$c1->create(
+    'rectangle', 40, 60, 80, 80,
+    -fill => 'red',
+    -tags => 'rectangle' );
+
+$dasm_frame->Button(
+    -text    => 'Locate',
+    -command => sub {
+        $target_binary = $fs->Show( -popover => 'cursor', -create => 1 );
+    },
+)->pack( -side => 'left' );
+
+my $no_index_1_1 =
+  { 'map' => { ':key' => { name => \&string, list => { value => \&string } } }
+  };
+
+#9...........
+        },
+
+        'rt144979.def' => {
+            source => "rt144979",
+            params => "def",
+            expect => <<'#10...........',
+# part 1
+GetOptions(
+    "format|f=s" => sub {
+        my ( $n, $v ) = @_;
+        if ( ( my $k = $formats{$v} ) ) {
+            $format = $k;
+        }
+        else {
+            die("--format must be 'system' or 'user'\n");
+        }
+        return;
+    },
+);
+
+# part 2
+{
+    {
+        {
+            my $desc =
+              $access
+              ? "for -$op under use filetest 'access' $desc_tail"
+              : "for -$op $desc_tail";
+            {
+                local $SIG{__WARN__} = sub {
+                    my $w = shift;
+                    if ( $w =~ /^File::stat ignores VMS ACLs/ ) {
+                        ++$vwarn;
+                    }
+                    elsif ( $w =~ /^File::stat ignores use filetest 'access'/ )
+                    {
+                        ++$awarn;
+                    }
+                    else {
+                        $warnings .= $w;
+                    }
+                };
+                $rv = eval "$access; -$op \$stat";
+            }
+        }
+    }
+}
+
+#10...........
+        },
+
+        'rt144979.rt144979' => {
+            source => "rt144979",
+            params => "rt144979",
+            expect => <<'#11...........',
+# part 1
+GetOptions(
+      "format|f=s" => sub {
+          my ( $n, $v ) = @_;
+          if ( ( my $k = $formats{$v} ) ) {
+              $format = $k;
+          } else {
+              die("--format must be 'system' or 'user'\n");
+          }
+          return;
+      },
+);
+
+# part 2
+{
+    {
+        {
+            my $desc =
+              $access
+              ? "for -$op under use filetest 'access' $desc_tail"
+              : "for -$op $desc_tail";
+            {
+                local $SIG{__WARN__} = sub {
+                    my $w = shift;
+                    if ( $w =~ /^File::stat ignores VMS ACLs/ ) {
+                        ++$vwarn;
+                    } elsif (
+                             $w =~ /^File::stat ignores use filetest 'access'/ )
+                    {
+                        ++$awarn;
+                    } else {
+                        $warnings .= $w;
+                    }
+                };
+                $rv = eval "$access; -$op \$stat";
+            }
+        }
+    }
+}
+
+#11...........
+        },
+    };
+
+    my $ntests = 0 + keys %{$rtests};
+    plan tests => $ntests;
+}
+
+###############
+# EXECUTE TESTS
+###############
+
+foreach my $key ( sort keys %{$rtests} ) {
+    my $output;
+    my $sname  = $rtests->{$key}->{source};
+    my $expect = $rtests->{$key}->{expect};
+    my $pname  = $rtests->{$key}->{params};
+    my $source = $rsources->{$sname};
+    my $params = defined($pname) ? $rparams->{$pname} : "";
+    my $stderr_string;
+    my $errorfile_string;
+    my $err = Perl::Tidy::perltidy(
+        source      => \$source,
+        destination => \$output,
+        perltidyrc  => \$params,
+        argv        => '',             # for safety; hide any ARGV from perltidy
+        stderr      => \$stderr_string,
+        errorfile   => \$errorfile_string,    # not used when -se flag is set
+    );
+    if ( $err || $stderr_string || $errorfile_string ) {
+        print STDERR "Error output received for test '$key'\n";
+        if ($err) {
+            print STDERR "An error flag '$err' was returned\n";
+            ok( !$err );
+        }
+        if ($stderr_string) {
+            print STDERR "---------------------\n";
+            print STDERR "<<STDERR>>\n$stderr_string\n";
+            print STDERR "---------------------\n";
+            ok( !$stderr_string );
+        }
+        if ($errorfile_string) {
+            print STDERR "---------------------\n";
+            print STDERR "<<.ERR file>>\n$errorfile_string\n";
+            print STDERR "---------------------\n";
+            ok( !$errorfile_string );
+        }
+    }
+    else {
+        if ( !is( $output, $expect, $key ) ) {
+            my $leno = length($output);
+            my $lene = length($expect);
+            if ( $leno == $lene ) {
+                print STDERR
+"#> Test '$key' gave unexpected output.  Strings differ but both have length $leno\n";
+            }
+            else {
+                print STDERR
+"#> Test '$key' gave unexpected output.  String lengths differ: output=$leno, expected=$lene\n";
+            }
+        }
+    }
+}
diff --git a/t/test_DEBUG.t b/t/test_DEBUG.t
new file mode 100755 (executable)
index 0000000..0bf0be7
--- /dev/null
@@ -0,0 +1,55 @@
+# Test that the -D (-DEBUG) flag works
+use strict;
+use Carp;
+use Perl::Tidy;
+use Test::More;
+my $name = 'DEBUG test';
+
+BEGIN {
+    plan tests => 2;
+}
+
+my $source = <<'EOM';
+my @words = qw(
+alpha beta gamma
+);
+EOM
+
+my $expect = <<'EOM';
+my @words = qw(
+  alpha beta gamma
+);
+EOM
+
+my $debug_expect = <<'EOM';
+Use -dump-token-types (-dtt) to get a list of token type codes
+1: my @words = qw(
+1: kkbiiiiiib=bqqq
+2: alpha beta gamma
+2: qqqqqqqqqqqqqqqq
+3: );
+3: q;
+EOM
+
+my $output;
+my $stderr_string;
+my $errorfile_string;
+my $debug_string;
+my $perltidyrc = "";
+my $err    = Perl::Tidy::perltidy(
+    argv        => '-D -npro',
+    perltidyrc  => \$perltidyrc,  # avoid reading unwanted .perltidyrc
+    source      => \$source,
+    destination => \$output,
+    stderr      => \$stderr_string,
+    errorfile   => \$errorfile_string,    # not used when -se flag is set
+    debugfile   => \$debug_string,
+);
+
+if ( $err || $stderr_string || $errorfile_string ) {
+    ok(0);
+}
+else {
+    is( $output,       $expect,       $name );
+    is( $debug_string, $debug_expect, $name );
+}
index 643023d393ee12464d6a70e9c28bd3896a2750c4..877409fd99e0db3f50e7ec9809e0533c2bfa158e 100644 (file)
@@ -21,10 +21,25 @@ use Perl::Tidy;
 # through perltidy should read/write identical contents (previously only
 # file test behaved correctly)
 
+# Test::More in perl versions before 5.10 does not have sub note
+# so just skip this test
+
 plan( tests => 6 );
 
 test_all();
 
+sub my_note {
+    my ($msg) = @_;
+
+    # try to work around problem where sub Test::More::note does not exist
+    # in older versions of perl
+    if ($] >= 5.010) {
+       note($msg);
+    }
+    return;
+}
+
+
 sub test_all {
     my $test_file = "$Bin/testwide-passthrough.pl.src";
     test_file2file($test_file);
@@ -40,7 +55,7 @@ sub test_file2file {
     my $source      = $test_file;
     my $destination = $tmp_file->filename();
 
-    note("Testing file2file: '$source' => '$destination'\n");
+    my_note("Testing file2file: '$source' => '$destination'\n");
 
     my $tidyresult = Perl::Tidy::perltidy(
         argv        => '-utf8 -npro',
@@ -54,7 +69,7 @@ sub test_file2file {
 
     my $source_hex      = unpack( 'H*', $source_str );
     my $destination_hex = unpack( 'H*', $destination_str );
-    note("Comparing contents:\n  $source_hex\n  $destination_hex\n");
+    my_note("Comparing contents:\n  $source_hex\n  $destination_hex\n");
 
     ok( $source_hex eq $destination_hex, 'file content compare' );
 }
@@ -65,7 +80,7 @@ sub test_scalar2scalar {
     my $source = slurp_raw($testfile);
     my $destination;
 
-    note("Testing scalar2scalar\n");
+    my_note("Testing scalar2scalar\n");
 
     my $tidyresult = Perl::Tidy::perltidy(
         argv        => '-utf8 -eos -npro',
@@ -77,7 +92,7 @@ sub test_scalar2scalar {
     my $source_hex      = unpack( 'H*', $source );
     my $destination_hex = unpack( 'H*', $destination );
 
-    note("Comparing contents:\n  $source_hex\n  $destination_hex\n");
+    my_note("Comparing contents:\n  $source_hex\n  $destination_hex\n");
     ok( $source_hex eq $destination_hex, 'scalar content compare' );
 }
 
@@ -87,7 +102,7 @@ sub test_scalararray2scalararray {
     my $source      = [ lines_raw($testfile) ];
     my $destination = [];
 
-    note("Testing scalararray2scalararray\n");
+    my_note("Testing scalararray2scalararray\n");
 
     my $tidyresult = Perl::Tidy::perltidy(
         argv        => '-utf8 -eos -npro',
@@ -102,7 +117,7 @@ sub test_scalararray2scalararray {
     my $source_hex      = unpack( 'H*', $source_str );
     my $destination_hex = unpack( 'H*', $destination_str );
 
-    note("Comparing contents:\n  $source_hex\n  $destination_hex\n");
+    my_note("Comparing contents:\n  $source_hex\n  $destination_hex\n");
     ok( $source_hex eq $destination_hex, 'scalararray content compare' );
 }
 
index 1dd12dc9d4419bcd63292b3956902af58cb4c2a0..13ba6405509a63a4d4bd2cbe297afe3f9e0d1609 100644 (file)
@@ -23,6 +23,17 @@ plan( tests => 6 );
 
 test_all();
 
+sub my_note {
+    my ($msg) = @_;
+
+    # work around problem where sub Test::More::note does not exist
+    # in older versions of perl
+    if ($] >= 5.010) {
+       note($msg);
+    }
+    return;
+}
+
 sub test_all {
     my $test_file = "$Bin/testwide-tidy.pl.src";
     my $tidy_file = "$Bin/testwide-tidy.pl.srctdy";
@@ -42,7 +53,7 @@ sub test_file2file {
     my $source      = $test_file;
     my $destination = $tmp_file->filename();
 
-    note("Testing file2file: '$source' => '$destination'\n");
+    my_note("Testing file2file: '$source' => '$destination'\n");
 
     my $tidyresult = Perl::Tidy::perltidy(
         argv        => '-utf8 -npro',
@@ -54,7 +65,7 @@ sub test_file2file {
     my $destination_str = slurp_raw($destination);
     my $destination_hex = unpack( 'H*', $destination_str );
 
-    note("Comparing contents:\n  $tidy_hex\n  $destination_hex\n");
+    my_note("Comparing contents:\n  $tidy_hex\n  $destination_hex\n");
     ok($tidy_hex eq $destination_hex, 'file content compare');
 
 }
@@ -67,7 +78,7 @@ sub test_scalar2scalar {
     my $source = slurp_raw($test_file);
     my $destination;
 
-    note("Testing scalar2scalar\n");
+    my_note("Testing scalar2scalar\n");
 
     my $tidyresult = Perl::Tidy::perltidy(
         argv        => '-utf8 -eos -npro',
@@ -78,7 +89,7 @@ sub test_scalar2scalar {
 
     my $destination_hex = unpack( 'H*', $destination );
 
-    note("Comparing contents:\n  $tidy_hex\n  $destination_hex\n");
+    my_note("Comparing contents:\n  $tidy_hex\n  $destination_hex\n");
     ok($tidy_hex eq $destination_hex, 'scalar content compare');
 
 }
@@ -91,7 +102,7 @@ sub test_scalararray2scalararray {
     my $source      = [ lines_raw($test_file) ];
     my $destination = [];
 
-    note("Testing scalararray2scalararray\n");
+    my_note("Testing scalararray2scalararray\n");
 
     my $tidyresult = Perl::Tidy::perltidy(
         argv        => '-utf8 -eos -npro',
@@ -103,7 +114,7 @@ sub test_scalararray2scalararray {
     my $destination_str = join( '', @$destination );
     my $destination_hex = unpack( 'H*', $destination_str );
 
-    note("Comparing contents:\n  $tidy_hex\n  $destination_hex\n");
+    my_note("Comparing contents:\n  $tidy_hex\n  $destination_hex\n");
     ok($tidy_hex eq $destination_hex, 'scalararray content compare');
 }