unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* Re: Using libunistring for string comparisons et al
@ 2011-03-12 21:28 Mike Gran
  2011-03-15 17:20 ` Mark H Weaver
  0 siblings, 1 reply; 48+ messages in thread
From: Mike Gran @ 2011-03-12 21:28 UTC (permalink / raw)
  To: Mark H Weaver, Ludovic Courtès; +Cc: guile-devel@gnu.org

> From:Mark H Weaver <mhw@netris.org>

> 
> ludo@gnu.org (Ludovic Courtès) writes:
> > I find Cowan’s proposal for string iteration and the R6RS editors
> > response interesting:
> >
> >   http://www.r6rs.org/formal-comments/comment-235.txt
> 
> Cowan was proposing a complex new API.  I am not, nor did Gauche.
> An efficient implementation of string ports is all that is needed.
> 
> > I also think strings should remain what they currently are, with O(1)
> > random access.
> 
> I understand your position, and perhaps you are right.
> 
> Unfortunately, the alternatives are not pleasant.  We have a bunch of
> bugs in our string handling functions.  Currently, our case-insensitive
> string comparisons and case conversions are not correct for several
> languages including German, according to the R6RS among other things.

(Just as an aside, in discussions like this, I think it important to distinguish
between
- where Guile doesn't match R6RS
- where R6RS doesn't match Unicode
- and where Unicode doesn't match reality

Each of these battles needs to be fought in the proper battlefield.)

> We could easily fix these problems by using libunistring, which provides
> the operations we need, but only if we use a single string
> representation, and one that is supported by libunistring (UTF-8,
> UTF-16, or UTF-32).

We do, in a matter of speaking, have a single string representation: UTF-32.
The 'narrow' encoding is UTF-32 with the initial 3 bytes of zero removed.

> Our use of two different internal string representations is another
> problem.  Right now, our string comparisons are painfully inefficient.
> Take a look at compare_strings in srfi-13.c.  It's also broken w.r.t.
> case-insensitive comparisons.  In order to fix this and make it
> efficient, we'll need to make several different variants:
> 
>   * case-sensitive
>      * narrow-narrow
>      * narrow-wide
>      * wide-wide (use libunistring's u32_cmp2 for this)
> 
>   * case-insensitive
>      * narrow-narrow
>      * narrow-wide
>      * wide-wide (use libunistring for this)
> 
> The case-insensitive narrow-narrow comparison must be able to handle
> this, for example (from r6rs-lib):
> 
>   (string-ci=? "Straße" "Strasse") => #t
> 
> I'm not yet sure what's involved in implementing the case-insensitive
> narrow-wide case properly.

It is not too difficult in practice, I think.  Converting narrow (Latin-1
aka truncated UTF-32) to wide (UTF-32) involves adding back
in the missing zero bytes.

For the sake of history, here's how we got to where we are now.
- R6RS says characters are Unicode codepoints
- R6RS says string ops are O(1)
- The only Unicode encoding that uses codepoints as its atomic units and 
  is O(1) is UTF-32
- UTF-32 wastes space for most normal circumstances
Thus we invented this wide (UTF-32) narrow (UTF-32 with initial
zeros stripped) encoding scheme we have now.  It may seem
to be suboptimal in terms of complexity and familiarity, but, 
what you see is an attempt to be optimal in terms of memory and
O(1).

I actually at one point had a nearly complete version of Guile 1.8
that used UTF-8 and another that used UTF-32.  There are some
other reasons why UTF-8 is bad, which I could bore you with
ad naseum.

Thanks,

Mike




^ permalink raw reply	[flat|nested] 48+ messages in thread
* Re: Using libunistring for string comparisons et al
@ 2011-03-17 18:07 Mike Gran
  0 siblings, 0 replies; 48+ messages in thread
From: Mike Gran @ 2011-03-17 18:07 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: Mark H Weaver, guile-devel@gnu.org

> From:Ludovic Courtès <ludo@gnu.org>
> >> Can we first check what would need to be done to fix this in 2.0.x?
> >> 
> >> At first glance:
> >> 
> >>   - “Straße” is normally stored as a Latin1 string, so it would need to
> >>     be converted to UTF-* before it can be passed to one of the
> >>     unicase.h functions.  *Or*, we could check with bug-libunistring
> >>     what it would take to add Latin1 string case mapping functions.
> >> 
> >>     Interestingly, ‘ß’ is the only Latin1 character that doesn’t have a
> >>     one-to-one case mapping.  All other Latin1 strings can be handled 
> by
> >>     iterating over characters, as is currently done.
> >
> > There is the micro sign, which, when case folded, becomes a Greek mu.
> > It is still a single character, but, it is the only latin-1 character that,
> > when folded, becomes a non-Latin-1 character
> 
> Blech.
> 
> It would have worked better with narrow == ASCII instead of
> narrow == Latin1.  It’s a change we can still make, I think.

It would be easy enough to do.  If someone were to fight for
a narrow encoding of Latin-1, I would expect it to be you, since
you're the only committer whose name requires ISO-8859-1.
So if you're okay with it, who am I to complain?

> 
> >>   - Case insensitive comparison is more difficult, as you already
> >>     pointed out.  To do it right we’d probably need to convert Latin1
> >>     strings to UTF-32 and then pass it to u32_casecmp.  We don’t have 
> to
> >>     do the conversion every time, though: we could just change Latin1
> >>     strings in-place so they now point to a wide stringbuf upon the
> >>     first ‘string-ci=’.
> >> 
> >> Thoughts?
> >

[...]

> 
> Indeed it’s quite inelegant.  ;-)
> 
> How about changing to narrow == ASCII and then string comparison would
> be:
> 
>   if (narrow (s1) != narrow (s2))
>     {

It would be easier and cleaner, as you demonstrate.

I guess the question is about future-proofing.  If the complications with the Latin-1
/ UTF-32 dual encoding are constrained to upcase/downcase and string-ci
comparison ops, then it doesn't seem worth it to change it.  But if it is going
to cause endless problems down the road, ASCII/UTF-32 is simpler.

A lot of this debate is about expectations, I think.  For my part, I think that
the string-ci ops only have real value for English language and ASCII text.
For non-English non-ASCII processing, sorting case-insensitively by numeric
codepoint values in the absence of locale sorting rules seems like an odd thing
to want to do. 

So I guess I'm not bothered with the ugly C necessary to make ISO-8859-1 work.
It is bad for string-ci ops but not too bad for upcase/downcase.  I also am
not too concerned that string-ci comparison ops for non-English non-ASCII
processing may be inefficient.  It does seem vital that string-locale comparison
ops be efficient, though.

Thanks,
Mike



^ permalink raw reply	[flat|nested] 48+ messages in thread
* Re: Using libunistring for string comparisons et al
@ 2011-03-16 15:22 Mike Gran
  2011-03-16 16:58 ` Ludovic Courtès
  0 siblings, 1 reply; 48+ messages in thread
