This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Deprecate non-grapheme string delimiter
authorKarl Williamson <khw@cpan.org>
Tue, 20 Dec 2016 20:41:58 +0000 (13:41 -0700)
committerKarl Williamson <khw@cpan.org>
Sat, 24 Dec 2016 05:52:44 +0000 (22:52 -0700)
In order for Perl to eventually allow string delimiters to be Unicode
grapheme clusters (which look like a single character, but may be
multiple ones), we have to stop allowing a single char delimiter that
isn't a grapheme by itself.  These are unlikely to exist in actual code,
as they would typically display as attached to the character in front of
them, but we should be sure.

embed.fnc
embed.h
pod/perldelta.pod
pod/perldiag.pod
proto.h
regexec.c
t/lib/warnings/toke
toke.c

index 7e1c3f2..66cbee8 100644 (file)
--- a/embed.fnc
+++ b/embed.fnc
@@ -2465,6 +2465,11 @@ Es       |U8     |regtail_study  |NN RExC_state_t *pRExC_state \
 #if defined(PERL_IN_REGEXEC_C) || defined(PERL_IN_UTF8_C)
 EXRpM  |bool   |isFOO_lc       |const U8 classnum|const U8 character
 #endif
+
+#if defined(PERL_IN_REGEXEC_C) || defined(PERL_IN_TOKE_C)
+ERp    |bool   |_is_grapheme   |NN const U8 * strbeg|NN const U8 * s|NN const U8 *strend|const UV cp
+#endif
+
 #if defined(PERL_IN_REGEXEC_C)
 ERs    |bool   |isFOO_utf8_lc  |const U8 classnum|NN const U8* character
 ERs    |SSize_t|regmatch       |NN regmatch_info *reginfo|NN char *startpos|NN regnode *prog
diff --git a/embed.h b/embed.h
index 3b3eb86..ba7b2ca 100644 (file)
--- a/embed.h
+++ b/embed.h
 #define to_byte_substr(a)      S_to_byte_substr(aTHX_ a)
 #define to_utf8_substr(a)      S_to_utf8_substr(aTHX_ a)
 #  endif
+#  if defined(PERL_IN_REGEXEC_C) || defined(PERL_IN_TOKE_C)
+#define _is_grapheme(a,b,c,d)  Perl__is_grapheme(aTHX_ a,b,c,d)
+#  endif
 #  if defined(PERL_IN_REGEXEC_C) || defined(PERL_IN_UTF8_C)
 #define isFOO_lc(a,b)          Perl_isFOO_lc(aTHX_ a,b)
 #  endif
index f34e3e0..7ad4812 100644 (file)
@@ -47,7 +47,15 @@ XXX For a release on a stable branch, this section aspires to be:
 
 =head1 Deprecations
 
-XXX Any deprecated features, syntax, modules etc. should be listed here.
+=head2 String delimiters that aren't stand-alone graphemes are now
+deprecated
+
+In order for Perl to eventually allow string delimiters to be Unicode
+grapheme clusters (which look like a single character, but may be
+a sequence of several ones), we have to stop allowing a single char
+delimiter that isn't a grapheme by itself.  These are unlikely to exist
+in actual code, as they would typically display as attached to the
+character in front of them.
 
 =head2 Module removals
 
index 13cf13d..450aa36 100644 (file)
@@ -7064,6 +7064,31 @@ arguments and at least one of them is tainted.  This used to be allowed
 but will become a fatal error in a future version of perl.  Untaint your
 arguments.  See L<perlsec>.
 
