+#define IN_UTF8_CTYPE_LOCALE PL_in_utf8_CTYPE_locale
+
+/* Use foo_LC_uvchr() instead of these for beyond the Latin1 range */
+
+/* For internal core Perl use only: the base macro for defining macros like
+ * isALPHA_LC, which uses the current LC_CTYPE locale. 'c' is the code point
+ * (0-255) to check. In a UTF-8 locale, the result is the same as calling
+ * isFOO_L1(); the 'utf8_locale_classnum' parameter is something like
+ * _CC_UPPER, which gives the class number for doing this. For non-UTF-8
+ * locales, the code to actually do the test this is passed in 'non_utf8'. If
+ * 'c' is above 255, 0 is returned. For accessing the full range of possible
+ * code points under locale rules, use the macros based on _generic_LC_uvchr
+ * instead of this. */
+#define _generic_LC_base(c, utf8_locale_classnum, non_utf8) \
+ (! FITS_IN_8_BITS(c) \
+ ? 0 \
+ : IN_UTF8_CTYPE_LOCALE \
+ ? cBOOL(PL_charclass[(U8) (c)] & _CC_mask(utf8_locale_classnum)) \
+ : cBOOL(non_utf8))
+
+/* For internal core Perl use only: a helper macro for defining macros like
+ * isALPHA_LC. 'c' is the code point (0-255) to check. The function name to
+ * actually do this test is passed in 'non_utf8_func', which is called on 'c',
+ * casting 'c' to the macro _LC_CAST, which should not be parenthesized. See
+ * _generic_LC_base for more info */
+#define _generic_LC(c, utf8_locale_classnum, non_utf8_func) \
+ _generic_LC_base(c,utf8_locale_classnum, \
+ non_utf8_func( (_LC_CAST) (c)))
+
+/* For internal core Perl use only: like _generic_LC, but also returns TRUE if
+ * 'c' is the platform's native underscore character */
+#define _generic_LC_underscore(c,utf8_locale_classnum,non_utf8_func) \
+ _generic_LC_base(c, utf8_locale_classnum, \
+ (non_utf8_func( (_LC_CAST) (c)) \
+ || (char)(c) == '_'))
+
+/* These next three are also for internal core Perl use only: case-change
+ * helper macros */
+#define _generic_toLOWER_LC(c, function, cast) (! FITS_IN_8_BITS(c) \
+ ? (c) \
+ : (IN_UTF8_CTYPE_LOCALE) \
+ ? PL_latin1_lc[ (U8) (c) ] \
+ : (cast)function((cast)(c)))
+
+/* Note that the result can be larger than a byte in a UTF-8 locale. It
+ * returns a single value, so can't adequately return the upper case of LATIN
+ * SMALL LETTER SHARP S in a UTF-8 locale (which should be a string of two
+ * values "SS"); instead it asserts against that under DEBUGGING, and
+ * otherwise returns its input */
+#define _generic_toUPPER_LC(c, function, cast) \
+ (! FITS_IN_8_BITS(c) \
+ ? (c) \
+ : ((! IN_UTF8_CTYPE_LOCALE) \
+ ? (cast)function((cast)(c)) \
+ : ((((U8)(c)) == MICRO_SIGN) \
+ ? GREEK_CAPITAL_LETTER_MU \
+ : ((((U8)(c)) == LATIN_SMALL_LETTER_Y_WITH_DIAERESIS) \
+ ? LATIN_CAPITAL_LETTER_Y_WITH_DIAERESIS \
+ : ((((U8)(c)) == LATIN_SMALL_LETTER_SHARP_S) \
+ ? (__ASSERT_(0) (c)) \
+ : PL_mod_latin1_uc[ (U8) (c) ])))))
+
+/* Note that the result can be larger than a byte in a UTF-8 locale. It
+ * returns a single value, so can't adequately return the fold case of LATIN
+ * SMALL LETTER SHARP S in a UTF-8 locale (which should be a string of two
+ * values "ss"); instead it asserts against that under DEBUGGING, and
+ * otherwise returns its input */
+#define _generic_toFOLD_LC(c, function, cast) \
+ ((UNLIKELY((c) == MICRO_SIGN) && IN_UTF8_CTYPE_LOCALE) \
+ ? GREEK_SMALL_LETTER_MU \
+ : (__ASSERT_(! IN_UTF8_CTYPE_LOCALE \
+ || (c) != LATIN_SMALL_LETTER_SHARP_S) \
+ _generic_toLOWER_LC(c, function, cast)))
+
+/* Use the libc versions for these if available. */
+#if defined(HAS_ISASCII)
+# define isASCII_LC(c) (FITS_IN_8_BITS(c) && isascii( (U8) (c)))
+#else
+# define isASCII_LC(c) isASCII(c)
+#endif
+
+#if defined(HAS_ISBLANK)
+# define isBLANK_LC(c) _generic_LC(c, _CC_BLANK, isblank)
+#else /* Unlike isASCII, varies if in a UTF-8 locale */
+# define isBLANK_LC(c) ((IN_UTF8_CTYPE_LOCALE) ? isBLANK_L1(c) : isBLANK(c))
+#endif
+
+#define _LC_CAST U8
+
+#ifdef WIN32
+ /* The Windows functions don't bother to follow the POSIX standard, which
+ * for example says that something can't both be a printable and a control.
+ * But Windows treats the \t control as a printable, and does such things
+ * as making superscripts into both digits and punctuation. This tames
+ * these flaws by assuming that the definitions of both controls and space
+ * are correct, and then making sure that other definitions don't have
+ * weirdnesses, by making sure that isalnum() isn't also ispunct(), etc.
+ * Not all possible weirdnesses are checked for, just the ones that were
+ * detected on actual Microsoft code pages */
+
+# define isCNTRL_LC(c) _generic_LC(c, _CC_CNTRL, iscntrl)
+# define isSPACE_LC(c) _generic_LC(c, _CC_SPACE, isspace)
+
+# define isALPHA_LC(c) (_generic_LC(c, _CC_ALPHA, isalpha) \
+ && isALPHANUMERIC_LC(c))
+# define isALPHANUMERIC_LC(c) (_generic_LC(c, _CC_ALPHANUMERIC, isalnum) && \
+ ! isPUNCT_LC(c))
+# define isDIGIT_LC(c) (_generic_LC(c, _CC_DIGIT, isdigit) && \
+ isALPHANUMERIC_LC(c))
+# define isGRAPH_LC(c) (_generic_LC(c, _CC_GRAPH, isgraph) && isPRINT_LC(c))
+# define isIDFIRST_LC(c) (((c) == '_') \
+ || (_generic_LC(c, _CC_IDFIRST, isalpha) && ! isPUNCT_LC(c)))
+# define isLOWER_LC(c) (_generic_LC(c, _CC_LOWER, islower) && isALPHA_LC(c))
+# define isPRINT_LC(c) (_generic_LC(c, _CC_PRINT, isprint) && ! isCNTRL_LC(c))
+# define isPUNCT_LC(c) (_generic_LC(c, _CC_PUNCT, ispunct) && ! isCNTRL_LC(c))
+# define isUPPER_LC(c) (_generic_LC(c, _CC_UPPER, isupper) && isALPHA_LC(c))
+# define isWORDCHAR_LC(c) (((c) == '_') || isALPHANUMERIC_LC(c))
+# define isXDIGIT_LC(c) (_generic_LC(c, _CC_XDIGIT, isxdigit) \
+ && isALPHANUMERIC_LC(c))
+
+# define toLOWER_LC(c) _generic_toLOWER_LC((c), tolower, U8)
+# define toUPPER_LC(c) _generic_toUPPER_LC((c), toupper, U8)
+# define toFOLD_LC(c) _generic_toFOLD_LC((c), tolower, U8)
+
+#elif defined(CTYPE256) || (!defined(isascii) && !defined(HAS_ISASCII))