This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
toke.c: Avoid work for tr/a-a/.../
authorKarl Williamson <khw@cpan.org>
Wed, 18 Jan 2017 21:21:02 +0000 (14:21 -0700)
committerKarl Williamson <khw@cpan.org>
Thu, 19 Jan 2017 21:42:08 +0000 (14:42 -0700)
A single element range can skip a bunch of work.

t/op/tr.t
toke.c

index 13d5e3c..25c397d 100644 (file)
--- a/t/op/tr.t
+++ b/t/op/tr.t
@@ -13,7 +13,7 @@ BEGIN {
 
 use utf8;
 
-plan tests => 214;
+plan tests => 215;
 
 # Test this first before we extend the stack with other operations.
 # This caused an asan failure due to a bad write past the end of the stack.
@@ -32,6 +32,9 @@ is($_, "abcdefghijklmnopqrstuvwxyz",    'lc');
 tr/b-y/B-Y/;
 is($_, "aBCDEFGHIJKLMNOPQRSTUVWXYz",    'partial uc');
 
+tr/a-a/AB/;
+is($_, "ABCDEFGHIJKLMNOPQRSTUVWXYz",    'single char range a-a');
+
 eval 'tr/a/\N{KATAKANA LETTER AINU P}/;';
 like $@,
      qr/\\N\{KATAKANA LETTER AINU P\} must not be a named sequence in transliteration operator/,
diff --git a/toke.c b/toke.c
index 5f8a76f..8409bb6 100644 (file)
--- a/toke.c
+++ b/toke.c
@@ -2993,7 +2993,7 @@ S_scan_const(pTHX_ char *start)
                  * 'offset_to_max' is the offset in 'sv' at which the character
                  *      (the range's maximum end point) before 'd'  begins.
                  */
-                const char * max_ptr = SvPVX_const(sv) + offset_to_max;
+                char * max_ptr = SvPVX(sv) + offset_to_max;
                 const char * min_ptr;
                 IV range_min;
                IV range_max;   /* last character in range */
@@ -3018,6 +3018,19 @@ S_scan_const(pTHX_ char *start)
                     range_max = * (U8*) max_ptr;
                 }
 
+                /* If the range is just a single code point, like tr/a-a/.../,
+                 * that code point is already in the output, twice.  We can
+                 * just back up over the second instance and avoid all the rest
+                 * of the work.  But if it is a variant character, it's been
+                 * counted twice, so decrement */
+                if (UNLIKELY(range_max == range_min)) {
+                    d = max_ptr;
+                    if (! has_utf8 && ! UVCHR_IS_INVARIANT(range_max)) {
+                        utf8_variant_count--;
+                    }
+                    goto range_done;
+                }
+
 #ifdef EBCDIC
                 /* On EBCDIC platforms, we may have to deal with portable
                  * ranges.  These happen if at least one range endpoint is a