* in such scope than if not. However, various libc functions called by Perl
* are affected by the LC_NUMERIC category, so there are macros in perl.h that
* are used to toggle between the current locale and the C locale depending on
- * the desired behavior of those functions at the moment.
+ * the desired behavior of those functions at the moment. And, LC_MESSAGES is
+ * switched to the C locale for outputting the message unless within the scope
+ * of 'use locale'.
*/
#include "EXTERN.h"
else
PL_numeric_radix_sv = NULL;
- DEBUG_L(PerlIO_printf(Perl_debug_log, "Locale radix is %s\n",
+ DEBUG_L(PerlIO_printf(Perl_debug_log, "Locale radix is '%s', ?UTF-8=%d\n",
(PL_numeric_radix_sv)
- ? lc->decimal_point
- : "NULL"));
+ ? SvPVX(PL_numeric_radix_sv)
+ : "NULL",
+ (PL_numeric_radix_sv)
+ ? cBOOL(SvUTF8(PL_numeric_radix_sv))
+ : 0));
# endif /* HAS_LOCALECONV */
#endif /* USE_LOCALE_NUMERIC */
}
save_newnum = stdize_locale(savepv(newnum));
+
+ PL_numeric_standard = isNAME_C_OR_POSIX(save_newnum);
+ PL_numeric_local = TRUE;
+
if (! PL_numeric_name || strNE(PL_numeric_name, save_newnum)) {
Safefree(PL_numeric_name);
PL_numeric_name = save_newnum;
}
-
- PL_numeric_standard = isNAME_C_OR_POSIX(save_newnum);
- PL_numeric_local = TRUE;
+ else {
+ Safefree(save_newnum);
+ }
/* Keep LC_NUMERIC in the C locale. This is for XS modules, so they don't
* have to worry about the radix being a non-dot. (Core operations that
#ifdef MB_CUR_MAX
/* We only handle single-byte locales (outside of UTF-8 ones; so if
- * this locale requires than one byte, there are going to be
+ * this locale requires more than one byte, there are going to be
* problems. */
if (check_for_problems && MB_CUR_MAX > 1
* Any code changing the locale (outside this file) should use
* POSIX::setlocale, which calls this function. Therefore this function
* should be called directly only from this file and from
- * POSIX::setlocale() */
+ * POSIX::setlocale().
+ *
+ * The design of locale collation is that every locale change is given an
+ * index 'PL_collation_ix'. The first time a string particpates in an
+ * operation that requires collation while locale collation is active, it
+ * is given PERL_MAGIC_collxfrm magic (via sv_collxfrm_flags()). That
+ * magic includes the collation index, and the transformation of the string
+ * by strxfrm(), q.v. That transformation is used when doing comparisons,
+ * instead of the string itself. If a string changes, the magic is
+ * cleared. The next time the locale changes, the index is incremented,
+ * and so we know during a comparison that the transformation is not
+ * necessarily still valid, and so is recomputed. Note that if the locale
+ * changes enough times, the index could wrap (a U32), and it is possible
+ * that a transformation would improperly be considered valid, leading to
+ * an unlikely bug */
if (! newcoll) {
if (PL_collation_name) {
PL_collation_name = NULL;
}
PL_collation_standard = TRUE;
+ is_standard_collation:
PL_collxfrm_base = 0;
PL_collxfrm_mult = 2;
+ PL_in_utf8_COLLATE_locale = FALSE;
+ *PL_strxfrm_min_char = '\0';
return;
}
+ /* If this is not the same locale as currently, set the new one up */
if (! PL_collation_name || strNE(PL_collation_name, newcoll)) {
++PL_collation_ix;
Safefree(PL_collation_name);
PL_collation_name = stdize_locale(savepv(newcoll));
PL_collation_standard = isNAME_C_OR_POSIX(newcoll);
+ if (PL_collation_standard) {
+ goto is_standard_collation;
+ }
+
+ PL_in_utf8_COLLATE_locale = _is_cur_LC_category_utf8(LC_COLLATE);
+ *PL_strxfrm_min_char = '\0';
+
+ /* A locale collation definition includes primary, secondary, tertiary,
+ * etc. weights for each character. To sort, the primary weights are
+ * used, and only if they compare equal, then the secondary weights are
+ * used, and only if they compare equal, then the tertiary, etc.
+ *
+ * strxfrm() works by taking the input string, say ABC, and creating an
+ * output transformed string consisting of first the primary weights,
+ * A¹B¹C¹ followed by the secondary ones, A²B²C²; and then the
+ * tertiary, etc, yielding A¹B¹C¹ A²B²C² A³B³C³ .... Some characters
+ * may not have weights at every level. In our example, let's say B
+ * doesn't have a tertiary weight, and A doesn't have a secondary
+ * weight. The constructed string is then going to be
+ * A¹B¹C¹ B²C² A³C³ ....
+ * This has the desired effect that strcmp() will look at the secondary
+ * or tertiary weights only if the strings compare equal at all higher
+ * priority weights. The spaces shown here, like in
+ * "A¹B¹C¹ * A²B²C² "
+ * are not just for readability. In the general case, these must
+ * actually be bytes, which we will call here 'separator weights'; and
+ * they must be smaller than any other weight value, but since these
+ * are C strings, only the terminating one can be a NUL (some
+ * implementations may include a non-NUL separator weight just before
+ * the NUL). Implementations tend to reserve 01 for the separator
+ * weights. They are needed so that a shorter string's secondary
+ * weights won't be misconstrued as primary weights of a longer string,
+ * etc. By making them smaller than any other weight, the shorter
+ * string will sort first. (Actually, if all secondary weights are
+ * smaller than all primary ones, there is no need for a separator
+ * weight between those two levels, etc.)
+ *
+ * The length of the transformed string is roughly a linear function of
+ * the input string. It's not exactly linear because some characters
+ * don't have weights at all levels. When we call strxfrm() we have to
+ * allocate some memory to hold the transformed string. The
+ * calculations below try to find coefficients 'm' and 'b' for this
+ * locale so that m*x + b equals how much space we need, given the size
+ * of the input string in 'x'. If we calculate too small, we increase
+ * the size as needed, and call strxfrm() again, but it is better to
+ * get it right the first time to avoid wasted expensive string
+ * transformations. */
{
/* 2: at most so many chars ('a', 'b'). */
* differences. First, it handles embedded NULs. Second, it allocates
* a bit more memory than needed for the transformed data itself.
* The real transformed data begins at offset sizeof(collationix).
+ * *xlen is set to the length of that, and doesn't include the collation index
+ * size.
* Please see sv_collxfrm() to see how this is used.
*/
char *
-Perl_mem_collxfrm(pTHX_ const char *s, STRLEN len, STRLEN *xlen)
+Perl_mem_collxfrm(pTHX_ const char *input_string,
+ STRLEN len,
+ STRLEN *xlen
+ )
{
+ char * s = (char *) input_string;
+ STRLEN s_strlen = strlen(input_string);
char *xbuf;
- STRLEN xAlloc, xin, xout; /* xalloc is a reserved word in VC */
+ STRLEN xAlloc, xout; /* xalloc is a reserved word in VC */
PERL_ARGS_ASSERT_MEM_COLLXFRM;
- /* the first sizeof(collationix) bytes are used by sv_collxfrm(). */
- /* the +1 is for the terminating NUL. */
+ /* Replace any embedded NULs with the control that sorts before any others.
+ * This will give as good as possible results on strings that don't
+ * otherwise contain that character, but otherwise there may be
+ * less-than-perfect results with that character and NUL. This is
+ * unavoidable unless we replace strxfrm with our own implementation.
+ *
+ * XXX This code may be overkill. khw wrote it before realizing that if
+ * you change a NUL into some other character, that that may change the
+ * strxfrm results if that character is part of a sequence with other
+ * characters for weight calculations. To minimize the chances of this,
+ * now the replacement is restricted to another control (likely to be
+ * \001). But the full generality has been retained.
+ *
+ * This is one of the few places in the perl core, where we can use
+ * standard functions like strlen() and strcat(). It's because we're
+ * looking for NULs. */
+ if (s_strlen < len) {
+ char * e = s + len;
+ char * sans_nuls;
+ STRLEN cur_min_char_len;
+
+ /* If we don't know what control character sorts lowest for this
+ * locale, find it */
+ if (*PL_strxfrm_min_char == '\0') {
+ int j;
+ char * cur_min_x = NULL; /* Cur cp's xfrm, (except it also
+ includes the collation index
+ prefixed. */
+
+ /* Look through all legal code points (NUL isn't) */
+ for (j = 1; j < 256; j++) {
+ char * x; /* j's xfrm plus collation index */
+ STRLEN x_len; /* length of 'x' */
+ STRLEN trial_len = 1;
+
+ /* Create a 1 byte string of the current code point, but with
+ * room to be 2 bytes */
+ char cur_source[] = { (char) j, '\0' , '\0' };
+
+ if (PL_in_utf8_COLLATE_locale) {
+ if (! isCNTRL_L1(j)) {
+ continue;
+ }
+
+ /* If needs to be 2 bytes, find them */
+ if (! UVCHR_IS_INVARIANT(j)) {
+ continue; /* Can't handle variants yet */
+ }
+ }
+ else if (! isCNTRL_LC(j)) {
+ continue;
+ }
+
+ /* Then transform it */
+ x = mem_collxfrm(cur_source, trial_len, &x_len);
+ /* If something went wrong (which it shouldn't), just
+ * ignore this code point */
+ if ( x_len == 0
+ || strlen(x + sizeof(PL_collation_ix)) < x_len)
+ {
+ continue;
+ }
+
+ /* If this character's transformation is lower than
+ * the current lowest, this one becomes the lowest */
+ if ( cur_min_x == NULL
+ || strLT(x + sizeof(PL_collation_ix),
+ cur_min_x + sizeof(PL_collation_ix)))
+ {
+ strcpy(PL_strxfrm_min_char, cur_source);
+ cur_min_x = x;
+ }
+ else {
+ Safefree(x);
+ }
+ } /* end of loop through all bytes */
+
+ /* Unlikely, but possible, if there aren't any controls in the
+ * locale, arbitrarily use \001 */
+ if (cur_min_x == NULL) {
+ STRLEN x_len; /* temporary */
+ cur_min_x = mem_collxfrm("\001", 1, &x_len);
+ /* cur_min_cp was already initialized to 1 */
+ }
+
+ Safefree(cur_min_x);
+ }
+
+ /* The worst case length for the replaced string would be if every
+ * character in it is NUL. Multiply that by the length of each
+ * replacement, and allow for a trailing NUL */
+ cur_min_char_len = strlen(PL_strxfrm_min_char);
+ Newx(sans_nuls, (len * cur_min_char_len) + 1, char);
+ *sans_nuls = '\0';
+
+
+ /* Replace each NUL with the lowest collating control. Loop until have
+ * exhausted all the NULs */
+ while (s + s_strlen < e) {
+ strcat(sans_nuls, s);
+
+ /* Do the actual replacement */
+ strcat(sans_nuls, PL_strxfrm_min_char);
+
+ /* Move past the input NUL */
+ s += s_strlen + 1;
+ s_strlen = strlen(s);
+ }
+
+ /* And add anything that trails the final NUL */
+ strcat(sans_nuls, s);
+
+ /* Switch so below we transform this modified string */
+ s = sans_nuls;
+ len = strlen(s);
+ }
+
+ /* The first element in the output is the collation id, used by
+ * sv_collxfrm(); then comes the space for the transformed string. The
+ * equation should give us a good estimate as to how much is needed */
xAlloc = sizeof(PL_collation_ix) + PL_collxfrm_base + (PL_collxfrm_mult * len) + 1;
Newx(xbuf, xAlloc, char);
- if (! xbuf)
+ if (UNLIKELY(! xbuf))
goto bad;
+ /* Store the collation id */
*(U32*)xbuf = PL_collation_ix;
xout = sizeof(PL_collation_ix);
- for (xin = 0; xin < len; ) {
- Size_t xused;
-
- for (;;) {
- xused = strxfrm(xbuf + xout, s + xin, xAlloc - xout);
- if (xused >= PERL_INT_MAX)
- goto bad;
- if ((STRLEN)xused < xAlloc - xout)
- break;
- xAlloc = (2 * xAlloc) + 1;
- Renew(xbuf, xAlloc, char);
- if (! xbuf)
- goto bad;
- }
- xin += strlen(s + xin) + 1;
- xout += xused;
+ /* Then the transformation of the input. We loop until successful, or we
+ * give up */
+ for (;;) {
+ STRLEN xused = strxfrm(xbuf + xout, s, xAlloc - xout);
+
+ /* If the transformed string occupies less space than we told strxfrm()
+ * was available, it means it successfully transformed the whole
+ * string. */
+ if (xused < xAlloc - xout) {
+ xout += xused;
+ break;
+ }
- /* Embedded NULs are understood but silently skipped
- * because they make no sense in locale collation. */
+ if (UNLIKELY(xused >= PERL_INT_MAX))
+ goto bad;
+
+ /* Otherwise it should be that the transformation stopped in the middle
+ * because it ran out of space. Malloc more, and try again. */
+ xAlloc = (2 * xAlloc) + 1;
+ Renew(xbuf, xAlloc, char);
+ if (UNLIKELY(! xbuf))
+ goto bad;
}
- xbuf[xout] = '\0';
*xlen = xout - sizeof(PL_collation_ix);
+
+ /* Free up unneeded space; retain ehough for trailing NUL */
+ Renew(xbuf, xout + 1, char);
+
+ if (s != input_string) {
+ Safefree(s);
+ }
+
return xbuf;
bad:
Safefree(xbuf);
+ if (s != input_string) {
+ Safefree(s);
+ }
*xlen = 0;
return NULL;
}
char *
Perl_my_strerror(pTHX_ const int errnum) {
+ dVAR;
/* Uses C locale for the error text unless within scope of 'use locale' for
* LC_MESSAGES */
#ifdef USE_LOCALE_MESSAGES
if (! IN_LC(LC_MESSAGES)) {
- char * save_locale = setlocale(LC_MESSAGES, NULL);
+ char * save_locale;
+
+ /* We have a critical section to prevent another thread from changing
+ * the locale out from under us (or zapping the buffer returned from
+ * setlocale() ) */
+ LOCALE_LOCK;
+
+ save_locale = setlocale(LC_MESSAGES, NULL);
if (! isNAME_C_OR_POSIX(save_locale)) {
char *errstr;
setlocale(LC_MESSAGES, save_locale);
Safefree(save_locale);
+
+ LOCALE_UNLOCK;
+
return errstr;
}
+
+ LOCALE_UNLOCK;
}
#endif
* be overwritten by the next call, so this should be used just to
* formulate a string to immediately print or savepv() on. */
- static char ret[128] = "";
+ /* initialise to a non-null value to keep it out of BSS and so keep
+ * -DPERL_GLOBAL_STRUCT_PRIVATE happy */
+ static char ret[128] = "x";
my_strlcpy(ret, "setlocale(", sizeof(ret));