FAQ
In perl.git, the branch blead has been updated

<http://perl5.git.perl.org/perl.git/commitdiff/17f41037d4817b6618a903e12aa1377ae078f66a?hp=79f120c89a6e123770f65fd49893ef0b379cd922>

- Log -----------------------------------------------------------------
commit 17f41037d4817b6618a903e12aa1377ae078f66a
Author: Karl Williamson <khw@cpan.org>
Date: Fri May 13 11:32:44 2016 -0600

     locale.c: Make locale collation predictions adaptive

     We try to avoid calling strxfrm() more than needed by predicting its
     needed buffer size. This generally works because the size of the
     transformed string is roughly linear with the size of the input string.
     But the key word here is "roughly". This commit changes things, so that
     when we guess low, we change the coefficients in the equation to guess
     higher the next time.

M locale.c

commit 6ddd902ce7b4052f3d48e6dd638d08d705d1ee16
Author: Karl Williamson <khw@cpan.org>
Date: Tue Apr 12 14:28:57 2016 -0600

     locale.c: Not so aggressive collation memory use guess

     On platforms where strxfrm() is not well-behaved, and it fails because
     it needs a larger buffer, prior to this commit, the size was doubled
     before trying again. This could require a lot of memory on large
     inputs. I'm uncomfortable with such a big delta on very large strings.
     This commit changes it so it is not so aggressive. Note that this now
     only gets called on platforms whose strxfrm() is not well behaved, and I
     think the size prediction is better due to a recent commit, and there
     isn't really much of a downside in not gobbling up memory so fast.

M locale.c

commit 58eebef2d34f0f429943dd0ab07bead821d9daac
Author: Karl Williamson <khw@cpan.org>
Date: Wed May 18 13:18:01 2016 -0600

     locale.c: Add some debugging statements

M locale.c

commit 55e5378d6579f68f700c44b38ccbeecf00493847
Author: Karl Williamson <khw@cpan.org>
Date: Wed May 18 13:17:25 2016 -0600

     locale.c: Minor cleanup

     This replaces an expression with what I think is an easier to understand
     macro, and eliminates a couple of temporary variables that just
     cluttered things up.

M locale.c

commit 2fcc0ca9e3b59e6224af067db588ef3249137029
Author: Karl Williamson <khw@cpan.org>
Date: Sat May 14 18:23:02 2016 -0600

     locale.c: Fix some debugging so will output during init

     Because the command line options are currently parsed after the locale
     initialization is done, an environment variable is read to allow
     debugging of the function that is called to do the initialization.
     However, any functions that it calls, prior to this commit, were unaware
     of this and so did not output debugging. This commit fixes most of
     them.

M locale.c

commit 78d57975d5aa732ef6dfba842558183e8880539c
Author: Karl Williamson <khw@cpan.org>
Date: Tue Apr 12 12:49:36 2016 -0600

     mv function from locale.c to mathoms.c

     The previous commit causes this function being moved to be just a
     wrapper not called in core. Just in case someone is calling it, it is
     retained, but moved to mathoms.c

M embed.fnc
M embed.h
M locale.c
M mathoms.c
M proto.h

commit a4a439fb9cd74c575855119abb55dc091955bdf4
Author: Karl Williamson <khw@cpan.org>
Date: Tue May 17 20:50:55 2016 -0600

     Do better locale collation in UTF-8 locales

     On some platforms, the libc strxfrm() works reasonably well on UTF-8
     locales, giving a default collation ordering. It will assume that every
     string passed to it is in UTF-8. This commit changes Perl to make sure
     that strxfrm's expectations are met.

     Likewise under a non-UTF-8 locale, strxfrm is expecting a non-UTF-8
     string. And this commit makes sure of that as well.

     So, simply meeting strxfrm's expectations allows Perl to start
     supporting default collation in UTF-8 locales, and fixes it to work on
     single-byte locales with UTF-8 input. (Unicode::Collate provides
     tailorable functionality and is portable to platforms where strxfrm
     isn't as intelligent, but is a much more heavy-weight solution that may
     not be needed for particular applications.)

     There is a problem in non-UTF-8 locales if the passed string contains
     code points representable only in UTF-8. This commit causes them to be
     changed, before being passed to strxfrm, into the highest collating
     character in the locale that doesn't require UTF-8. They then will sort
     the same as that character, which means after all other characters in
     the locale but that one. In strings that don't have that character,
     this will generally provide exactly correct operation. There still is a
     problem, if that character, in the given locale, combines with adjacent
     characters to form a specially weighted sequence. Then, the change of
     these above-255 code points into that character can skew the results.
     See the commit message for 6696cfa7cc3a0e1e0eab29a11ac131e6f5a3469e for
     more on this. But it is really an illegal situation to have above-255
     code points in a single-byte locale, so this behavior is a reasonable
     degradation when given illegal input. If two transformed strings
     compare exactly equal, Perl already uses the un-transformed versions to
     break ties, and there, these faked-up strings will collate so the
     above-255 code points sort after everything else, and in code point
     order amongst themselves.

M embed.fnc
M embed.h
M embedvar.h
M intrpvar.h
M lib/locale.t
M locale.c
M pod/perldelta.pod
M pod/perllocale.pod
M proto.h
M sv.c

commit ff52fcf1dae90deb49f680d7cdbf78a04458ac47
Author: Karl Williamson <khw@cpan.org>
Date: Tue Apr 12 13:51:48 2016 -0600

     perllocale: Change headings so two aren't identical

     Two html anchors in this pod were identical, which isn't a problem
     unless you try to link to one of them, as the next commit does

M pod/perllocale.pod
-----------------------------------------------------------------------

Summary of changes:
  embed.fnc | 8 +-
  embed.h | 7 +
  embedvar.h | 1 +
  intrpvar.h | 1 +
  lib/locale.t | 54 ++++++-
  locale.c | 437 +++++++++++++++++++++++++++++++++++++++++++++--------
  mathoms.c | 12 ++
  pod/perldelta.pod | 10 ++
  pod/perllocale.pod | 58 ++++---
  proto.h | 9 ++
  sv.c | 3 +-
  11 files changed, 515 insertions(+), 85 deletions(-)

