This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
utf8.c Extract common code into macros
authorKarl Williamson <khw@cpan.org>
Sun, 18 Dec 2016 00:25:29 +0000 (17:25 -0700)
committerKarl Williamson <khw@cpan.org>
Fri, 23 Dec 2016 18:41:32 +0000 (11:41 -0700)
The 3 case changing functions: to upper, lower, and title case are
essentially identical except for what they call to actually do the
change; those being different macros or functions.

The fourth function, to fold, is identical to the other three for the
first part of its code, but diverges at the end in order to handle some
special cases.

This commit replaces the first part of the bodies of these 4 functions
by a common macro.  And it replaces the remainder of the first 3
functions by another common macro.

I'm not a fan of this kind of macro to use in generating code, but it
seems the best way to keep these definitions in sync.  (It has to be a
macro instead of a function because one of the parameters is a macro,
which you can't pass to a function.  I suppose one could create
functions that just calls their macro, and get around it that way, but
it doesn't seem worth it.)

This commit just moved the code to the macro, and I manually verified
that there were no logic changes.

1 of the passed-in functions requires one less argument (the final one)
than the other 3.  I originally tried to do something with the C
preprocessor to get around that, but it didn't work with the Win32
version of the preprocessor, so I gave up and added a dummy parameter to
the fourth function, which is static so that's ok to do.  Below, for the
record is my original attempt:

    /* These two macros are used to make optional a parameter to the
     * passed-in function to the macros just above.  If the passed-in
     * function doesn't take the parameter, use PLACEHOLDER in the macro
     * call; otherwise surround the parameter by a PARAM() call */
    #define PARAM(parameter) ,parameter
    #define PLACEHOLDER    /* Something for the preprocessor to grab onto */

And within the macro, it called the function like this:

    L1_func(*p, ustrp, lenp/*,*/ L1_func_extra_param)

embed.fnc
proto.h
utf8.c

index ca15006..392e5ee 100644 (file)
--- a/embed.fnc
+++ b/embed.fnc
@@ -738,7 +738,8 @@ AMp |UV     |to_uni_title   |UV c|NN U8 *p|NN STRLEN *lenp
 ADMpR  |bool   |isIDFIRST_lazy |NN const char* p
 ADMpR  |bool   |isALNUM_lazy   |NN const char* p
 #ifdef PERL_IN_UTF8_C
-snR    |U8     |to_lower_latin1|const U8 c|NULLOK U8 *p|NULLOK STRLEN *lenp
+snR    |U8     |to_lower_latin1|const U8 c|NULLOK U8 *p|NULLOK STRLEN *lenp  \
+               |const char dummy
 inR    |bool   |is_utf8_cp_above_31_bits|NN const U8 * const s|NN const U8 * const e
 #endif
 #if defined(PERL_IN_UTF8_C) || defined(PERL_IN_REGCOMP_C) || defined(PERL_IN_REGEXEC_C)
diff --git a/proto.h b/proto.h
index 7fcb579..1d1846e 100644 (file)
--- a/proto.h
+++ b/proto.h
@@ -5641,7 +5641,7 @@ STATIC SV*        S_swatch_get(pTHX_ SV* swash, UV start, UV span)
 #define PERL_ARGS_ASSERT_SWATCH_GET    \
        assert(swash)
 
-STATIC U8      S_to_lower_latin1(const U8 c, U8 *p, STRLEN *lenp)
+STATIC U8      S_to_lower_latin1(const U8 c, U8 *p, STRLEN *lenp, const char dummy)
                        __attribute__warn_unused_result__;
 
 STATIC char *  S_unexpected_non_continuation_text(pTHX_ const U8 * const s, STRLEN print_len, const STRLEN non_cont_byte_pos, const STRLEN expect_len)
diff --git a/utf8.c b/utf8.c
index bc7cc06..9fe9b03 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -2207,7 +2207,7 @@ Perl_to_uni_title(pTHX_ UV c, U8* p, STRLEN *lenp)
 }
 
 STATIC U8