+=item Use of unassigned code point or non-standalone grapheme for a
+delimiter will be a fatal error starting in Perl v5.30
+
+(D deprecated)
+A grapheme is what appears to a native-speaker of a language to be a
+character.  In Unicode (and hence Perl) a grapheme may actually be
+several adjacent characters that together form a complete grapheme.  For
+example, there can be a base character, like "R" and an accent, like a
+circumflex "^", that appear when displayed to be a single character with
+the circumflex hovering over the "R".  Perl currently allows things like
+that circumflex to be delimiters of strings, patterns, I<etc>.  When
+displayed, the circumflex would look like it belongs to the character
+just to the left of it.  In order to move the language to be able to
+accept graphemes as delimiters, we have to deprecate the use of
+delimiters which aren't graphemes by themselves.  Also, a delimiter must
+already be assigned (or known to be never going to be assigned) to try
+to future-proof code, for otherwise code that works today would fail to
+compile if the currently unassigned delimiter ends up being something
+that isn't a stand-alone grapheme.  Because Unicode is never going to
+assign
+L<non-character code points|perlunicode/Noncharacter code points>, nor
+L<code points that are above the legal Unicode maximum|
+perlunicode/Beyond Unicode code points>, those can be delimiters, and
+their use won't raise this warning.
+
 =item Use of uninitialized value%s
 
 (W uninitialized) An undefined value was used as if it were already
diff --git a/proto.h b/proto.h
index 3c3a6ce..cc9a584 100644 (file)
--- a/proto.h
+++ b/proto.h
@@ -5355,6 +5355,13 @@ STATIC void      S_to_utf8_substr(pTHX_ regexp * prog);
 #define PERL_ARGS_ASSERT_TO_UTF8_SUBSTR        \
        assert(prog)
 #endif
+#if defined(PERL_IN_REGEXEC_C) || defined(PERL_IN_TOKE_C)
+PERL_CALLCONV bool     Perl__is_grapheme(pTHX_ const U8 * strbeg, const U8 * s, const U8 *strend, const UV cp)
+                       __attribute__warn_unused_result__;
+#define PERL_ARGS_ASSERT__IS_GRAPHEME  \
+       assert(strbeg); assert(s); assert(strend)
+
+#endif
 #if defined(PERL_IN_REGEXEC_C) || defined(PERL_IN_UTF8_C)
 PERL_CALLCONV bool     Perl_isFOO_lc(pTHX_ const U8 classnum, const U8 character)
                        __attribute__warn_unused_result__;
index 5c5241c..23b2d3f 100644 (file)
--- a/regexec.c
+++ b/regexec.c
@@ -9704,6 +9704,64 @@ S_to_byte_substr(pTHX_ regexp *prog)
     return TRUE;
 }
 
+bool
+Perl__is_grapheme(pTHX_ const U8 * strbeg, const U8 * s, const U8 * strend, const UV cp)
+{
+    /* Temporary helper function for toke.c.  Verify that the code point 'cp'
+     * is a stand-alone grapheme.  The UTF-8 for 'cp' begins at position 's' in
+     * the larger string bounded by 'strbeg' and 'strend'.
+     *
+     * 'cp' needs to be assigned (if not a future version of the Unicode
+     * Standard could make it something that combines with adjacent characters,
+     * so code using it would then break), and there has to be a GCB break
+     * before and after the character. */
+
+    GCB_enum cp_gcb_val, prev_cp_gcb_val, next_cp_gcb_val;
+    const U8 * prev_cp_start;
+
+    PERL_ARGS_ASSERT__IS_GRAPHEME;
+
+    /* Unassigned code points are forbidden */
+    if (UNLIKELY(! ELEMENT_RANGE_MATCHES_INVLIST(
+                                    _invlist_search(PL_Assigned_invlist, cp))))
+    {
+        return FALSE;
+    }
+
+    cp_gcb_val = getGCB_VAL_CP(cp);
+
+    /* Find the GCB value of the previous code point in the input */
+    prev_cp_start = utf8_hop_back(s, -1, strbeg);
+    if (UNLIKELY(prev_cp_start == s)) {
+        prev_cp_gcb_val = GCB_EDGE;
+    }
+    else {
+        prev_cp_gcb_val = getGCB_VAL_UTF8(prev_cp_start, strend);
+    }
+
+    /* And check that is a grapheme boundary */
+    if (! isGCB(prev_cp_gcb_val, cp_gcb_val, strbeg, s,
+                TRUE /* is UTF-8 encoded */ ))
+    {
+        return FALSE;
+    }
+
+    /* Similarly verify there is a break between the current character and the
+     * following one */
+    s += UTF8SKIP(s);
+    if (s >= strend) {
+        next_cp_gcb_val = GCB_EDGE;
+    }
+    else {
+        next_cp_gcb_val = getGCB_VAL_UTF8(s, strend);
+    }
+
+    return isGCB(cp_gcb_val, next_cp_gcb_val, strbeg, s, TRUE);
+}
+
+
+
+
 /*
  * ex: set ts=8 sts=4 sw=4 et:
  */