diff --git a/embed.fnc b/embed.fnc
index 85166eb..967fdfc 100644
--- a/embed.fnc
+++ b/embed.fnc
@@ -908,8 +908,14 @@ pod |SV* |magic_methcall |NN SV *sv|NN const MAGIC *mg \
  Ap |I32 * |markstack_grow
  #if defined(USE_LOCALE_COLLATE)
  p |int |magic_setcollxfrm|NN SV* sv|NN MAGIC* mg
+pb |char* |mem_collxfrm |NN const char* input_string|STRLEN len|NN STRLEN* xlen
  : Defined in locale.c, used only in sv.c
-p |char* |mem_collxfrm |NN const char* input_string|STRLEN len|NN STRLEN* xlen
+# if defined(PERL_IN_LOCALE_C) || defined(PERL_IN_SV_C) || defined(PERL_IN_MATHOMS_C)
+pM |char* |_mem_collxfrm |NN const char* input_string \
+ |STRLEN len \
+ |NN STRLEN* xlen \
+ |bool utf8
+# endif
  #endif
  Afpd |SV* |mess |NN const char* pat|...
  Apd |SV* |mess_sv |NN SV* basemsg|bool consume
diff --git a/embed.h b/embed.h
index 6071c31..f37b76b 100644
--- a/embed.h
+++ b/embed.h
@@ -1559,6 +1559,11 @@
  #define share_hek_flags(a,b,c,d) S_share_hek_flags(aTHX_ a,b,c,d)
  #define unshare_hek_or_pvn(a,b,c,d) S_unshare_hek_or_pvn(aTHX_ a,b,c,d)
  # endif
+# if defined(PERL_IN_LOCALE_C) || defined(PERL_IN_SV_C) || defined(PERL_IN_MATHOMS_C)
+# if defined(USE_LOCALE_COLLATE)
+#define _mem_collxfrm(a,b,c,d) Perl__mem_collxfrm(aTHX_ a,b,c,d)
+# endif
+# endif
  # if defined(PERL_IN_MALLOC_C)
  #define adjust_size_and_find_bucket S_adjust_size_and_find_bucket
  # endif
@@ -1844,7 +1849,9 @@
  # endif
  # if defined(USE_LOCALE_COLLATE)
  #define magic_setcollxfrm(a,b) Perl_magic_setcollxfrm(aTHX_ a,b)
+#ifndef NO_MATHOMS
  #define mem_collxfrm(a,b,c) Perl_mem_collxfrm(aTHX_ a,b,c)
+#endif
  # endif
  # if defined(USE_PERLIO)
  #define PerlIO_restore_errno(a) Perl_PerlIO_restore_errno(aTHX_ a)
diff --git a/embedvar.h b/embedvar.h
index 6738368..c2831d6 100644
--- a/embedvar.h
+++ b/embedvar.h
@@ -310,6 +310,7 @@
  #define PL_stdingv (vTHX->Istdingv)
  #define PL_strtab (vTHX->Istrtab)
  #define PL_strxfrm_is_behaved (vTHX->Istrxfrm_is_behaved)
+#define PL_strxfrm_max_cp (vTHX->Istrxfrm_max_cp)
  #define PL_strxfrm_min_char (vTHX->Istrxfrm_min_char)
  #define PL_sub_generation (vTHX->Isub_generation)
  #define PL_subline (vTHX->Isubline)
diff --git a/intrpvar.h b/intrpvar.h
index f540a9d..ca1bb71 100644
--- a/intrpvar.h
+++ b/intrpvar.h
@@ -567,6 +567,7 @@ PERLVARI(I, collation_ix, U32, 0) /* Collation generation index */
  PERLVARA(I, strxfrm_min_char, 3, char)
  PERLVARI(I, strxfrm_is_behaved, bool, TRUE)
                              /* Assume until proven otherwise that it works */
+PERLVARI(I, strxfrm_max_cp, U8, 0) /* Highest collating cp in locale */
  PERLVARI(I, collation_standard, bool, TRUE)
       /* Assume simple collation */
  #endif /* USE_LOCALE_COLLATE */
diff --git a/lib/locale.t b/lib/locale.t
index ce0c987..9afa9a4 100644
--- a/lib/locale.t
+++ b/lib/locale.t
@@ -1752,16 +1752,66 @@ foreach my $Locale (@Locale) {

          ++$locales_test_number;
          $test_names{$locales_test_number}
- = 'TODO Verify that strings with embedded NUL collate';
+ = 'Verify that strings with embedded NUL collate';
          my $ok = "a\0a\0a" lt "a\001a\001a";
          report_result($Locale, $locales_test_number, $ok);

          ++$locales_test_number;
          $test_names{$locales_test_number}
- = 'TODO Verify that strings with embedded NUL and '
+ = 'Verify that strings with embedded NUL and '
                              . 'extra trailing NUL collate';
          $ok = "a\0a\0" lt "a\001a\001";
          report_result($Locale, $locales_test_number, $ok);
+
+ ++$locales_test_number;
+ $test_names{$locales_test_number}
+ = "Skip in non-UTF-8 locales; otherwise verify that UTF8ness "
+ . "doesn't matter with collation";
+ if (! $is_utf8_locale) {
+ report_result($Locale, $locales_test_number, 1);
+ }
+ else {
+
+ # khw can't think of anything better. Start with a string that is
+ # higher than its UTF-8 representation in both EBCDIC and ASCII
+ my $string = chr utf8::unicode_to_native(0xff);
+ my $utf8_string = $string;
+ utf8::upgrade($utf8_string);
+
+ # 8 should be lt 9 in all locales (except ones that aren't
+ # ASCII-based, which might fail this)
+ $ok = ("a${string}8") lt ("a${utf8_string}9");
+ report_result($Locale, $locales_test_number, $ok);
+ }
+
+ ++$locales_test_number;
+ $test_names{$locales_test_number}
+ = "Skip in UTF-8 locales; otherwise verify that single byte "
+ . "collates before 0x100 and above";
+ if ($is_utf8_locale) {
+ report_result($Locale, $locales_test_number, 1);
+ }
+ else {
+ my $max_collating = chr 0; # Find byte that collates highest
+ for my $i (0 .. 255) {
+ my $char = chr $i;
+ $max_collating = $char if $char gt $max_collating;
+ }
+ $ok = $max_collating lt chr 0x100;
+ report_result($Locale, $locales_test_number, $ok);
+ }
+
+ ++$locales_test_number;
+ $test_names{$locales_test_number}
+ = "Skip in UTF-8 locales; otherwise verify that 0x100 and "
+ . "above collate in code point order";
+ if ($is_utf8_locale) {
+ report_result($Locale, $locales_test_number, 1);
+ }
+ else {
+ $ok = chr 0x100 lt chr 0x101;
+ report_result($Locale, $locales_test_number, $ok);
+ }
      }

      my $ok1;