-S_to_lower_latin1(const U8 c, U8* p, STRLEN *lenp)
+S_to_lower_latin1(const U8 c, U8* p, STRLEN *lenp, const char dummy)
 {
     /* We have the latin1-range values compiled into the core, so just use
      * those, converting the result to UTF-8.  Since the result is always just
@@ -2215,6 +2215,8 @@ S_to_lower_latin1(const U8 c, U8* p, STRLEN *lenp)
 
     U8 converted = toLOWER_LATIN1(c);
 
+    PERL_UNUSED_ARG(dummy);
+
     if (p != NULL) {
        if (NATIVE_BYTE_IS_INVARIANT(converted)) {
            *p = converted;
@@ -2237,7 +2239,7 @@ Perl_to_uni_lower(pTHX_ UV c, U8* p, STRLEN *lenp)
     PERL_ARGS_ASSERT_TO_UNI_LOWER;
 
     if (c < 256) {
-       return to_lower_latin1((U8) c, p, lenp);
+       return to_lower_latin1((U8) c, p, lenp, 0 /* 0 is a dummy arg */ );
     }
 
     uvchr_to_utf8(p, c);
@@ -2746,6 +2748,89 @@ S_check_locale_boundary_crossing(pTHX_ const U8* const p, const UV result, U8* c
     return original;
 }
 