index 1eb9f2e..828d597 100644 (file)
@@ -1563,3 +1563,15 @@ print (...) interpreted as function at - line 3.
 print (...) interpreted as function at - line 4.
 )
 ))
+########
+# NAME  Non-grapheme delimiters
+BEGIN{
+    if (ord('A') == 193) {
+        print "SKIPPED\n# ebcdic platforms generates different Malformed UTF-8 warnings.";
+        exit 0;
+    }
+}
+use utf8;
+my $a = qr ̂foobar̂;
+EXPECT
+Use of unassigned code point or non-standalone grapheme for a delimiter will be a fatal error starting in Perl v5.30 at - line 8.
diff --git a/toke.c b/toke.c
index 489e772..06a050e 100644 (file)
--- a/toke.c
+++ b/toke.c
@@ -10253,6 +10253,14 @@ S_scan_str(pTHX_ char *start, int keep_bracketed_quoted, int keep_delims, int re
     const char * opening_delims = "([{<";
     const char * closing_delims = ")]}>";
 
+    const char * non_grapheme_msg = "Use of unassigned code point or"
+                                    " non-standalone grapheme for a delimiter"
+                                    " will be a fatal error starting in Perl"
+                                    " v5.30";
+    /* The only non-UTF character that isn't a stand alone grapheme is
+     * white-space, hence can't be a delimiter.  So can skip for non-UTF-8 */
+    bool check_grapheme = UTF && ckWARN_d(WARN_DEPRECATED);
+
     PERL_ARGS_ASSERT_SCAN_STR;
 
     /* skip space before the delimiter */
@@ -10271,6 +10279,28 @@ S_scan_str(pTHX_ char *start, int keep_bracketed_quoted, int keep_delims, int re
     }
     else {
        termcode = utf8_to_uvchr_buf((U8*)s, (U8*)PL_bufend, &termlen);
+        if (check_grapheme) {
+            if (   UNLIKELY(UNICODE_IS_SUPER(termcode))
+                || UNLIKELY(UNICODE_IS_NONCHAR(termcode)))
+            {
+                /* These are considered graphemes, and since the ending
+                 * delimiter will be the same, we don't have to check the other
+                 * end */
+                check_grapheme = FALSE;
+            }
+            else if (UNLIKELY(! _is_grapheme((U8 *) start,
+                                             (U8 *) s,
+                                             (U8 *) PL_bufend,
+                                             termcode)))
+            {
+                Perl_warner(aTHX_ packWARN(WARN_DEPRECATED), "%s", non_grapheme_msg);
+
+                /* Don't have to check the other end, as have already warned at
+                 * this one */
+                check_grapheme = FALSE;
+            }
+        }
+
        Copy(s, termstr, termlen, U8);
     }
 
@@ -10329,6 +10359,15 @@ S_scan_str(pTHX_ char *start, int keep_bracketed_quoted, int keep_delims, int re
                    if (termlen == 1)
                        break;
                    if (s+termlen <= PL_bufend && memEQ(s, (char*)termstr, termlen))
+                        if (   check_grapheme
+                            && UNLIKELY(! _is_grapheme((U8 *) start,
+                                                              (U8 *) s,
+                                                              (U8 *) PL_bufend,
+                                                              termcode)))
+                        {
+                            Perl_warner(aTHX_ packWARN(WARN_DEPRECATED),
+                                        "%s", non_grapheme_msg);
+                        }
                        break;
                }
                else if (!has_utf8 && !UTF8_IS_INVARIANT((U8)*s) && UTF)
@@ -10405,7 +10444,7 @@ S_scan_str(pTHX_ char *start, int keep_bracketed_quoted, int keep_delims, int re
            CopLINE_set(PL_curcop, (line_t)PL_multi_start);
            return NULL;
        }
-       s = PL_bufptr;
+       s = start = PL_bufptr;
     }
 
     /* at this point, we have successfully read the delimited string */