diff --git a/locale.c b/locale.c
index 23c54e6..97cc735 100644
--- a/locale.c
+++ b/locale.c
@@ -44,6 +44,13 @@

  #include "reentr.h"

+/* If the environment says to, we can output debugging information during
+ * initialization. This is done before option parsing, and before any thread
+ * creation, so can be a file-level static */
+#ifdef DEBUGGING
+static bool debug_initialization = FALSE;
+#endif
+
  #ifdef USE_LOCALE

  /*
@@ -119,13 +126,17 @@ Perl_set_numeric_radix(pTHX)
      else
   PL_numeric_radix_sv = NULL;

- DEBUG_L(PerlIO_printf(Perl_debug_log, "Locale radix is '%s', ?UTF-8=%d\n",
+#ifdef DEBUGGING
+ if (DEBUG_L_TEST || debug_initialization) {
+ PerlIO_printf(Perl_debug_log, "Locale radix is '%s', ?UTF-8=%d\n",
                                            (PL_numeric_radix_sv)
                                             ? SvPVX(PL_numeric_radix_sv)
                                             : "NULL",
                                            (PL_numeric_radix_sv)
                                             ? cBOOL(SvUTF8(PL_numeric_radix_sv))
- : 0));
+ : 0);
+ }
+#endif

  # endif /* HAS_LOCALECONV */
  #endif /* USE_LOCALE_NUMERIC */
@@ -230,8 +241,12 @@ Perl_set_numeric_standard(pTHX)
      PL_numeric_standard = TRUE;
      PL_numeric_local = isNAME_C_OR_POSIX(PL_numeric_name);
      set_numeric_radix();
- DEBUG_L(PerlIO_printf(Perl_debug_log,
- "Underlying LC_NUMERIC locale now is C\n"));
+#ifdef DEBUGGING
+ if (DEBUG_L_TEST || debug_initialization) {
+ PerlIO_printf(Perl_debug_log,
+ "Underlying LC_NUMERIC locale now is C\n");
+ }
+#endif

  #endif /* USE_LOCALE_NUMERIC */
  }
@@ -250,9 +265,13 @@ Perl_set_numeric_local(pTHX)
      PL_numeric_standard = isNAME_C_OR_POSIX(PL_numeric_name);
      PL_numeric_local = TRUE;
      set_numeric_radix();
- DEBUG_L(PerlIO_printf(Perl_debug_log,
+#ifdef DEBUGGING
+ if (DEBUG_L_TEST || debug_initialization) {
+ PerlIO_printf(Perl_debug_log,
                            "Underlying LC_NUMERIC locale now is %s\n",
- PL_numeric_name));
+ PL_numeric_name);
+ }
+#endif

  #endif /* USE_LOCALE_NUMERIC */
  }
@@ -487,6 +506,7 @@ Perl_new_collate(pTHX_ const char *newcoll)
   PL_collxfrm_mult = 2;
          PL_in_utf8_COLLATE_locale = FALSE;
          *PL_strxfrm_min_char = '\0';
+ PL_strxfrm_max_cp = 0;
   return;
      }

@@ -502,6 +522,7 @@ Perl_new_collate(pTHX_ const char *newcoll)

          PL_in_utf8_COLLATE_locale = _is_cur_LC_category_utf8(LC_COLLATE);
          *PL_strxfrm_min_char = '\0';
+ PL_strxfrm_max_cp = 0;

          /* A locale collation definition includes primary, secondary, tertiary,
           * etc. weights for each character. To sort, the primary weights are
@@ -564,7 +585,7 @@ Perl_new_collate(pTHX_ const char *newcoll)
              char * x_shorter; /* We also transform a substring of 'longer' */
              Size_t x_len_shorter;

- /* mem_collxfrm() is used get the transformation (though here we
+ /* _mem_collxfrm() is used get the transformation (though here we
               * are interested only in its length). It is used because it has
               * the intelligence to handle all cases, but to work, it needs some
               * values of 'm' and 'b' to get it started. For the purposes of
@@ -576,9 +597,18 @@ Perl_new_collate(pTHX_ const char *newcoll)
              PL_collxfrm_mult = 5 * sizeof(UV);

              /* Find out how long the transformation really is */
- x_longer = mem_collxfrm(longer,
- sizeof(longer) - 1,
- &x_len_longer);
+ x_longer = _mem_collxfrm(longer,
+ sizeof(longer) - 1,
+ &x_len_longer,
+
+ /* We avoid converting to UTF-8 in the
+ * called function by telling it the
+ * string is in UTF-8 if the locale is a
+ * UTF-8 one. Since the string passed
+ * here is invariant under UTF-8, we can
+ * claim it's UTF-8 even though it isn't.
+ * */
+ PL_in_utf8_COLLATE_locale);
              Safefree(x_longer);

              /* Find out how long the transformation of a substring of 'longer'
@@ -586,9 +616,10 @@ Perl_new_collate(pTHX_ const char *newcoll)
               * sufficient to calculate 'm' and 'b'. The substring is all of
               * 'longer' except the first character. This minimizes the chances
               * of being swayed by outliers */