+/* The process for changing the case is essentially the same for the four case
+ * change types, except there are complications for folding.  Otherwise the
+ * difference is only which case to change to.  To make sure that they all do
+ * the same thing, the bodies of the functions are extracted out into the
+ * following two macros.  The functions are written with the same variable
+ * names, and these are known and used inside these macros.  It would be
+ * better, of course, to have inline functions to do it, but since different
+ * macros are called, depending on which case is being changed to, this is not
+ * feasible in C (to khw's knowledge).  Two macros are created so that the fold
+ * function can start with the common start macro, then finish with its special
+ * handling; while the other three cases can just use the common end macro.
+ *
+ * The algorithm is to use the proper (passed in) macro or function to change
+ * the case for code points that are below 256.  The macro is used if using
+ * locale rules for the case change; the function if not.  If the code point is
+ * above 255, it is computed from the input UTF-8, and another macro is called
+ * to do the conversion.  If necessary, the output is converted to UTF-8.  If
+ * using a locale, we have to check that the change did not cross the 255/256
+ * boundary, see check_locale_boundary_crossing() for further details.
+ *
+ * The macros are split with the correct case change for the below-256 case
+ * stored into 'result', and in the middle of an else clause for the above-255
+ * case.  At that point in the 'else', 'result' is not the final result, but is
+ * the input code point calculated from the UTF-8.  The fold code needs to
+ * realize all this and take it from there.
+ *
+ * If you read the two macros as sequential, it's easier to understand what's
+ * going on. */
+#define CASE_CHANGE_BODY_START(locale_flags, LC_L1_change_macro, L1_func,    \
+                               L1_func_extra_param)                          \
+    if (flags & (locale_flags)) {                                            \
+        /* Treat a UTF-8 locale as not being in locale at all */             \
+        if (IN_UTF8_CTYPE_LOCALE) {                                          \
+            flags &= ~(locale_flags);                                        \
+        }                                                                    \
+        else {                                                               \
+            _CHECK_AND_WARN_PROBLEMATIC_LOCALE;                              \
+        }                                                                    \
+    }                                                                        \
+                                                                             \
+    if (UTF8_IS_INVARIANT(*p)) {                                             \
+        if (flags & (locale_flags)) {                                        \
+            result = LC_L1_change_macro(*p);                                 \
+        }                                                                    \
+        else {                                                               \
+            return L1_func(*p, ustrp, lenp, L1_func_extra_param);            \
+        }                                                                    \
+    }                                                                        \
+    else if UTF8_IS_DOWNGRADEABLE_START(*p) {                                \
+        if (flags & (locale_flags)) {                                        \
+            result = LC_L1_change_macro(EIGHT_BIT_UTF8_TO_NATIVE(*p,         \
+                                                                 *(p+1)));   \
+        }                                                                    \
+        else {                                                               \
+            return L1_func(EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1)),             \
+                           ustrp, lenp,  L1_func_extra_param);               \
+        }                                                                    \
+    }                                                                        \
+    else {  /* malformed UTF-8 */                                            \
+        result = valid_utf8_to_uvchr(p, NULL);                               \
+
+#define CASE_CHANGE_BODY_END(locale_flags, change_macro)                     \
+        result = change_macro(result, p, ustrp, lenp);                       \
+                                                                             \
+        if (flags & (locale_flags)) {                                        \
+            result = check_locale_boundary_crossing(p, result, ustrp, lenp); \
+        }                                                                    \
+        return result;                                                       \
+    }                                                                        \
+                                                                             \
+    /* Here, used locale rules.  Convert back to UTF-8 */                    \
+    if (UTF8_IS_INVARIANT(result)) {                                         \
+        *ustrp = (U8) result;                                                \
+        *lenp = 1;                                                           \
+    }                                                                        \
+    else {                                                                   \
+        *ustrp = UTF8_EIGHT_BIT_HI((U8) result);                             \
+        *(ustrp + 1) = UTF8_EIGHT_BIT_LO((U8) result);                       \
+        *lenp = 2;                                                           \
+    }                                                                        \
+                                                                             \
+    return result;
+
 /*
 =for apidoc to_utf8_upper
 
@@ -2764,55 +2849,10 @@ Perl__to_utf8_upper_flags(pTHX_ const U8 *p, U8* ustrp, STRLEN *lenp, bool flags
 
     PERL_ARGS_ASSERT__TO_UTF8_UPPER_FLAGS;
 
-    if (flags) {
-        /* Treat a UTF-8 locale as not being in locale at all */
-        if (IN_UTF8_CTYPE_LOCALE) {
-            flags = FALSE;
-        }
-        else {
-            _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
-        }
-    }
-
-    if (UTF8_IS_INVARIANT(*p)) {
-       if (flags) {
-           result = toUPPER_LC(*p);
-       }
-       else {
-           return _to_upper_title_latin1(*p, ustrp, lenp, 'S');
-       }
-    }
-    else if UTF8_IS_DOWNGRADEABLE_START(*p) {
-       if (flags) {
-            U8 c = EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1));
-           result = toUPPER_LC(c);
-       }
-       else {
-           return _to_upper_title_latin1(EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1)),
-                                         ustrp, lenp, 'S');
-       }
-    }
-    else {  /* UTF-8, ord above 255 */
-       result = CALL_UPPER_CASE(valid_utf8_to_uvchr(p, NULL), p, ustrp, lenp);
-
-       if (flags) {
-           result = check_locale_boundary_crossing(p, result, ustrp, lenp);
-       }
-       return result;
-    }
-
-    /* Here, used locale rules.  Convert back to UTF-8 */
-    if (UTF8_IS_INVARIANT(result)) {
-       *ustrp = (U8) result;
-       *lenp = 1;
-    }
-    else {
-       *ustrp = UTF8_EIGHT_BIT_HI((U8) result);
-       *(ustrp + 1) = UTF8_EIGHT_BIT_LO((U8) result);
-       *lenp = 2;
-    }
-
-    return result;
+    /* ~0 makes anything non-zero in 'flags' mean we are using locale rules */
+    /* 2nd char of uc(U+DF) is 'S' */
+    CASE_CHANGE_BODY_START(~0, toUPPER_LC, _to_upper_title_latin1, 'S');
+    CASE_CHANGE_BODY_END  (~0, CALL_UPPER_CASE);
 }
 
 /*
@@ -2835,55 +2875,9 @@ Perl__to_utf8_title_flags(pTHX_ const U8 *p, U8* ustrp, STRLEN *lenp, bool flags
 
     PERL_ARGS_ASSERT__TO_UTF8_TITLE_FLAGS;
 
-    if (flags) {
-        /* Treat a UTF-8 locale as not being in locale at all */
-        if (IN_UTF8_CTYPE_LOCALE) {
-            flags = FALSE;
-        }
-        else {
-            _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
-        }
-    }
-
-    if (UTF8_IS_INVARIANT(*p)) {
-       if (flags) {
-           result = toUPPER_LC(*p);
-       }
-       else {
-           return _to_upper_title_latin1(*p, ustrp, lenp, 's');
-       }
-    }
-    else if UTF8_IS_DOWNGRADEABLE_START(*p) {
-       if (flags) {
-            U8 c = EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1));
-           result = toUPPER_LC(c);
-       }
-       else {
-           return _to_upper_title_latin1(EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1)),
-                                         ustrp, lenp, 's');
-       }
-    }
-    else {  /* UTF-8, ord above 255 */
-       result = CALL_TITLE_CASE(valid_utf8_to_uvchr(p, NULL), p, ustrp, lenp);
-
-       if (flags) {
-           result = check_locale_boundary_crossing(p, result, ustrp, lenp);
-       }
-       return result;
-    }
-
-    /* Here, used locale rules.  Convert back to UTF-8 */
-    if (UTF8_IS_INVARIANT(result)) {
-       *ustrp = (U8) result;
-       *lenp = 1;
-    }
-    else {
-       *ustrp = UTF8_EIGHT_BIT_HI((U8) result);
-       *(ustrp + 1) = UTF8_EIGHT_BIT_LO((U8) result);
-       *lenp = 2;
-    }
-
-    return result;
+    /* 2nd char of ucfirst(U+DF) is 's' */
+    CASE_CHANGE_BODY_START(~0, toUPPER_LC, _to_upper_title_latin1, 's');
+    CASE_CHANGE_BODY_END  (~0, CALL_TITLE_CASE);
 }
 
 /*
@@ -2905,56 +2899,8 @@ Perl__to_utf8_lower_flags(pTHX_ const U8 *p, U8* ustrp, STRLEN *lenp, bool flags
 
     PERL_ARGS_ASSERT__TO_UTF8_LOWER_FLAGS;
 
-    if (flags) {
-        /* Treat a UTF-8 locale as not being in locale at all */
-        if (IN_UTF8_CTYPE_LOCALE) {
-            flags = FALSE;
-        }
-        else {
-            _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
-        }
-    }
-
-    if (UTF8_IS_INVARIANT(*p)) {
-       if (flags) {
-           result = toLOWER_LC(*p);
-       }
-       else {
-           return to_lower_latin1(*p, ustrp, lenp);
-       }
-    }
-    else if UTF8_IS_DOWNGRADEABLE_START(*p) {
-       if (flags) {
-            U8 c = EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1));
-           result = toLOWER_LC(c);
-       }
-       else {
-           return to_lower_latin1(EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1)),
-                                  ustrp, lenp);
-       }
-    }
-    else {  /* UTF-8, ord above 255 */
-       result = CALL_LOWER_CASE(valid_utf8_to_uvchr(p, NULL), p, ustrp, lenp);
-
-       if (flags) {
-           result = check_locale_boundary_crossing(p, result, ustrp, lenp);
-       }
-
-       return result;
-    }
-
-    /* Here, used locale rules.  Convert back to UTF-8 */
-    if (UTF8_IS_INVARIANT(result)) {
-       *ustrp = (U8) result;
-       *lenp = 1;
-    }
-    else {
-       *ustrp = UTF8_EIGHT_BIT_HI((U8) result);
-       *(ustrp + 1) = UTF8_EIGHT_BIT_LO((U8) result);
-       *lenp = 2;
-    }
-
-    return result;
+    CASE_CHANGE_BODY_START(~0, toLOWER_LC, to_lower_latin1, 0 /* 0 is dummy */)
+    CASE_CHANGE_BODY_END  (~0, CALL_LOWER_CASE)
 }
 
 /*
@@ -2986,38 +2932,10 @@ Perl__to_utf8_fold_flags(pTHX_ const U8 *p, U8* ustrp, STRLEN *lenp, U8 flags)
 
     assert(p != ustrp); /* Otherwise overwrites */
 
