chiark / gitweb /
Merge from disorder.dev.
[disorder] / lib / unicode.c
index 4f4f2ca80bdee6649634706e06fba21a7476b952..675f703d6e5f49ef4ee9af2d2b910271aa25a448 100644 (file)
@@ -1,21 +1,19 @@
 /*
  * This file is part of DisOrder
- * Copyright (C) 2007 Richard Kettlewell
+ * Copyright (C) 2007, 2009 Richard Kettlewell
  *
- * This program is free software; you can redistribute it and/or modify
+ * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 /** @file lib/unicode.c
  * @brief Unicode support functions
  * - @ref utf8
  */
 
-#include <config.h>
-#include "types.h"
-
-#include <string.h>
-#include <stdio.h>             /* TODO */
+#include "common.h"
 
 #include "mem.h"
 #include "vector.h"
@@ -109,6 +103,16 @@ static inline int utf32__combining_class(uint32_t c) {
   return utf32__unidata(c)->ccc;
 }
 
+/** @brief Return the combining class of @p c
+ * @param c Code point
+ * @return Combining class of @p c
+ *
+ * @p c can be any 32-bit value, a sensible value will be returned regardless.
+ */
+int utf32_combining_class(uint32_t c) {
+  return utf32__combining_class(c);
+}
+
 /** @brief Return the General_Category value for @p c
  * @param c Code point
  * @return General_Category property value
@@ -372,21 +376,10 @@ struct utf32_iterator_data {
    * the value is (uint32_t)-1.
    */
   uint32_t last[2];
-};
 
-/** @brief Create a new iterator pointing at the start of a string
- * @param s Start of string
- * @param ns Length of string
- * @return New iterator
- */
-utf32_iterator utf32_iterator_new(const uint32_t *s, size_t ns) {
-  utf32_iterator it = xmalloc(sizeof *it);
-  it->s = s;
-  it->ns = ns;
-  it->n = 0;
-  it->last[0] = it->last[1] = -1;
-  return it;
-}
+  /** @brief Tailoring for Word_Break */
+  unicode_property_tailor *word_break;
+};
 
 /** @brief Initialize an internal private iterator
  * @param it Iterator
@@ -400,9 +393,54 @@ static void utf32__iterator_init(utf32_iterator it,
   it->ns = ns;
   it->n = 0;
   it->last[0] = it->last[1] = -1;
+  it->word_break = 0;
   utf32_iterator_set(it, n);
 }
 
+/** @brief Create a new iterator pointing at the start of a string
+ * @param s Start of string
+ * @param ns Length of string
+ * @return New iterator
+ */
+utf32_iterator utf32_iterator_new(const uint32_t *s, size_t ns) {
+  utf32_iterator it = xmalloc(sizeof *it);
+  utf32__iterator_init(it, s, ns, 0);
+  return it;
+}
+
+/** @brief Tailor this iterator's interpretation of the Word_Break property.
+ * @param it Iterator
+ * @param pt Property tailor function or NULL
+ *
+ * After calling this the iterator will call @p pt to determine the Word_Break
+ * property of each code point.  If it returns -1 the default value will be
+ * used otherwise the returned value will be used.
+ *
+ * @p pt can be NULL to revert to the default value of the property.
+ *
+ * It is safe to call this function at any time; the iterator's internal state
+ * will be reset to suit the new tailoring.
+ */
+void utf32_iterator_tailor_word_break(utf32_iterator it,
+                                      unicode_property_tailor *pt) {
+  it->word_break = pt;
+  utf32_iterator_set(it, it->n);
+}
+
+static inline enum unicode_Word_Break utf32__iterator_word_break(utf32_iterator it,
+                                                                 uint32_t c) {
+  if(!it->word_break)
+    return utf32__word_break(c);
+  else {
+    const int t = it->word_break(c);
+
+    if(t < 0)
+      return utf32__word_break(c);
+    else
+      return t;
+  }
+}
+
 /** @brief Destroy an iterator
  * @param it Iterator
  */