From: Mike Gran @ 2011-03-16 15:22 UTC (permalink / raw)
  To: Ludovic Courtès, Mark H Weaver; +Cc: guile-devel@gnu.org

[-- Attachment #1: Type: text/plain, Size: 3110 bytes --]

> From:Ludovic Courtès <ludo@gnu.org>

> > I know of two categories of bugs.  One has to do with case conversions
> > and case-insensitive comparisons, which must be done on entire strings
> > but are currently done for each character.  Here are some examples:
> >
> >   (string-upcase "Straße")         => "STRAßE"  
> (should be "STRASSE")
> >   (string-downcase "ΧΑΟΣΣ")        => "χαοσσ"  
> (should be "χαoσς")
> >   (string-downcase "ΧΑΟΣ Σ")       => "χαοσ σ"  
> (should be "χαoς σ")
> >   (string-ci=? "Straße" "Strasse") => #f        
> (should be #t)
> >   (string-ci=? "ΧΑΟΣ" "χαoσ")      => #f        
> (should be #t)
> 
> (Mike pointed out that SRFI-13 does not consider these bugs, but that’s
> linguistically wrong so I’d consider it a bug.  Note that all these
> functions are ‘linguistically buggy’ anyway since they don’t have a
> locale argument, which breaks with Turkish ‘İ’.)
> 
> Can we first check what would need to be done to fix this in 2.0.x?
> 
> At first glance:
> 
>   - “Straße” is normally stored as a Latin1 string, so it would need to
>     be converted to UTF-* before it can be passed to one of the
>     unicase.h functions.  *Or*, we could check with bug-libunistring
>     what it would take to add Latin1 string case mapping functions.
> 
>     Interestingly, ‘ß’ is the only Latin1 character that doesn’t have a
>     one-to-one case mapping.  All other Latin1 strings can be handled by
>     iterating over characters, as is currently done.

There is the micro sign, which, when case folded, becomes a Greek mu.
It is still a single character, but, it is the only latin-1 character that,
when folded, becomes a non-Latin-1 character

> 
>     With this in mind, we could hack our way so that strings that
>     contain an ‘ß’ are stored as UTF-32 (yes, that’s a hack.)
> 
>   - For ‘string-downcase’, the Greek strings above are wide strings, so
>     they can be passed directly to u32_toupper & co.  For these, the fix
>     is almost two lines.
> 
>   - Case insensitive comparison is more difficult, as you already
>     pointed out.  To do it right we’d probably need to convert Latin1
>     strings to UTF-32 and then pass it to u32_casecmp.  We don’t have to
>     do the conversion every time, though: we could just change Latin1
>     strings in-place so they now point to a wide stringbuf upon the
>     first ‘string-ci=’.
> 
> Thoughts?

What about the srfi-13 case insensitive comparisons (the ones that don't
terminate in question marks, like string-ci<)?  Should they remain
as srfi-13 suggests, or should they remain similar in behavior
to the question-mark-terminated comparisons?

Mark is right that fixing this will not be pretty.  The case insensitive
string comparisons, for example, could be patched like the attached
snippet. If you don't find it too ugly of an approach, I could work on
a real patch.

Thanks,

Mike

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: strorder.c.patch --]
[-- Type: text/x-patch; name="strorder.c.patch", Size: 8227 bytes --]

diff --git a/libguile/strorder.c b/libguile/strorder.c
index a51ce17..6df8343 100644
--- a/libguile/strorder.c
+++ b/libguile/strorder.c
@@ -21,6 +21,8 @@
 # include <config.h>
 #endif
 
+#include <unicase.h>
+
 #include "libguile/_scm.h"
 #include "libguile/chars.h"
 #include "libguile/strings.h"
@@ -42,6 +44,164 @@ srfi13_cmp (SCM s1, SCM s2, SCM (*cmp) (SCM, SCM, SCM, SCM, SCM, SCM))
     return SCM_BOOL_F;
 }
 
+#define SHARP_S (0xdf)
+#define MICRO_SIGN (0xb5)
+#define MU (0x3bc)
+/* This function compares does a comparison of the case-folded
+   versions of S1 and S2. It returns -1 if S1 < S2, 0 if they are equal
+   or 1 if S1 > S2. */
+static int
+compare_folded_strings (SCM s1, SCM s2)
+{
+  if (!scm_is_string (s1))
+    scm_wrong_type_arg (__func__, 0, s1);
+  if (!scm_is_string (s2))
+    scm_wrong_type_arg (__func__, 1, s2);
+  if (scm_i_is_narrow_string (s1) && scm_i_is_narrow_string (s2))
+    {
+      size_t cindex1 = 0, cindex2 = 0;
+      const size_t cend1 = scm_i_string_length (s1);
+      const size_t cend2 = scm_i_string_length (s2);
+      ucs4_t a, b;
+      int ss1 = 0, ss2 = 0;
+  
+      /* For narrow strings, folding equals downcasing except for sharp
+	 s which becomes 'ss' and the micro sign which becomes Greek
+	 mu.  */
+      while (cindex1 < cend1 && cindex2 < cend2)
+	{
+	  if (ss1)
+	    a = (ucs4_t) 's';
+	  else
+	    {
+	      a = uc_tolower ((unsigned char) (scm_i_string_chars (s1))[cindex1]);
+	      if (a == SHARP_S)
+		{
+		  a = (ucs4_t) 's';
+		  ss1 = 2;
+		}
+	      if (a == MICRO_SIGN)
+		a = MU;
+	    }
+	  if (ss2)
+	    b = (ucs4_t) 's';
+	  else
+	    {
+	      b = uc_tolower ((unsigned char) (scm_i_string_chars (s2))[cindex2]);
+	      if (b == SHARP_S)
+		{
+		  b = 's';
+		  ss2 = 2;
+		}
+	      if (b == MICRO_SIGN)
+		b = MU;
+	    }
+	  if (a < b)
+	    return -1;
+	  else if (a > b)
+	    return 1;
+	  if (ss1)
+	    ss1 --;
+	  if (!ss1)
+	    cindex1 ++;
+	  if (ss2)
+	    ss2 --;
+	  if (!ss2)
+	    cindex2 ++;
+	}
+      if (cindex1 < cend1)
+	return 1;
+      else if (cindex2 < cend2)
+	return -1;
+
+      return 0;
+    }
+  else if (!scm_i_is_narrow_string (s1) && !scm_i_is_narrow_string (s2))
+    {
+      int ret, result;
+
+      ret = u32_casecmp ((const uint32_t *) scm_i_string_wide_chars (s1),
+			 scm_i_string_length (s1),
+			 (const uint32_t *) scm_i_string_wide_chars (s2),
+			 scm_i_string_length (s2),
+			 NULL, NULL, &result);
+      if (ret != 0)
+        scm_encoding_error (__func__, errno,
+			    "cannot do case-folded comparison",
+			    SCM_BOOL_F,
+			    /* FIXME: Faulty character unknown.  */
+			    SCM_BOOL_F);
+      return result;
+    }
+  else
+    {
+      int swap = 1, ss1 = 0;
+      uint32_t *str2 = NULL;
+      size_t cindex1 = 0, cindex2 = 0;
+      const size_t cend1 = scm_i_string_length (s1);
+      size_t cend2;
+      ucs4_t a, b;
+
+      /* Swap so that s1 is narrow and s2 is wide.  */
+      if (scm_i_is_narrow_string (s2))
+	{
+	  SCM s3;
+	  s3 = s1;
+	  s1 = s2;
+	  s2 = s3;
+	  swap = -1;
+	}
+      str2 = u32_casefold ((const uint32_t *) scm_i_string_wide_chars (s2),
+			   scm_i_string_length (s2),
+			   NULL, NULL, NULL, &cend2);
+      if (str2 == NULL)
+	scm_memory_error (__func__);
+
+      while (cindex1 < cend1 && cindex2 < cend2)
+	{
+	  if (ss1)
+	    a = (ucs4_t) 's';
+	  else
+	    {
+	      a = uc_tolower ((unsigned char) scm_i_string_chars (s1)[cindex1]);
+	      if (a == SHARP_S)
+		{
+		  a = (ucs4_t) 's';
+		  ss1 = 2;
+		}
+	      if (a == MICRO_SIGN)
+		a = MU;
+	    }
+	  b = str2[cindex2];
+	  if (a < b)
+	    {
+	      free (str2);
+	      return -1 * swap;
+	    }
+	  else if (a > b)
+	    {
+	      free (str2);
+	      return 1 * swap;
+	    }
+	  if (ss1)
+	    ss1 --;
+	  if (!ss1) 
+	    cindex1 ++;
+	  cindex2 ++;
+	}
+      free (str2);
+      if (cindex1 < cend1)
+	return -1 * swap;
+      else if (cindex2 > cend2)
+	return 1 * swap;
+
+      return 0;
+    }
+      
+  return 0;
+}
+
+
 static SCM scm_i_string_equal_p (SCM s1, SCM s2, SCM rest);
 SCM_DEFINE (scm_i_string_equal_p, "string=?", 0, 2, 1,
             (SCM s1, SCM s2, SCM rest),
@@ -80,8 +240,8 @@ static SCM scm_i_string_ci_equal_p (SCM s1, SCM s2, SCM rest);
 SCM_DEFINE (scm_i_string_ci_equal_p, "string-ci=?", 0, 2, 1,
             (SCM s1, SCM s2, SCM rest),
 	    "Case-insensitive string equality predicate; return @code{#t} if\n"
-	    "the two strings are the same length and their component\n"
-	    "characters match (ignoring case) at each position; otherwise\n"
+	    "case-folded versions of the the two strings are the same length\n"
+            "and their component characters match at each position; otherwise\n"
 	    "return @code{#f}.")
 #define FUNC_NAME s_scm_i_string_ci_equal_p
 {
@@ -89,13 +249,13 @@ SCM_DEFINE (scm_i_string_ci_equal_p, "string-ci=?", 0, 2, 1,
     return SCM_BOOL_T;
   while (!scm_is_null (rest))
     {
-      if (scm_is_false (srfi13_cmp (s1, s2, scm_string_ci_eq)))
+      if (0 != compare_folded_strings (s1, s2))
         return SCM_BOOL_F;
       s1 = s2;
       s2 = scm_car (rest);
       rest = scm_cdr (rest);
     }
-  return srfi13_cmp (s1, s2, scm_string_ci_eq);
+  return scm_from_bool (0 == compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 
@@ -218,6 +378,7 @@ SCM scm_string_geq_p (SCM s1, SCM s2)
 }
 #undef FUNC_NAME
 
+
 static SCM scm_i_string_ci_less_p (SCM s1, SCM s2, SCM rest);
 SCM_DEFINE (scm_i_string_ci_less_p, "string-ci<?", 0, 2, 1,
             (SCM s1, SCM s2, SCM rest),
@@ -230,20 +391,20 @@ SCM_DEFINE (scm_i_string_ci_less_p, "string-ci<?", 0, 2, 1,
     return SCM_BOOL_T;
   while (!scm_is_null (rest))
     {
-      if (scm_is_false (srfi13_cmp (s1, s2, scm_string_ci_lt)))
+      if (-1 != compare_folded_strings (s1, s2))
         return SCM_BOOL_F;
       s1 = s2;
       s2 = scm_car (rest);
       rest = scm_cdr (rest);
     }
-  return srfi13_cmp (s1, s2, scm_string_ci_lt);
+  return scm_from_bool (-1 == compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 
 SCM scm_string_ci_less_p (SCM s1, SCM s2)
 #define FUNC_NAME s_scm_i_string_ci_less_p
 {
-  return srfi13_cmp (s1, s2, scm_string_ci_lt);
+  return scm_from_bool (-1 == compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 
@@ -259,20 +420,20 @@ SCM_DEFINE (scm_i_string_ci_leq_p, "string-ci<=?", 0, 2, 1,
     return SCM_BOOL_T;
   while (!scm_is_null (rest))
     {
-      if (scm_is_false (srfi13_cmp (s1, s2, scm_string_ci_le)))
+      if (1 == compare_folded_strings (s1, s2))
         return SCM_BOOL_F;
       s1 = s2;
       s2 = scm_car (rest);
       rest = scm_cdr (rest);
     }
-  return srfi13_cmp (s1, s2, scm_string_ci_le);
+  return scm_from_bool (1 != compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 
 SCM scm_string_ci_leq_p (SCM s1, SCM s2)
 #define FUNC_NAME s_scm_i_string_ci_leq_p
 {
-  return srfi13_cmp (s1, s2, scm_string_ci_le);
+  return scm_from_bool (1 != compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 
@@ -288,13 +449,13 @@ SCM_DEFINE (scm_i_string_ci_gr_p, "string-ci>?", 0, 2, 1,
     return SCM_BOOL_T;
   while (!scm_is_null (rest))
     {
-      if (scm_is_false (srfi13_cmp (s1, s2, scm_string_ci_gt)))
+      if (1 != compare_folded_strings (s1, s2))
         return SCM_BOOL_F;
       s1 = s2;
       s2 = scm_car (rest);
       rest = scm_cdr (rest);
     }
-  return srfi13_cmp (s1, s2, scm_string_ci_gt);
+  return scm_from_bool (1 == compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 
@@ -317,20 +478,20 @@ SCM_DEFINE (scm_i_string_ci_geq_p, "string-ci>=?", 0, 2, 1,
     return SCM_BOOL_T;
   while (!scm_is_null (rest))
     {
-      if (scm_is_false (srfi13_cmp (s1, s2, scm_string_ci_ge)))
+      if (-1 == compare_folded_strings (s1, s2))
         return SCM_BOOL_F;
       s1 = s2;
       s2 = scm_car (rest);
       rest = scm_cdr (rest);
     }
-  return srfi13_cmp (s1, s2, scm_string_ci_ge);
+  return scm_from_bool (-1 != compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 
 SCM scm_string_ci_geq_p (SCM s1, SCM s2)
 #define FUNC_NAME s_scm_i_string_ci_geq_p
 {
-  return srfi13_cmp (s1, s2, scm_string_ci_ge);
+  return scm_from_bool (-1 != compare_folded_strings (s1, s2));
 }
 #undef FUNC_NAME
 

^ permalink raw reply related	[flat|nested] 48+ messages in thread
* Re: Using libunistring for string comparisons et al
@ 2011-03-16  2:03 Mike Gran
  0 siblings, 0 replies; 48+ messages in thread
From: Mike Gran @ 2011-03-16  2:03 UTC (permalink / raw)
  To: Alex Shinn; +Cc: Mark H Weaver, Ludovic Courtès, guile-devel@gnu.org

> From:Alex Shinn <alexshinn@gmail.com>

> > Keep in mind that the UTF-8 forward iterator operation has conditional
> > branches.  Merely the act of advancing from one character to another
> > could take one of four paths, or more if you include the possibility
> > of invalid UTF-8 sequences.
> 
> No, technically you don't need any branching:
> 
>   /* first-byte lookup table encoded as an integer */
>   #define magic 3841982464uL
> ...

Cool.  I stand corrected.

- Mike




^ permalink raw reply	[flat|nested] 48+ messages in thread
* Re: Using libunistring for string comparisons et al
@ 2011-03-16  1:30 Mike Gran
  0 siblings, 0 replies; 48+ messages in thread
From: Mike Gran @ 2011-03-16  1:30 UTC (permalink / raw)
  To: Mark H Weaver; +Cc: Ludovic Courtès, guile-devel@gnu.org

>   (string-upcase "Straße")         => "STRAßE"  (should 

> be "STRASSE")
>   (string-downcase "ΧΑΟΣΣ")        => "χαοσσ"   (should 
> be "χαoσς")
>   (string-downcase "ΧΑΟΣ Σ")       => "χαοσ σ"  (should 
> be "χαoς σ")

Well, yes and no.  R6RS yes.  SRFI-13 no.



^ permalink raw reply	[flat|nested] 48+ messages in thread
* Re: uc_tolower (uc_toupper (x))
@ 2011-03-11  0:54 Mike Gran
  2011-03-11 22:33 ` Using libunistring for string comparisons et al Mark H Weaver
  0 siblings, 1 reply; 48+ messages in thread
From: Mike Gran @ 2011-03-11  0:54 UTC (permalink / raw)
  To: Mark H Weaver, guile-devel@gnu.org

> From:Mark H Weaver <mhw@netris.org>
> To:guile-devel@gnu.org
> Cc:
> Sent:Thursday, March 10, 2011 3:39 PM
> Subject:uc_tolower (uc_toupper (x))
> 
> I've noticed that srfi-13.c very frequently does:
> 
>   uc_tolower (uc_toupper (x))
> 
> Is there a good reason to do this instead of:
> 
>   uc_tolower (x)

Unicode defines a case folding algorithm as well as
a data table for case insensitive sorting.  Setting
things to lowercase is a decent approximation of
case folding.  But doing the upper->lower operation picks
up a few more of the corner cases, like U+03C2 GREEK
SMALL LETTER FINAL SIGMA and U+03C3 GREEK SMALL LETTER SIGMA
which are the same letter with different representations,
or U+00B5 MICRO SIGN and U+039C GREEK SMALL LETTER MU
which are supposed to have the same sort ordering.

Now that we've pulled in all of libunistring, it might
be a good idea to see if it has a complete implementation
of unicode case folding, because upper->lower is also not
completely correct.

-Mike



^ permalink raw reply	[flat|nested] 48+ messages in thread

end of thread, other threads:[~2011-03-31 20:12 UTC | newest]

Thread overview: 48+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-03-12 21:28 Using libunistring for string comparisons et al Mike Gran
2011-03-15 17:20 ` Mark H Weaver
2011-03-15 20:39   ` Mike Gran
2011-03-15 22:49     ` Mark H Weaver
2011-03-16  0:01       ` Mike Gran
2011-03-16  1:12         ` Mark H Weaver
2011-03-16 11:26           ` Ludovic Courtès
2011-03-17 15:38             ` Mark H Weaver
2011-03-17 15:56               ` Ludovic Courtès
2011-03-17 17:58                 ` Mark H Weaver
2011-03-18  0:10                   ` Thien-Thi Nguyen
2011-03-18  1:38                     ` Mark H Weaver
2011-03-18  8:46                       ` Thien-Thi Nguyen
2011-03-18 12:05                         ` Mark H Weaver
2011-03-20 22:12                   ` Ludovic Courtès
2011-03-30 10:14                     ` Andy Wingo
2011-03-17 21:47           ` Ludovic Courtès
2011-03-19 12:31           ` Andy Wingo
2011-03-19 14:06             ` Mark H Weaver
2011-03-19 14:53               ` Noah Lavine
2011-03-19 15:49                 ` Mark H Weaver
2011-03-19 15:08               ` Andy Wingo
2011-03-19 19:43                 ` Mark H Weaver
2011-03-19 16:37               ` Mark H Weaver
2011-03-20 21:49               ` Ludovic Courtès
2011-03-30  9:50               ` Andy Wingo
2011-03-29 12:39             ` Peter Brett
2011-03-29 13:35               ` Andy Wingo
2011-03-29 21:15               ` Ludovic Courtès
2011-03-31 14:59                 ` Peter Brett
2011-03-31 20:12                   ` Ludovic Courtès
2011-03-30  9:33       ` Andy Wingo
2011-03-16  0:22     ` Alex Shinn
  -- strict thread matches above, loose matches on Subject: below --
2011-03-17 18:07 Mike Gran
2011-03-16 15:22 Mike Gran
2011-03-16 16:58 ` Ludovic Courtès
2011-03-16  2:03 Mike Gran
2011-03-16  1:30 Mike Gran
2011-03-11  0:54 uc_tolower (uc_toupper (x)) Mike Gran
2011-03-11 22:33 ` Using libunistring for string comparisons et al Mark H Weaver
2011-03-11 22:36   ` Mark H Weaver
2011-03-11 23:09   ` Mark H Weaver
2011-03-12 13:46     ` Ludovic Courtès
2011-03-12 17:28       ` Mark H Weaver
2011-03-13 21:30         ` Ludovic Courtès
2011-03-30  9:05           ` Andy Wingo
2011-03-30  9:03     ` Andy Wingo
2011-03-31 14:19       ` Ludovic Courtès
2011-03-12 13:36   ` Ludovic Courtès

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).