- x_shorter = mem_collxfrm(longer + 1,
+ x_shorter = _mem_collxfrm(longer + 1,
                                        sizeof(longer) - 2,
- &x_len_shorter);
+ &x_len_shorter,
+ PL_in_utf8_COLLATE_locale);
              Safefree(x_shorter);

              /* If the results are nonsensical for this simple test, the whole
@@ -635,6 +666,19 @@ Perl_new_collate(pTHX_ const char *newcoll)
                  /* Add 1 for the trailing NUL */
                  PL_collxfrm_base = base + 1;
              }
+
+#ifdef DEBUGGING
+ if (DEBUG_L_TEST || debug_initialization) {
+ PerlIO_printf(Perl_debug_log,
+ "%s:%d: ?UTF-8 locale=%d; x_len_shorter=%"UVuf", "
+ "x_len_longer=%"UVuf","
+ " collate multipler=%"UVuf", collate base=%"UVuf"\n",
+ __FILE__, __LINE__,
+ PL_in_utf8_COLLATE_locale,
+ x_len_shorter, x_len_longer,
+ PL_collxfrm_mult, PL_collxfrm_base);
+ }
+#endif
   }
      }

@@ -872,24 +916,6 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
      const char * const setlocale_init = (PerlEnv_getenv("PERL_SKIP_LOCALE_INIT"))
                                          ? NULL
                                          : "";
-#ifdef DEBUGGING
- const bool debug = (PerlEnv_getenv("PERL_DEBUG_LOCALE_INIT"))
- ? TRUE
- : FALSE;
-# define DEBUG_LOCALE_INIT(category, locale, result) \
- STMT_START { \
- if (debug) { \
- PerlIO_printf(Perl_debug_log, \
- "%s:%d: %s\n", \
- __FILE__, __LINE__, \
- _setlocale_debug_string(category, \
- locale, \
- result)); \
- } \
- } STMT_END
-#else
-# define DEBUG_LOCALE_INIT(a,b,c)
-#endif
      const char* trial_locales[5]; /* 5 = 1 each for "", LC_ALL, LANG, "", C */
      unsigned int trial_locales_count;
      const char * const lc_all = savepv(PerlEnv_getenv("LC_ALL"));
@@ -920,6 +946,25 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
      const char *system_default_locale = NULL;
  #endif

+#ifdef DEBUGGING
+ debug_initialization = (PerlEnv_getenv("PERL_DEBUG_LOCALE_INIT"))
+ ? TRUE
+ : FALSE;
+# define DEBUG_LOCALE_INIT(category, locale, result) \
+ STMT_START { \
+ if (debug_initialization) { \
+ PerlIO_printf(Perl_debug_log, \
+ "%s:%d: %s\n", \
+ __FILE__, __LINE__, \
+ _setlocale_debug_string(category, \
+ locale, \
+ result)); \
+ } \
+ } STMT_END
+#else
+# define DEBUG_LOCALE_INIT(a,b,c)
+#endif
+
  #ifndef LOCALE_ENVIRON_REQUIRED
      PERL_UNUSED_VAR(done);
      PERL_UNUSED_VAR(locale_param);
@@ -1358,35 +1403,46 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
      PERL_UNUSED_ARG(printwarn);
  #endif /* USE_LOCALE */

+#ifdef DEBUGGING
+ /* So won't continue to output stuff */
+ debug_initialization = FALSE;
+#endif
+
      return ok;
  }

-
  #ifdef USE_LOCALE_COLLATE

-/*
- * mem_collxfrm() is a bit like strxfrm() but with two important
- * 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 *input_string,
- STRLEN len,
- STRLEN *xlen
+Perl__mem_collxfrm(pTHX_ const char *input_string,
+ STRLEN len, /* Length of 'input_string' */
+ STRLEN *xlen, /* Set to length of returned string
+ (not including the collation index
+ prefix) */
+ bool utf8 /* Is the input in UTF-8? */
                     )
  {
+
+ /* _mem_collxfrm() is a bit like strxfrm() but with two important
+ * 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 COLLXFRM_HDR_LEN. *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. */
+
+#define COLLXFRM_HDR_LEN sizeof(PL_collation_ix)
+
      char * s = (char *) input_string;
      STRLEN s_strlen = strlen(input_string);
      char *xbuf = NULL;
- STRLEN xAlloc, xout; /* xalloc is a reserved word in VC */
+ STRLEN xAlloc; /* xalloc is a reserved word in VC */
+ STRLEN length_in_chars;
      bool first_time = TRUE; /* Cleared after first loop iteration */

- PERL_ARGS_ASSERT_MEM_COLLXFRM;
+ PERL_ARGS_ASSERT__MEM_COLLXFRM;
+
+ /* Must be NUL-terminated */
+ assert(*(input_string + len) == '\0');

      /* If this locale has defective collation, skip */
      if (PL_collxfrm_base == 0 && PL_collxfrm_mult == 0) {
@@ -1418,7 +1474,10 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
           * locale, find it */
          if (*PL_strxfrm_min_char == '\0') {
              int j;
- char * cur_min_x = NULL; /* Cur cp's xfrm, (except it also
+#ifdef DEBUGGING
+ U8 cur_min_cp = 1; /* The code point that sorts lowest, so far */
+#endif
+ char * cur_min_x = NULL; /* And its xfrm, (except it also
                                             includes the collation index
                                             prefixed. */

@@ -1439,7 +1498,9 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,

                      /* If needs to be 2 bytes, find them */
                      if (! UVCHR_IS_INVARIANT(j)) {
- continue; /* Can't handle variants yet */
+ char * d = cur_source;
+ append_utf8_from_native_byte((U8) j, (U8 **) &d);
+ trial_len = 2;
                      }
                  }
                  else if (! isCNTRL_LC(j)) {
@@ -1447,12 +1508,13 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
                  }

                  /* Then transform it */
- x = mem_collxfrm(cur_source, trial_len, &x_len);
+ x = _mem_collxfrm(cur_source, trial_len, &x_len,
+ PL_in_utf8_COLLATE_locale);

                  /* 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)
+ || strlen(x + COLLXFRM_HDR_LEN) < x_len)
                  {
                      continue;
                  }
@@ -1460,11 +1522,14 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
                  /* 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)))
+ || strLT(x + COLLXFRM_HDR_LEN,
+ cur_min_x + COLLXFRM_HDR_LEN))
                  {
                      strcpy(PL_strxfrm_min_char, cur_source);
                      cur_min_x = x;
+#ifdef DEBUGGING
+ cur_min_cp = j;
+#endif
                  }
                  else {
                      Safefree(x);
@@ -1475,10 +1540,26 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
               * locale, arbitrarily use \001 */
              if (cur_min_x == NULL) {
                  STRLEN x_len; /* temporary */
- cur_min_x = mem_collxfrm("\001", 1, &x_len);
+ cur_min_x = _mem_collxfrm("\001", 1, &x_len,
+ PL_in_utf8_COLLATE_locale);
                  /* cur_min_cp was already initialized to 1 */
              }

+ DEBUG_L(PerlIO_printf(Perl_debug_log,
+ "_mem_collxfrm: lowest collating control in the 0-255 "
+ "range in locale %s is 0x%02X\n",
+ PL_collation_name,
+ cur_min_cp));
+ if (DEBUG_Lv_TEST) {
+ unsigned i;
+ PerlIO_printf(Perl_debug_log, "Its xfrm is");
+ for (i = 0; i < strlen(cur_min_x + COLLXFRM_HDR_LEN); i ++) {
+ PerlIO_printf(Perl_debug_log, " %02x",
+ (U8) *(cur_min_x + COLLXFRM_HDR_LEN + i));
+ }
+ PerlIO_printf(Perl_debug_log, "\n");
+ }
+
              Safefree(cur_min_x);
          }

@@ -1511,32 +1592,231 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
          len = strlen(s);
      }