-    if (flags & FOLD_FLAGS_LOCALE) {
-        /* Treat a UTF-8 locale as not being in locale at all */
-        if (IN_UTF8_CTYPE_LOCALE) {
-            flags &= ~FOLD_FLAGS_LOCALE;
-        }
-        else {
-            _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
-        }
-    }
+    CASE_CHANGE_BODY_START(FOLD_FLAGS_LOCALE, toFOLD_LC, _to_fold_latin1,
+                 ((flags) & (FOLD_FLAGS_FULL | FOLD_FLAGS_NOMIX_ASCII)));
 
-    if (UTF8_IS_INVARIANT(*p)) {
-       if (flags & FOLD_FLAGS_LOCALE) {
-           result = toFOLD_LC(*p);
-       }
-       else {
-           return _to_fold_latin1(*p, ustrp, lenp,
-                            flags & (FOLD_FLAGS_FULL | FOLD_FLAGS_NOMIX_ASCII));
-       }
-    }
-    else if UTF8_IS_DOWNGRADEABLE_START(*p) {
-       if (flags & FOLD_FLAGS_LOCALE) {
-            U8 c = EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1));
-           result = toFOLD_LC(c);
-       }
-       else {
-           return _to_fold_latin1(EIGHT_BIT_UTF8_TO_NATIVE(*p, *(p+1)),
-                            ustrp, lenp,
-                            flags & (FOLD_FLAGS_FULL | FOLD_FLAGS_NOMIX_ASCII));
-       }
-    }
-    else {  /* UTF-8, ord above 255 */
-       result = CALL_FOLD_CASE(valid_utf8_to_uvchr(p, NULL), p, ustrp, lenp, flags & FOLD_FLAGS_FULL);
+       result = CALL_FOLD_CASE(result, p, ustrp, lenp, flags & FOLD_FLAGS_FULL);
 
        if (flags & FOLD_FLAGS_LOCALE) {