@@ -444,14 +482,18 @@ int utf32_iterator_set(utf32_iterator it, size_t n) {
     return -1;
   /* Walk backwards skipping ignorable code points */
   m = n;
-  while(m > 0 && (utf32__boundary_ignorable(utf32__word_break(it->s[m-1]))))
+  while(m > 0
+        && (utf32__boundary_ignorable(utf32__iterator_word_break(it,
+                                                                 it->s[m-1]))))
     --m;
   /* Either m=0 or s[m-1] is not ignorable */
   if(m > 0) {
     --m;
     /* s[m] is our first non-ignorable code; look for a second in the same
        way **/
-    while(m > 0 && (utf32__boundary_ignorable(utf32__word_break(it->s[m-1]))))
+    while(m > 0
+          && (utf32__boundary_ignorable(utf32__iterator_word_break(it,
+                                                                   it->s[m-1]))))
       --m;
     /* Either m=0 or s[m-1] is not ignorable */
     if(m > 0)
@@ -478,7 +520,7 @@ int utf32_iterator_advance(utf32_iterator it, size_t count) {
   if(count <= it->ns - it->n) {
     while(count > 0) {
       const uint32_t c = it->s[it->n];
-      const enum unicode_Word_Break wb = utf32__word_break(c);
+      const enum unicode_Word_Break wb = utf32__iterator_word_break(it, c);
       if(it->last[1] == (uint32_t)-1
          || !utf32__boundary_ignorable(wb)) {
         it->last[0] = it->last[1];
@@ -561,6 +603,12 @@ int utf32_iterator_grapheme_boundary(utf32_iterator it) {
   /* GB9 */
   if(gbafter == unicode_Grapheme_Break_Extend)
     return 0;
+  /* GB9a */
+  if(gbafter == unicode_Grapheme_Break_SpacingMark)
+    return 0;
+  /* GB9b */
+  if(gbbefore == unicode_Grapheme_Break_Prepend)
+    return 0;
   /* GB10 */
   return 1;
 
@@ -576,94 +624,112 @@ int utf32_iterator_grapheme_boundary(utf32_iterator it) {
  * string) and 0 otherwise.
  */
 int utf32_iterator_word_boundary(utf32_iterator it) {
-  enum unicode_Word_Break twobefore, before, after, twoafter;
+  uint32_t before, after;
+  enum unicode_Word_Break wbtwobefore, wbbefore, wbafter, wbtwoafter;
   size_t nn;
 
   /* WB1 and WB2 */
   if(it->n == 0 || it->n == it->ns)
     return 1;
+  before = it->s[it->n-1];
+  after = it->s[it->n];
   /* WB3 */
-  if(it->s[it->n-1] == 0x000D && it->s[it->n] == 0x000A)
+  if(before == 0x000D && after == 0x000A)
     return 0;
+  /* WB3a */
+  if(utf32__iterator_word_break(it, before) == unicode_Word_Break_Newline
+     || before == 0x000D
+     || before == 0x000A)
+    return 1;
+  /* WB3b */
+  if(utf32__iterator_word_break(it, after) == unicode_Word_Break_Newline
+     || after == 0x000D
+     || after == 0x000A)
+    return 1;
   /* WB4 */
   /* (!Sep) x (Extend|Format) as in UAX #29 s6.2 */
-  if(utf32__sentence_break(it->s[it->n-1]) != unicode_Sentence_Break_Sep
-     && utf32__boundary_ignorable(utf32__word_break(it->s[it->n])))
+  if(utf32__sentence_break(before) != unicode_Sentence_Break_Sep
+     && utf32__boundary_ignorable(utf32__iterator_word_break(it, after)))
     return 0;
   /* Gather the property values we'll need for the rest of the test taking the
    * s6.2 changes into account */
   /* First we look at the code points after the proposed boundary */
   nn = it->n;                           /* <it->ns */
-  after = utf32__word_break(it->s[nn++]);
-  if(!utf32__boundary_ignorable(after)) {
+  wbafter = utf32__iterator_word_break(it, it->s[nn++]);
+  if(!utf32__boundary_ignorable(wbafter)) {
     /* X (Extend|Format)* -> X */
     while(nn < it->ns
-          && utf32__boundary_ignorable(utf32__word_break(it->s[nn])))
+          && utf32__boundary_ignorable(utf32__iterator_word_break(it,
+                                                                  it->s[nn])))
       ++nn;
   }
   /* It's possible now that nn=ns */
   if(nn < it->ns)
-    twoafter = utf32__word_break(it->s[nn]);
+    wbtwoafter = utf32__iterator_word_break(it, it->s[nn]);
   else
-    twoafter = unicode_Word_Break_Other;
+    wbtwoafter = unicode_Word_Break_Other;
 
   /* We've already recorded the non-ignorable code points before the proposed
    * boundary */
-  before = utf32__word_break(it->last[1]);
-  twobefore = utf32__word_break(it->last[0]);
+  wbbefore = utf32__iterator_word_break(it, it->last[1]);
+  wbtwobefore = utf32__iterator_word_break(it, it->last[0]);
 
   /* WB5 */
-  if(before == unicode_Word_Break_ALetter
-     && after == unicode_Word_Break_ALetter)
+  if(wbbefore == unicode_Word_Break_ALetter
+     && wbafter == unicode_Word_Break_ALetter)
     return 0;
   /* WB6 */
-  if(before == unicode_Word_Break_ALetter
-     && after == unicode_Word_Break_MidLetter
-     && twoafter == unicode_Word_Break_ALetter)
+  if(wbbefore == unicode_Word_Break_ALetter
+     && (wbafter == unicode_Word_Break_MidLetter
+         || wbafter == unicode_Word_Break_MidNumLet)
+     && wbtwoafter == unicode_Word_Break_ALetter)
     return 0;
   /* WB7 */
-  if(twobefore == unicode_Word_Break_ALetter
-     && before == unicode_Word_Break_MidLetter
-     && after == unicode_Word_Break_ALetter)
+  if(wbtwobefore == unicode_Word_Break_ALetter
+     && (wbbefore == unicode_Word_Break_MidLetter
+         || wbbefore == unicode_Word_Break_MidNumLet)
+     && wbafter == unicode_Word_Break_ALetter)
     return 0;
-  /* WB8 */  
-  if(before == unicode_Word_Break_Numeric
-     && after == unicode_Word_Break_Numeric)
+  /* WB8 */
+  if(wbbefore == unicode_Word_Break_Numeric
+     && wbafter == unicode_Word_Break_Numeric)
     return 0;
   /* WB9 */
-  if(before == unicode_Word_Break_ALetter
-     && after == unicode_Word_Break_Numeric)
+  if(wbbefore == unicode_Word_Break_ALetter
+     && wbafter == unicode_Word_Break_Numeric)
     return 0;
   /* WB10 */
-  if(before == unicode_Word_Break_Numeric
-     && after == unicode_Word_Break_ALetter)
+  if(wbbefore == unicode_Word_Break_Numeric
+     && wbafter == unicode_Word_Break_ALetter)
     return 0;
    /* WB11 */
-  if(twobefore == unicode_Word_Break_Numeric
-     && before == unicode_Word_Break_MidNum
-     && after == unicode_Word_Break_Numeric)
+  if(wbtwobefore == unicode_Word_Break_Numeric
+     && (wbbefore == unicode_Word_Break_MidNum
+         || wbbefore == unicode_Word_Break_MidNumLet)
+     && wbafter == unicode_Word_Break_Numeric)
     return 0;
   /* WB12 */
-  if(before == unicode_Word_Break_Numeric
-     && after == unicode_Word_Break_MidNum
-     && twoafter == unicode_Word_Break_Numeric)
+  if(wbbefore == unicode_Word_Break_Numeric
+     && (wbafter == unicode_Word_Break_MidNum
+         || wbafter == unicode_Word_Break_MidNumLet)
+     && wbtwoafter == unicode_Word_Break_Numeric)
     return 0;
   /* WB13 */
-  if(before == unicode_Word_Break_Katakana
-     && after == unicode_Word_Break_Katakana)
+  if(wbbefore == unicode_Word_Break_Katakana
+     && wbafter == unicode_Word_Break_Katakana)
     return 0;
   /* WB13a */
-  if((before == unicode_Word_Break_ALetter
-      || before == unicode_Word_Break_Numeric
-      || before == unicode_Word_Break_Katakana
-      || before == unicode_Word_Break_ExtendNumLet)
-     && after == unicode_Word_Break_ExtendNumLet)
+  if((wbbefore == unicode_Word_Break_ALetter
+      || wbbefore == unicode_Word_Break_Numeric
+      || wbbefore == unicode_Word_Break_Katakana
+      || wbbefore == unicode_Word_Break_ExtendNumLet)
+     && wbafter == unicode_Word_Break_ExtendNumLet)
     return 0;
   /* WB13b */
-  if(before == unicode_Word_Break_ExtendNumLet
-     && (after == unicode_Word_Break_ALetter
-         || after == unicode_Word_Break_Numeric
-         || after == unicode_Word_Break_Katakana))
+  if(wbbefore == unicode_Word_Break_ExtendNumLet
+     && (wbafter == unicode_Word_Break_ALetter
+         || wbafter == unicode_Word_Break_Numeric
+         || wbafter == unicode_Word_Break_Katakana))
     return 0;
   /* WB14 */
   return 1;
@@ -1271,6 +1337,64 @@ int utf32_is_word_boundary(const uint32_t *s, size_t ns, size_t n) {
   return utf32_iterator_word_boundary(it);
 }
 
+/** @brief Split [s,ns) into multiple words
+ * @param s Pointer to start of string
+ * @param ns Length of string
+ * @param nwp Where to store word count, or NULL
+ * @param wbreak Word_Break property tailor, or NULL
+ * @return Pointer to array of pointers to words
+ *
+ * The returned array is terminated by a NULL pointer and individual
+ * strings are 0-terminated.
+ */
+uint32_t **utf32_word_split(const uint32_t *s, size_t ns, size_t *nwp,
+                            unicode_property_tailor *wbreak) {
+  struct utf32_iterator_data it[1];
+  size_t b1 = 0, b2 = 0 ,i;
+  int isword;
+  struct vector32 v32[1];
+  uint32_t *w;
+
+  vector32_init(v32);
+  utf32__iterator_init(it, s, ns, 0);
+  it->word_break = wbreak;
+  /* Work our way through the string stopping at each word break. */
+  do {
+    if(utf32_iterator_word_boundary(it)) {
+      /* We've found a new boundary */
+      b1 = b2;
+      b2 = it->n;
+      /*fprintf(stderr, "[%zu, %zu) is a candidate word\n", b1, b2);*/
+      /* Inspect the characters between the boundary and form an opinion as to
+       * whether they are a word or not */
+      isword = 0;
+      for(i = b1; i < b2; ++i) {
+        switch(utf32__iterator_word_break(it, it->s[i])) {
+        case unicode_Word_Break_ALetter:
+        case unicode_Word_Break_Numeric:
+        case unicode_Word_Break_Katakana:
+          isword = 1;
+          break;
+        default:
+          break;
+        }
+      }
+      /* If it's a word add it to the list of results */
+      if(isword) {
+        const size_t len = b2 - b1;
+        w = xcalloc_noptr(len + 1, sizeof(uint32_t));
+        memcpy(w, it->s + b1, len * sizeof (uint32_t));
+        w[len] = 0;
+        vector32_append(v32, w);
+      }
+    }
+  } while(!utf32_iterator_advance(it, 1));
+  vector32_terminate(v32);
+  if(nwp)
+    *nwp = v32->nvec;
+  return v32->vec;
+}
+
 /*@}*/
 /** @defgroup utf8 Functions that operate on UTF-8 strings */
 /*@{*/
@@ -1411,6 +1535,47 @@ char *utf8_casefold_compat(const char *s, size_t ns, size_t *ndp) {
   utf8__transform(utf32_casefold_compat);
 }
 
+/** @brief Split [s,ns) into multiple words
+ * @param s Pointer to start of string
+ * @param ns Length of string
+ * @param nwp Where to store word count, or NULL
+ * @param wbreak Word_Break property tailor, or NULL
+ * @return Pointer to array of pointers to words
+ *
+ * The returned array is terminated by a NULL pointer and individual
+ * strings are 0-terminated.
+ */
+char **utf8_word_split(const char *s, size_t ns, size_t *nwp,
+                       unicode_property_tailor *wbreak) {
+  uint32_t *to32 = 0, **v32 = 0;
+  size_t nto32, nv, n;
+  char **v8 = 0, **ret = 0;
+
+  if(!(to32 = utf8_to_utf32(s, ns, &nto32))) goto error;
+  if(!(v32 = utf32_word_split(to32, nto32, &nv, wbreak))) goto error;
+  v8 = xcalloc(sizeof (char *), nv + 1);
+  for(n = 0; n < nv; ++n)
+    if(!(v8[n] = utf32_to_utf8(v32[n], utf32_len(v32[n]), 0)))
+      goto error;
+  ret = v8;
+  *nwp = nv;
+  v8 = 0;                               /* don't free */
+error:
+  if(v8) {
+    for(n = 0; n < nv; ++n)
+      xfree(v8[n]);
+    xfree(v8);
+  }
+  if(v32) {
+    for(n = 0; n < nv; ++n)
+      xfree(v32[n]);
+    xfree(v32);
+  }
+  xfree(to32);
+  return ret;
+}
+
+
 /*@}*/
 
 /*