+ /* Make sure the UTF8ness of the string and locale match */
+ if (utf8 != PL_in_utf8_COLLATE_locale) {
+ const char * const t = s; /* Temporary so we can later find where the
+ input was */
+
+ /* Here they don't match. Change the string's to be what the locale is
+ * expecting */
+
+ if (! utf8) { /* locale is UTF-8, but input isn't; upgrade the input */
+ s = (char *) bytes_to_utf8((const U8 *) s, &len);
+ utf8 = TRUE;
+ }
+ else { /* locale is not UTF-8; but input is; downgrade the input */
+
+ s = (char *) bytes_from_utf8((const U8 *) s, &len, &utf8);
+
+ /* If the downgrade was successful we are done, but if the input
+ * contains things that require UTF-8 to represent, have to do
+ * damage control ... */
+ if (UNLIKELY(utf8)) {
+
+ /* What we do is construct a non-UTF-8 string with
+ * 1) the characters representable by a single byte converted
+ * to be so (if necessary);
+ * 2) and the rest converted to collate the same as the
+ * highest collating representable character. That makes
+ * them collate at the end. This is similar to how we
+ * handle embedded NULs, but we use the highest collating
+ * code point instead of the smallest. Like the NUL case,
+ * this isn't perfect, but is the best we can reasonably
+ * do. Every above-255 code point will sort the same as
+ * the highest-sorting 0-255 code point. If that code
+ * point can combine in a sequence with some other code
+ * points for weight calculations, us changing something to
+ * be it can adversely affect the results. But in most
+ * cases, it should work reasonably. And note that this is
+ * really an illegal situation: using code points above 255
+ * on a locale where only 0-255 are valid. If two strings
+ * sort entirely equal, then the sort order for the
+ * above-255 code points will be in code point order. */
+
+ utf8 = FALSE;
+
+ /* If we haven't calculated the code point with the maximum
+ * collating order for this locale, do so now */
+ if (! PL_strxfrm_max_cp) {
+ int j;
+
+ /* The current transformed string that collates the
+ * highest (except it also includes the prefixed collation
+ * index. */
+ char * cur_max_x = NULL;
+
+ /* Look through all legal code points (NUL isn't) */
+ for (j = 1; j < 256; j++) {
+ char * x;
+ STRLEN x_len;
+
+ /* Create a 1-char string of the current code point. */
+ char cur_source[] = { (char) j, '\0' };
+
+ /* Then transform it */
+ x = _mem_collxfrm(cur_source, 1, &x_len, FALSE);
+
+ /* If something went wrong (which it shouldn't), just
+ * ignore this code point */
+ if (x_len == 0) {
+ Safefree(x);
+ continue;
+ }
+
+ /* If this character's transformation is higher than
+ * the current highest, this one becomes the highest */
+ if ( cur_max_x == NULL
+ || strGT(x + COLLXFRM_HDR_LEN,
+ cur_max_x + COLLXFRM_HDR_LEN))
+ {
+ PL_strxfrm_max_cp = j;
+ cur_max_x = x;
+ }
+ else {
+ Safefree(x);
+ }
+ }
+
+ DEBUG_L(PerlIO_printf(Perl_debug_log,
+ "_mem_collxfrm: highest 1-byte collating character"
+ " in locale %s is 0x%02X\n",
+ PL_collation_name,
+ PL_strxfrm_max_cp));
+ if (DEBUG_Lv_TEST) {
+ unsigned i;
+ PerlIO_printf(Perl_debug_log, "Its xfrm is ");
+ for (i = 0;
+ i < strlen(cur_max_x + COLLXFRM_HDR_LEN);
+ i++)
+ {
+ PerlIO_printf(Perl_debug_log, " %02x",
+ (U8) cur_max_x[i + COLLXFRM_HDR_LEN]);
+ }
+ PerlIO_printf(Perl_debug_log, "\n");
+ }
+
+ Safefree(cur_max_x);
+ }
+
+ /* Here we know which legal code point collates the highest.
+ * We are ready to construct the non-UTF-8 string. The length
+ * will be at least 1 byte smaller than the input string
+ * (because we changed at least one 2-byte character into a
+ * single byte), but that is eaten up by the trailing NUL */
+ Newx(s, len, char);
+
+ {
+ STRLEN i;
+ STRLEN d= 0;
+
+ for (i = 0; i < len; i+= UTF8SKIP(t + i)) {
+ U8 cur_char = t[i];
+ if (UTF8_IS_INVARIANT(cur_char)) {
+ s[d++] = cur_char;
+ }
+ else if (UTF8_IS_DOWNGRADEABLE_START(cur_char)) {
+ s[d++] = EIGHT_BIT_UTF8_TO_NATIVE(cur_char, t[i+1]);
+ }
+ else { /* Replace illegal cp with highest collating
+ one */
+ s[d++] = PL_strxfrm_max_cp;
+ }
+ }
+ s[d++] = '\0';
+ Renew(s, d, char); /* Free up unused space */
+ }
+ }
+ }
+
+ /* Here, we have constructed a modified version of the input. It could
+ * be that we already had a modified copy before we did this version.
+ * If so, that copy is no longer needed */
+ if (t != input_string) {
+ Safefree(t);
+ }
+ }
+
+ length_in_chars = (utf8)
+ ? utf8_length((U8 *) s, (U8 *) s + len)
+ : len;
+
      /* 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;
+ xAlloc = COLLXFRM_HDR_LEN
+ + PL_collxfrm_base
+ + (PL_collxfrm_mult * length_in_chars);
      Newx(xbuf, xAlloc, char);
      if (UNLIKELY(! xbuf))
   goto bad;

      /* Store the collation id */
      *(U32*)xbuf = PL_collation_ix;
- xout = sizeof(PL_collation_ix);

      /* Then the transformation of the input. We loop until successful, or we
       * give up */
      for (;;) {
- STRLEN xused = strxfrm(xbuf + xout, s, xAlloc - xout);
+ *xlen = strxfrm(xbuf + COLLXFRM_HDR_LEN, s, xAlloc - COLLXFRM_HDR_LEN);

          /* 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;
+ if (*xlen < xAlloc - COLLXFRM_HDR_LEN) {
+
+ /* If the first try didn't get it, it means our prediction was low.
+ * Modify the coefficients so that we predict a larger value in any
+ * future transformations */
+ if (! first_time) {
+ STRLEN needed = *xlen + 1; /* +1 For trailing NUL */
+ STRLEN computed_guess = PL_collxfrm_base
+ + (PL_collxfrm_mult * length_in_chars);
+ const STRLEN new_m = needed / length_in_chars;
+
+ DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+ "%s: %d: initial size of %"UVuf" bytes for a length "
+ "%"UVuf" string was insufficient, %"UVuf" needed\n",
+ __FILE__, __LINE__,
+ (UV) computed_guess, (UV) length_in_chars, (UV) needed));
+
+ /* If slope increased, use it, but discard this result for
+ * length 1 strings, as we can't be sure that it's a real slope
+ * change */
+ if (length_in_chars > 1 && new_m > PL_collxfrm_mult) {
+#ifdef DEBUGGING
+ STRLEN old_m = PL_collxfrm_mult;
+ STRLEN old_b = PL_collxfrm_base;
+#endif
+ PL_collxfrm_mult = new_m;
+ PL_collxfrm_base = 1; /* +1 For trailing NUL */
+ computed_guess = PL_collxfrm_base
+ + (PL_collxfrm_mult * length_in_chars);
+ if (computed_guess < needed) {
+ PL_collxfrm_base += needed - computed_guess;
+ }
+
+ DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+ "%s: %d: slope is now %"UVuf"; was %"UVuf", base "
+ "is now %"UVuf"; was %"UVuf"\n",
+ __FILE__, __LINE__,
+ (UV) PL_collxfrm_mult, (UV) old_m,
+ (UV) PL_collxfrm_base, (UV) old_b));
+ }
+ else { /* Slope didn't change, but 'b' did */
+ const STRLEN new_b = needed
+ - computed_guess
+ + PL_collxfrm_base;
+ DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+ "%s: %d: base is now %"UVuf"; was %"UVuf"\n",
+ __FILE__, __LINE__,
+ (UV) new_b, (UV) PL_collxfrm_base));
+ PL_collxfrm_base = new_b;
+ }
+ }
+
              break;
          }

- if (UNLIKELY(xused >= PERL_INT_MAX))
+ if (UNLIKELY(*xlen >= PERL_INT_MAX))
              goto bad;

          /* A well-behaved strxfrm() returns exactly how much space it needs
@@ -1544,7 +1824,7 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
           * space being provided. Assume that this is the case unless it's been
           * proven otherwise */
          if (LIKELY(PL_strxfrm_is_behaved) && first_time) {
- xAlloc = xused + sizeof(PL_collation_ix) + 1;
+ xAlloc = *xlen + COLLXFRM_HDR_LEN + 1;
          }
          else { /* Here, either:
                  * 1) The strxfrm() has previously shown bad behavior; or
@@ -1556,10 +1836,19 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
                  * isn't sufficient, they return the input size instead of
                  * how much is needed.)
                  * Increase the buffer size by a fixed percentage and try again. */
- xAlloc = (2 * xAlloc) + 1;
+ xAlloc += (xAlloc / 4) + 1;
              PL_strxfrm_is_behaved = FALSE;
- }

+#ifdef DEBUGGING
+ if (DEBUG_Lv_TEST || debug_initialization) {
+ PerlIO_printf(Perl_debug_log,
+ "_mem_collxfrm required more space than previously calculated"
+ " for locale %s, trying again with new guess=%d+%"UVuf"\n",
+ PL_collation_name, (int) COLLXFRM_HDR_LEN,
+ (UV) xAlloc - COLLXFRM_HDR_LEN);
+ }
+#endif
+ }

          Renew(xbuf, xAlloc, char);
          if (UNLIKELY(! xbuf))
@@ -1568,10 +1857,23 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
          first_time = FALSE;
      }

- *xlen = xout - sizeof(PL_collation_ix);
+
+#ifdef DEBUGGING
+ if (DEBUG_Lv_TEST || debug_initialization) {
+ unsigned i;
+ PerlIO_printf(Perl_debug_log,
+ "_mem_collxfrm[%d]: returning %"UVuf" for locale %s '%s'\n",
+ PL_collation_ix, *xlen, PL_collation_name, input_string);
+ PerlIO_printf(Perl_debug_log, "Its xfrm is");
+ for (i = COLLXFRM_HDR_LEN; i < *xlen + COLLXFRM_HDR_LEN; i++) {
+ PerlIO_printf(Perl_debug_log, " %02x", (U8) xbuf[i]);
+ }
+ PerlIO_printf(Perl_debug_log, "\n");
+ }
+#endif

      /* Free up unneeded space; retain ehough for trailing NUL */
- Renew(xbuf, xout + 1, char);
+ Renew(xbuf, COLLXFRM_HDR_LEN + *xlen + 1, char);

      if (s != input_string) {
          Safefree(s);
@@ -1585,10 +1887,17 @@ Perl_mem_collxfrm(pTHX_ const char *input_string,
          Safefree(s);
      }
      *xlen = 0;
+#ifdef DEBUGGING
+ if (DEBUG_Lv_TEST || debug_initialization) {
+ PerlIO_printf(Perl_debug_log, "_mem_collxfrm[%d] returning NULL\n",
+ PL_collation_ix);
+ }
+#endif
      return NULL;
  }

  #endif /* USE_LOCALE_COLLATE */
+
  #ifdef USE_LOCALE

  bool
diff --git a/mathoms.c b/mathoms.c
index cd2c1a4..82ee778 100644
--- a/mathoms.c
+++ b/mathoms.c
@@ -1096,6 +1096,18 @@ Perl_sv_collxfrm(pTHX_ SV *const sv, STRLEN *const nxp)
  {
      return sv_collxfrm_flags(sv, nxp, SV_GMAGIC);
  }
+
+char *
+Perl_mem_collxfrm(pTHX_ const char *input_string, STRLEN len, STRLEN *xlen)
+{
+ /* This function is retained for compatibility in case someone outside core
+ * is using this (but it is undocumented) */
+
+ PERL_ARGS_ASSERT_MEM_COLLXFRM;
+
+ return _mem_collxfrm(input_string, len, xlen, FALSE);
+}
+
  #endif

  bool
diff --git a/pod/perldelta.pod b/pod/perldelta.pod
index d334bb8..69a3d53 100644
--- a/pod/perldelta.pod
+++ b/pod/perldelta.pod
@@ -27,6 +27,16 @@ here, but most should go in the L</Performance Enhancements> section.

  [ List each enhancement as a =head2 entry ]

+=head2 Perl can now do default collation in UTF-8 locales on platforms
+that support it
+
+Some platforms natively do a reasonable job of collating and sorting in
+UTF-8 locales. Perl now works with those. For portability and full
+control, L<Unicode::Collate> is still recommended, but now you may
+not need to do anything special to get good-enough results, depending on
+your application. See
+L<perllocale/Category C<LC_COLLATE>: Collation: Text Comparisons and Sorting>
+
  =head2 Better locale collation of strings containing embedded C<NUL>
  characters

diff --git a/pod/perllocale.pod b/pod/perllocale.pod
index ddb60f2..369f8dc 100644
--- a/pod/perllocale.pod
+++ b/pod/perllocale.pod
@@ -33,9 +33,11 @@ design deficiencies, and nowadays, there is a series of "UTF-8
  locales", based on Unicode. These are locales whose character set is
  Unicode, encoded in UTF-8. Starting in v5.20, Perl fully supports
  UTF-8 locales, except for sorting and string comparisons like C<lt> and
-C<ge>. (Use L<Unicode::Collate> for these.) Perl continues to support
-the old non UTF-8 locales as well. There are currently no UTF-8 locales
-for EBCDIC platforms.
+C<ge>. Starting in v5.26, Perl can handle these reasonably as well,
+depending on the platform's implementation. However, for earlier
+releases or for better control, use L<Unicode::Collate> . Perl continues to
+support the old non UTF-8 locales as well. There are currently no UTF-8
+locales for EBCDIC platforms.

  (Unicode is also creating C<CLDR>, the "Common Locale Data Repository",
  L<http://cldr.unicode.org/> which includes more types of information than
@@ -768,7 +770,7 @@ The following subsections describe basic locale categories. Beyond these,
  some combination categories allow manipulation of more than one
  basic category at a time. See L<"ENVIRONMENT"> for a discussion of these.

-=head2 Category C<LC_COLLATE>: Collation
+=head2 Category C<LC_COLLATE>: Collation: Text Comparisons and Sorting

  In the scope of a S<C<use locale>> form that includes collation, Perl
  looks to the C<LC_COLLATE>
@@ -815,10 +817,31 @@ C<$equal_in_locale> will be true if the collation locale specifies a
  dictionary-like ordering that ignores space characters completely and
  which folds case.

-Perl currently only supports single-byte locales for C<LC_COLLATE>. This means
-that a UTF-8 locale likely will just give you machine-native ordering.
-Use L<Unicode::Collate> for the full implementation of the Unicode
-Collation Algorithm.
+Perl uses the platform's C library collation functions C<strcoll()> and
+C<strxfrm()>. That means you get whatever they give. On some
+platforms, these functions work well on UTF-8 locales, giving
+a reasonable default collation for the code points that are important in
+that locale. (And if they aren't working well, the problem may only be
+that the locale definition is deficient, so can be fixed by using a
+better definition file. Unicode's definitions (see L</Freely available
+locale definitions>) provide reasonable UTF-8 locale collation
+definitions.) Starting in Perl v5.26, Perl's use of these functions has
+been made more seamless. This may be sufficient for your needs. For
+more control, and to make sure strings containing any code point (not
+just the ones important in the locale) collate properly, the
+L<Unicode::Collate> module is suggested.
+
+In non-UTF-8 locales (hence single byte), code points above 0xFF are
+technically invalid. But if present, again starting in v5.26, they will
+collate to the same position as the highest valid code point does. This
+generally gives good results, but the collation order may be skewed if
+the valid code point gets special treatment when it forms particular
+sequences with other characters as defined by the locale.
+When two strings collate identically, the code point order is used as a
+tie breaker.
+
+If Perl detects that there are problems with the locale collation order,
+it reverts to using non-locale collation rules for that locale.

  If Perl detects that there are problems with the locale collation order,
  it reverts to using non-locale collation rules for that locale.
@@ -975,7 +998,7 @@ to crack.

  See also L<I18N::Langinfo> and C<CRNCYSTR>.

-=head2 C<LC_TIME>
+=head2 Category C<LC_TIME>: Respresentation of time

  Output produced by C<POSIX::strftime()>, which builds a formatted
  human-readable date/time string, is affected by the current C<LC_TIME>
@@ -1417,9 +1440,12 @@ into bankers, bikers, gamers, and so on.
  The support of Unicode is new starting from Perl version v5.6, and more fully
  implemented in versions v5.8 and later. See L<perluniintro>.

-Starting in Perl v5.20, UTF-8 locales are supported in Perl, except for
-C<LC_COLLATE> (use L<Unicode::Collate> instead). If you have Perl v5.16
-or v5.18 and can't upgrade, you can use
+Starting in Perl v5.20, UTF-8 locales are supported in Perl, except
+C<LC_COLLATE> is only partially supported; collation support is improved
+in Perl v5.26 to a level that may be sufficient for your needs
+(see L</Category C<LC_COLLATE>: Collation: Text Comparisons and Sorting>).
+
+If you have Perl v5.16 or v5.18 and can't upgrade, you can use

      use locale ':not_characters';

@@ -1445,10 +1471,7 @@ command line switch.

  This form of the pragma allows essentially seamless handling of locales
  with Unicode. The collation order will be by Unicode code point order.
-It is strongly
-recommended that when you need to order and sort strings that you use
-the standard module L<Unicode::Collate> which gives much better results
-in many instances than you can get with the old-style locale handling.
+L<Unicode::Collate> can be used to get Unicode rules collation.

  All the modules and switches just described can be used in v5.20 with
  just plain C<use locale>, and, should the input locales not be UTF-8,
@@ -1564,7 +1587,8 @@ consistently to regular expression matching except for bracketed
  character classes; in v5.14 it was extended to all regex matches; and in
  v5.16 to the casing operations such as C<\L> and C<uc()>. For
  collation, in all releases so far, the system's C<strxfrm()> function is
-called, and whatever it does is what you get.
+called, and whatever it does is what you get. Starting in v5.26, various
+bugs are fixed with the way perl uses this function.

  =head1 BUGS

diff --git a/proto.h b/proto.h
index 0819f26..4ad4b93 100644
--- a/proto.h
+++ b/proto.h
@@ -4430,6 +4430,13 @@ PERL_CALLCONV SV* Perl_hfree_next_entry(pTHX_ HV *hv, STRLEN *indexp);
  #define PERL_ARGS_ASSERT_HFREE_NEXT_ENTRY \
   assert(hv); assert(indexp)
  #endif
+#if defined(PERL_IN_LOCALE_C) || defined(PERL_IN_SV_C) || defined(PERL_IN_MATHOMS_C)
+# if defined(USE_LOCALE_COLLATE)
+PERL_CALLCONV char* Perl__mem_collxfrm(pTHX_ const char* input_string, STRLEN len, STRLEN* xlen, bool utf8);
+#define PERL_ARGS_ASSERT__MEM_COLLXFRM \
+ assert(input_string); assert(xlen)
+# endif
+#endif
  #if defined(PERL_IN_MALLOC_C)
  STATIC int S_adjust_size_and_find_bucket(size_t *nbytes_p);
  #define PERL_ARGS_ASSERT_ADJUST_SIZE_AND_FIND_BUCKET \
@@ -5785,9 +5792,11 @@ STATIC char* S_stdize_locale(pTHX_ char* locs);
  PERL_CALLCONV int Perl_magic_setcollxfrm(pTHX_ SV* sv, MAGIC* mg);
  #define PERL_ARGS_ASSERT_MAGIC_SETCOLLXFRM \
   assert(sv); assert(mg)
+#ifndef NO_MATHOMS
  PERL_CALLCONV char* Perl_mem_collxfrm(pTHX_ const char* input_string, STRLEN len, STRLEN* xlen);
  #define PERL_ARGS_ASSERT_MEM_COLLXFRM \
   assert(input_string); assert(xlen)
+#endif
  /* PERL_CALLCONV char* sv_collxfrm(pTHX_ SV *const sv, STRLEN *const nxp); */
  PERL_CALLCONV char* Perl_sv_collxfrm_flags(pTHX_ SV *const sv, STRLEN *const nxp, I32 const flags);
  #define PERL_ARGS_ASSERT_SV_COLLXFRM_FLAGS \
diff --git a/sv.c b/sv.c
index e2288b5..535ee8d 100644
--- a/sv.c
+++ b/sv.c
@@ -8152,7 +8152,7 @@ Perl_sv_collxfrm_flags(pTHX_ SV *const sv, STRLEN *const nxp, const I32 flags)
       Safefree(mg->mg_ptr);

   s = SvPV_flags_const(sv, len, flags);
- if ((xf = mem_collxfrm(s, len, &xlen))) {
+ if ((xf = _mem_collxfrm(s, len, &xlen, cBOOL(SvUTF8(sv))))) {
       if (! mg) {
    mg = sv_magicext(sv, 0, PERL_MAGIC_collxfrm, &PL_vtbl_collxfrm,
       0, 0);
@@ -14779,6 +14779,7 @@ perl_clone_using(PerlInterpreter *proto_perl, UV flags,
      PL_collation_standard = proto_perl->Icollation_standard;
      PL_collxfrm_base = proto_perl->Icollxfrm_base;
      PL_collxfrm_mult = proto_perl->Icollxfrm_mult;
+ PL_strxfrm_max_cp = proto_perl->Istrxfrm_max_cp;
  #endif /* USE_LOCALE_COLLATE */

  #ifdef USE_LOCALE_NUMERIC

--
Perl5 Master Repository

Search Discussions

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupperl5-changes @
categoriesperl
postedMay 24, '16 at 4:28p
activeMay 24, '16 at 4:28p
posts1
users1
websiteperl.org

1 user in discussion

Karl Williamson: 1 post

People

Translate

site design / logo © 2018 Grokbase