packages feed

text-short 0.1.1 → 0.1.2

raw patch · 10 files changed

+2801/−323 lines, 10 filesdep +ghc-primdep ~basedep ~hashabledep ~semigroupsPVP ok

version bump matches the API change (PVP)

Dependencies added: ghc-prim

Dependency ranges changed: base, hashable, semigroups, tasty, tasty-hunit, tasty-quickcheck

API changes (from Hackage documentation)

+ Data.Text.Short: (!?) :: ShortText -> Int -> Maybe Char
+ Data.Text.Short: all :: (Char -> Bool) -> ShortText -> Bool
+ Data.Text.Short: any :: (Char -> Bool) -> ShortText -> Bool
+ Data.Text.Short: append :: ShortText -> ShortText -> ShortText
+ Data.Text.Short: break :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)
+ Data.Text.Short: breakEnd :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)
+ Data.Text.Short: concat :: [ShortText] -> ShortText
+ Data.Text.Short: cons :: Char -> ShortText -> ShortText
+ Data.Text.Short: drop :: Int -> ShortText -> ShortText
+ Data.Text.Short: dropAround :: (Char -> Bool) -> ShortText -> ShortText
+ Data.Text.Short: dropEnd :: Int -> ShortText -> ShortText
+ Data.Text.Short: dropWhile :: (Char -> Bool) -> ShortText -> ShortText
+ Data.Text.Short: dropWhileEnd :: (Char -> Bool) -> ShortText -> ShortText
+ Data.Text.Short: empty :: ShortText
+ Data.Text.Short: filter :: (Char -> Bool) -> ShortText -> ShortText
+ Data.Text.Short: find :: (Char -> Bool) -> ShortText -> Maybe Char
+ Data.Text.Short: findIndex :: (Char -> Bool) -> ShortText -> Maybe Int
+ Data.Text.Short: foldl :: (a -> Char -> a) -> a -> ShortText -> a
+ Data.Text.Short: foldl' :: (a -> Char -> a) -> a -> ShortText -> a
+ Data.Text.Short: foldr :: (Char -> a -> a) -> a -> ShortText -> a
+ Data.Text.Short: indexEndMaybe :: ShortText -> Int -> Maybe Char
+ Data.Text.Short: indexMaybe :: ShortText -> Int -> Maybe Char
+ Data.Text.Short: intercalate :: ShortText -> [ShortText] -> ShortText
+ Data.Text.Short: intersperse :: Char -> ShortText -> ShortText
+ Data.Text.Short: isPrefixOf :: ShortText -> ShortText -> Bool
+ Data.Text.Short: isSuffixOf :: ShortText -> ShortText -> Bool
+ Data.Text.Short: pack :: [Char] -> ShortText
+ Data.Text.Short: replicate :: Int -> ShortText -> ShortText
+ Data.Text.Short: reverse :: ShortText -> ShortText
+ Data.Text.Short: singleton :: Char -> ShortText
+ Data.Text.Short: snoc :: ShortText -> Char -> ShortText
+ Data.Text.Short: span :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)
+ Data.Text.Short: spanEnd :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)
+ Data.Text.Short: splitAt :: Int -> ShortText -> (ShortText, ShortText)
+ Data.Text.Short: splitAtEnd :: Int -> ShortText -> (ShortText, ShortText)
+ Data.Text.Short: stripPrefix :: ShortText -> ShortText -> Maybe ShortText
+ Data.Text.Short: stripSuffix :: ShortText -> ShortText -> Maybe ShortText
+ Data.Text.Short: take :: Int -> ShortText -> ShortText
+ Data.Text.Short: takeEnd :: Int -> ShortText -> ShortText
+ Data.Text.Short: takeWhile :: (Char -> Bool) -> ShortText -> ShortText
+ Data.Text.Short: takeWhileEnd :: (Char -> Bool) -> ShortText -> ShortText
+ Data.Text.Short: uncons :: ShortText -> Maybe (Char, ShortText)
+ Data.Text.Short: unpack :: ShortText -> [Char]
+ Data.Text.Short: unsnoc :: ShortText -> Maybe (ShortText, Char)
+ Data.Text.Short.Partial: foldl1 :: (Char -> Char -> Char) -> ShortText -> Char
+ Data.Text.Short.Partial: foldl1' :: (Char -> Char -> Char) -> ShortText -> Char
+ Data.Text.Short.Partial: foldr1 :: (Char -> Char -> Char) -> ShortText -> Char
+ Data.Text.Short.Partial: head :: ShortText -> Char
+ Data.Text.Short.Partial: index :: ShortText -> Int -> Char
+ Data.Text.Short.Partial: init :: ShortText -> ShortText
+ Data.Text.Short.Partial: last :: ShortText -> Char
+ Data.Text.Short.Partial: tail :: ShortText -> ShortText

Files

ChangeLog.md view
@@ -1,10 +1,72 @@-# Revision history for `text-short`+## 0.1.2 -## 0.1+  * Add `IsList ShortText` and `PrintfArg ShortText` instances+  * Expose partial functions via new `Data.Text.Short.Partial` module -* First version. Released on an unsuspecting world.+        foldl1 :: (Char -> Char -> Char) -> ShortText -> Char+        foldl1' :: (Char -> Char -> Char) -> ShortText -> Char+        foldr1 :: (Char -> Char -> Char) -> ShortText -> Char+        head :: ShortText -> Char+        index :: ShortText -> Int -> Char+        init :: ShortText -> ShortText+        last :: ShortText -> Char+        tail :: ShortText -> ShortText +  * Add several `Data.Text` verbs to `Data.Text.Short` API++        (!?) :: ShortText -> Int -> Maybe Char+        all :: (Char -> Bool) -> ShortText -> Bool+        any :: (Char -> Bool) -> ShortText -> Bool+        append :: ShortText -> ShortText -> ShortText+        break :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)+        breakEnd :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)+        concat :: [ShortText] -> ShortText+        cons :: Char -> ShortText -> ShortText+        drop :: Int -> ShortText -> ShortText+        dropAround :: (Char -> Bool) -> ShortText -> ShortText+        dropEnd :: Int -> ShortText -> ShortText+        dropWhile :: (Char -> Bool) -> ShortText -> ShortText+        dropWhileEnd :: (Char -> Bool) -> ShortText -> ShortText+        empty :: ShortText+        filter :: (Char -> Bool) -> ShortText -> ShortText+        find :: (Char -> Bool) -> ShortText -> Maybe Char+        findIndex :: (Char -> Bool) -> ShortText -> Maybe Int+        foldl :: (a -> Char -> a) -> a -> ShortText -> a+        foldl' :: (a -> Char -> a) -> a -> ShortText -> a+        foldr :: (Char -> a -> a) -> a -> ShortText -> a+        indexEndMaybe :: ShortText -> Int -> Maybe Char+        indexMaybe :: ShortText -> Int -> Maybe Char+        intercalate :: ShortText -> [ShortText] -> ShortText+        intersperse :: Char -> ShortText -> ShortText+        isPrefixOf :: ShortText -> ShortText -> Bool+        isSuffixOf :: ShortText -> ShortText -> Bool+        pack :: [Char] -> ShortText+        replicate :: Int -> ShortText -> ShortText+        reverse :: ShortText -> ShortText+        singleton :: Char -> ShortText+        snoc :: ShortText -> Char -> ShortText+        span :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)+        spanEnd :: (Char -> Bool) -> ShortText -> (ShortText, ShortText)+        splitAt :: Int -> ShortText -> (ShortText, ShortText)+        splitAtEnd :: Int -> ShortText -> (ShortText, ShortText)+        stripPrefix :: ShortText -> ShortText -> Maybe ShortText+        stripSuffix :: ShortText -> ShortText -> Maybe ShortText+        take :: Int -> ShortText -> ShortText+        takeEnd :: Int -> ShortText -> ShortText+        takeWhile :: (Char -> Bool) -> ShortText -> ShortText+        takeWhileEnd :: (Char -> Bool) -> ShortText -> ShortText+        uncons :: ShortText -> Maybe (Char, ShortText)+        unpack :: ShortText -> [Char]+        unsnoc :: ShortText -> Maybe (ShortText, Char)++  * Optimise low-level primitives+  * Add support for GHC 8.4+ ## 0.1.1  * Expose *unsafe* conversion API via `Data.Text.Short.Unsafe` module * Minor documentation improvement++## 0.1++* First version. Released on an unsuspecting world.
cbits/cbits.c view
@@ -1,23 +1,23 @@ /*  * Copyright (c) 2017, Herbert Valerio Riedel- *  + *  * All rights reserved.- *  + *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions are met:- *  + *  *     * Redistributions of source code must retain the above copyright  *       notice, this list of conditions and the following disclaimer.- *  + *  *     * Redistributions in binary form must reproduce the above  *       copyright notice, this list of conditions and the following  *       disclaimer in the documentation and/or other materials provided  *       with the distribution.- *  + *  *     * Neither the name of Herbert Valerio Riedel nor the names of other  *       contributors may be used to endorse or promote products derived  *       from this software without specific prior written permission.- *  + *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR@@ -31,28 +31,326 @@  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ +#if !defined(NDEBUG)+# warning assert(3) checks enabled+#endif+ #include <stdint.h>+#include <stdbool.h> #include <string.h>+#include <stdlib.h> #include <assert.h>+#include <HsFFI.h> +#if !defined(SIZEOF_VOID_P)+# error <HsFFI.h> SIZEOF_VOID_P not defined+#endif++#if (SIZEOF_VOID_P) == 8+const static bool is_64bit = true;+#elif (SIZEOF_VOID_P) == 4+const static bool is_64bit = false;+#else+# error unexpected SIZEOF_VOID_P value+#endif++#if (WORDS_BIGENDIAN)+const static bool is_bigendian = true;+#else+const static bool is_bigendian = false;+#endif++#if defined(__GNUC__)+# define likely(x)     __builtin_expect(!!(x),1)+# define unlikely(x)   __builtin_expect(!!(x),0)+#else+# define likely(x)     (x)+# define unlikely(x)   (x)+#endif++/* test whether octet in UTF-8 steam is not a continuation byte, i.e. a leading byte */+#define utf8_lead_p(octet) (((octet) & 0xc0) != 0x80)++/* 0 <= x <= 0x110000 */+typedef HsWord codepoint_t;+ /* Count number of code-points in well-formed utf8 string */ size_t hs_text_short_length(const uint8_t buf[], const size_t n) {-  size_t j,l = 0;+  size_t j = 0;+  size_t l = 0;++  /* Both GCC & Clang are able to optimise the code below quite well at -O3 */   for (j = 0; j < n; j++)-    if ((buf[j] & 0xc0) != 0x80)+    if (utf8_lead_p(buf[j]))       l++;    return l; } +/* Locate offset of j-th code-point in well-formed utf8 string+ *+ */+size_t+hs_text_short_index_ofs(const uint8_t buf[], const size_t n, const size_t i)+{+  if (!n)+    return n;++  size_t m = 0;+  size_t j = 0;++  for (;;) {+    assert(m >= 0);+    assert(j <= i);+    assert(j <= m);++    if (j == i)+      return m; /* found */++    if (i-j >= n-m)+      return n; /* i-th char is >= buf+n */++    assert(m < n);+    const uint8_t b0 = buf[m];++    if (!(b0 & 0x80))+      m += 1;   /* 0_______ */+    else+      switch(b0 >> 4) {+      case 0xf: /* 11110___ */+        m += 4;+        break;+      case 0xe: /* 1110____ */+        m += 3;+        break;+      default:  /* 110_____ */+        m += 2;+        break;+      }++    j += 1;+  }++  assert(0);+}++/* Locate offset of j-th code-point (in reverse direction) in+ * well-formed utf8 string starting at end of buffer.+ *+ * The 0-th character from the end is the last character in the utf8+ * string (if it exists).+ *+ * Returns original 'n' if out of bounds.+ *+ */+size_t+hs_text_short_index_ofs_rev(const uint8_t buf[], const size_t n, const size_t i)+{+  size_t m = n;+  size_t j = i;++  for (;;) {+    assert(m <= n);+    assert(j >= 0);++    if (j >= m)+      return n; /* i-th char is < buf */++    /* if (m == i-j) /\* suffix is made up only of ASCII chars, so we can shortcut *\/ */+    /*   return 0; */++    /* scan until octet does not match 10_ */+    assert(m > 0);+    if (!(buf[--m] & 0x80))+      goto l_cont;++    assert(m > 0);+    if (utf8_lead_p(buf[--m])) {+      assert ((buf[m] & 0xe0) == 0xc0); /* 110_ */+      goto l_cont;+    }++    assert(m > 0);+    if (utf8_lead_p(buf[--m])) {+      assert ((buf[m] & 0xf0) == 0xe0); /* 1110_ */+      goto l_cont;+    }++    /* this must be a non-10_ octet in a well-formed stream */+    assert(m > 0);+    m -= 1;++    assert ((buf[m] & 0xf8) == 0xf0); /* 11110_ */++  l_cont:+    assert(utf8_lead_p(buf[m]));++    if (!j)+      return m; /* found */++    j -= 1;+  }++  assert(0);+}++/* Decode UTF8 code units into code-point+ * Assumes buf[] points to start of a valid UTF8-encoded code-point+ */+static inline uint32_t+hs_text_short_decode_cp(const uint8_t buf[])+{+  /*  7 bits | 0xxxxxxx+   * 11 bits | 110yyyyx  10xxxxxx+   * 16 bits | 1110yyyy  10yxxxxx  10xxxxxx+   * 21 bits | 11110yyy  10yyxxxx  10xxxxxx  10xxxxxx+   */++  const uint8_t b0 = buf[0];++  if (!(b0 & 0x80))+    return b0;++  uint32_t cp = 0;++  switch(b0 >> 4) {+  case 0xf: /* 11110___ */+    assert((b0 & 0xf8) == 0xf0);+    assert(!utf8_lead_p(buf[1]));+    assert(!utf8_lead_p(buf[2]));+    assert(!utf8_lead_p(buf[3]));+    cp  = ((uint32_t)(b0     & 0x07)) << (6+6+6);+    cp |= ((uint32_t)(buf[1] & 0x3f)) << (6+6);+    cp |= ((uint32_t)(buf[2] & 0x3f)) << 6;+    cp |=             buf[3] & 0x3f;+    assert (cp > 0xffff); assert (cp < 0x110000);+    return cp;++  case 0xe: /* 1110____ */+    assert(!utf8_lead_p(buf[1]));+    assert(!utf8_lead_p(buf[2]));+    cp  = ((uint32_t)(b0     & 0x0f)) << (6+6);+    cp |= ((uint32_t)(buf[1] & 0x3f)) << 6;+    cp |=             buf[2] & 0x3f;+    assert (cp > 0x7ff); assert (cp < 0x10000);+    assert (cp < 0xd800 || cp > 0xdfff);+    return cp;++  default:  /* 110_____ */+    assert((b0 & 0xe0) == 0xc0);+    assert(!utf8_lead_p(buf[1]));+    cp  = ((uint32_t)(b0     & 0x1f)) << 6;+    cp |=             buf[1] & 0x3f;+    assert (cp > 0x7f); assert (cp < 0x800);+    return cp;+  }+}++/* decode codepoint starting at buf[ofs] */+codepoint_t+hs_text_short_ofs_cp(const uint8_t buf[], const size_t ofs)+{+  return hs_text_short_decode_cp(buf+ofs);+}++/* reverse-decode codepoint starting at offset right after a code-point */+codepoint_t+hs_text_short_ofs_cp_rev(const uint8_t *buf, const size_t ofs)+{+  /*  7 bits | 0xxxxxxx+   * 11 bits | 110yyyyx  10xxxxxx+   * 16 bits | 1110yyyy  10yxxxxx  10xxxxxx+   * 21 bits | 11110yyy  10yyxxxx  10xxxxxx  10xxxxxx+   */++  buf = buf + ofs - 1;++  /* this octet is either 10_ or 0_ */+  uint32_t cp = *buf;++  if (!(cp & 0x80))+    return cp;++  assert (!utf8_lead_p(cp));+  cp &= 0x3f;++  /* this octet is either 10_ or 110_ */+  {+    const uint8_t b = *(--buf);+    assert (!utf8_lead_p(b) || ((b & 0xe0) == 0xc0));++    cp |=  (b & 0x3f) << 6;++    if (b & 0x40) {+      assert (cp > 0x7f); assert (cp < 0x800);+      return cp;+    }+  }++  /* this octet is either 10_ or 1110_ */+  {+    const uint8_t b = *(--buf);+    assert (!utf8_lead_p(b) || ((b & 0xf0) == 0xe0));++    if (b & 0x40) {+      cp |= (b & 0xf) << 12;++      assert (cp > 0x7ff); assert (cp < 0x10000);+      assert (cp < 0xd800 || cp > 0xdfff);+      return cp;+    }++    cp |= (b & 0x3f) << 12;+  }++  /* this octet must be 11110_ */+  const uint8_t b = *(buf-1);+  assert ((b & 0xf8) == 0xf0);++  cp |= (b & 0x7) << 18;++  assert (cp > 0xffff); assert (cp < 0x110000);+  return cp;+}++/* Retrieve i-th code-point in (valid) UTF8 stream+ *+ * Returns -1 if out of bounds+ */+codepoint_t+hs_text_short_index_cp(const uint8_t buf[], const size_t n, const size_t i)+{+  const size_t ofs = hs_text_short_index_ofs(buf, n, i);++  if (ofs >= n)+    return -1;++  return hs_text_short_decode_cp(&buf[ofs]);+}++/* Retrieve i-th code-point in (valid) UTF8 stream+ *+ * Returns -1 if out of bounds+ */+codepoint_t+hs_text_short_index_cp_rev(const uint8_t buf[], const size_t n, const size_t i)+{+  const size_t ofs = hs_text_short_index_ofs_rev(buf, n, i);++  if (ofs >= n)+    return -1;++  return hs_text_short_decode_cp(&buf[ofs]);+}+ /* Validate UTF8 encoding   7 bits | 0xxxxxxx-11 bits | 110yyyyx  10xxxxxx -16 bits | 1110yyyy  10yxxxxx  10xxxxxx -21 bits | 11110yyy  10yyxxxx  10xxxxxx  10xxxxxx +11 bits | 110yyyyx  10xxxxxx+16 bits | 1110yyyy  10yxxxxx  10xxxxxx+21 bits | 11110yyy  10yyxxxx  10xxxxxx  10xxxxxx  Valid code-points: @@ -60,10 +358,14 @@  Return values: - 0 -> ok- 1 -> invalid byte/code-point- 2 -> truncated+  0 -> ok +  1 -> invalid byte/code-point++ -1 -> truncated (1 byte missing)+ -2 -> truncated (2 byte missing)+ -3 -> truncated (3 byte missing)+ */  int@@ -75,50 +377,66 @@     const uint8_t b0 = buf[j++];      if (!(b0 & 0x80))-      continue;+      continue; /* b0 elem [ 0x00 .. 0x7f ] */ -    if ((b0 & 0xe0) == 0xc0) {-      if (!(b0 & 0x1e)) return 1; /* denorm */-      if (j >= n) return 2;-      -      /* b1 */-      if ((buf[j++] & 0xc0) != 0x80) return 1;-      continue;+    if ((b0 & 0xe0) == 0xc0) { /* [ 0xc0 .. 0xdf ] */+      if (!(b0 & 0x1e)) return 1; /* 0xc0 or 0xc1; denorm */+      if (j >= n) return -1;++      goto l_trail1; /* b1 */     } -    if ((b0 & 0xf0) == 0xe0) {-      if ((j+1) >= n) return 2;+    if ((b0 & 0xf0) == 0xe0) { /* [ 0xe0 .. 0xef ] */+      if ((j+1) >= n) return (n-(j+2));        const uint8_t b1 = buf[j++];-      if ((b1 & 0xc0) != 0x80) return 1;+      if (utf8_lead_p(b1)) return 1; /* b1 elem [ 0x80 .. 0xbf ] */++      /* if b0==0xe0: b1 elem [ 0xa0 .. 0xbf ] */       if (!((b0 & 0x0f) | (b1 & 0x20))) return 1; /* denorm */+       /* UTF16 Surrogate pairs [U+D800 .. U+DFFF] */+      /* if b0==0xed: b1 elem [ 0x80 .. 0x9f ] */       if ((b0 == 0xed) && (b1 & 0x20)) return 1;-      -      /* b2 */-      if ((buf[j++] & 0xc0) != 0x80) return 1;-        -      continue;++      goto l_trail1; /* b2 */     } -    if ((b0 & 0xf8) == 0xf0) {-      if ((j+2) >= n) return 2;-      +    if ((b0 & 0xfc) == 0xf0) { /* [ 0xf0 .. 0xf3 ] */+      if ((j+2) >= n) return (n-(j+3));+       const uint8_t b1 = buf[j++];-      if ((b1 & 0xc0) != 0x80) return 1;-      if (!((b0 & 0x07) | (b1 & 0x30))) return 1; /* denorm */-      /* make sure we're below U+10FFFF */-      if (b0 > 0xf4) return 1;-      if ((b0 == 0xf4) && (b1 & 0x30)) return 1;-      ++      if (utf8_lead_p(b1))         /* b1 elem [ 0x80 .. 0xbf ] */+        return 1;++      if (!((b0 & 0x03) | (b1 & 0x30))) /* if b0==0xf0: b1 elem [ 0x90 .. 0xbf ] */+        return 1;++      goto l_trail2; /* b1, b2 */+    }++    if (b0 == 0xf4) {+      if ((j+2) >= n) return (n-(j+3));++      /* b1 */+      if ((buf[j++] & 0xf0) != 0x80) return 1;+      /* b1 elem [ 0x80 .. 0x8f ] */++    l_trail2:       /* b2 */-      if ((buf[j++] & 0xc0) != 0x80) return 1;+      if (utf8_lead_p(buf[j++])) return 1;+      /* b2 elem [ 0x80 .. 0xbf ] */++    l_trail1:       /* b3 */-      if ((buf[j++] & 0xc0) != 0x80) return 1;+      if (utf8_lead_p(buf[j++])) return 1;+      /* b3 elem [ 0x80 .. 0xbf ] */        continue;     }-    ++    /* invalid b0 byte */     return 1;   } @@ -128,14 +446,212 @@ }  -/* Test whether well-formed UTF8 string contains only ASCII code-points- * Returns length of longest ASCII-code-point prefix.+/* Returns length of longest ASCII-code-point prefix.  */ size_t-hs_text_short_is_ascii(const uint8_t buf[], const size_t n)+hs_text_short_ascii_length(const uint8_t buf[], const size_t n) {-  size_t j;-  for (j = 0; j < n; j++)+  size_t j = 0;++  if (is_64bit) {+    /* "vectorized" optimisation checking 8 octets at once+     *+     * NB: A 64-bit aligned buffer is assumed. This is assumption is+     * justified when the buffer is the payload of a `ByteArray#`.+     */+    const uint64_t *buf64 = (const uint64_t*)buf;++    for (; (j+7) < n; j+=8, ++buf64)+      if (*buf64 & UINT64_C(0x8080808080808080))+        break;+  } else {+    /* "vectorized" optimisation checking 4 octets at once */+    const uint32_t *buf32 = (const uint32_t*)buf;++    for (; (j+3) < n; j+=4, ++buf32)+      if (*buf32 & UINT64_C(0x80808080))+        break;+  }++  for (; j < n; ++j)     if (buf[j] & 0x80)       return j;++  return j;+}++/* Test whether well-formed UTF8 string contains only ASCII code-points+ * returns 0 if not ASCII+ *+ * This code assumes a naturally aligned buf[]+ */+int+hs_text_short_is_ascii(const uint8_t buf[], const size_t n)+{+  size_t j = 0;++  if (n < 2)+    return 1;++  if (is_64bit) {+    /* "vectorized" optimisation checking 8 octets at once+     *+     * NB: A 64-bit aligned buffer is assumed. This is assumption is+     * justified when the buffer is the payload of a `ByteArray#`.+     *+     */+    const uint64_t *buf64 = (const uint64_t*)buf;++    for (; (j+7) < n; j+=8, ++buf64)+      if (*buf64 & UINT64_C(0x8080808080808080))+        return 0;++    if (j < n) {+      const int maskshift = (8 - (n - j)) << 3;+      const uint64_t mask = is_bigendian ? (UINT64_C(0x8080808080808080) << maskshift)  /* big endian */+                                         : (UINT64_C(0x8080808080808080) >> maskshift); /* little endian */++      if (*buf64 & mask)+        return 0;+    }+  } else {+    /* "vectorized" optimisation checking 4 octets at once */+    const uint32_t *buf32 = (const uint32_t*)buf;++    for (; (j+3) < n; j+=4, ++buf32)+      if (*buf32 & UINT64_C(0x80808080))+        return 0;++    for (; j < n; ++j)+      if (buf[j] & 0x80)+        return 0;+  }++  return 1;+}++/*+ * Compute length of (transcoded) mutf8 literal+ *+ * If the mutf8 literal does not contain either surrogates nor escaped+ * NULs, a positive length is returned which matches what strlen(3)+ * would have returned.+ *+ * Otherwise, a negated size is returned which corresponds to the size+ * of a the mutf8->utf8 transcoded string.+ *+ */+HsInt+hs_text_short_mutf8_strlen(const uint8_t buf[])+{+  size_t j = 0;+  size_t nulls = 0;+  bool surr_seen = false;++  for (;;) {+    const uint8_t b0 = buf[j];++    if (unlikely(!b0))+      break;++    if (likely(!(b0 & 0x80)))+      j += 1;   /* 0_______ */+    else+      switch(b0 >> 4) {+      case 0xf: /* 11110___ */+        j += 4;+        break;+      case 0xe: /* 1110____ */+        /* UTF16 Surrogate pairs [U+D800 .. U+DFFF] */+        if (unlikely(!surr_seen && (b0 == 0xed) && (buf[j+1] & 0x20)))+          surr_seen = true;+        j += 3;+        break;+      default:  /* 110_____ */+        /* escaped NUL */+        if (unlikely((b0 == 0xc0) && (buf[j+1] == 0x80)))+          nulls += 1;+        j += 2;+        break;+      }+  } /* for */+++  if ((nulls > 0) || surr_seen)+    return -(HsInt)(j - nulls);++  return j;+}++/* Transcode Modified UTF-8 to proper UTF-8+ *+ * This involves+ *+ *  1. Unescape denormal 2-byte NULs (0xC0 0x80)+ *  2. Rewrite surrogate pairs to U+FFFD+ */+void+hs_text_short_mutf8_trans(const uint8_t src0[], uint8_t dst0[])+{+  const uint8_t *src = src0;+  uint8_t *dst = dst0;++  for (;;) {+    const uint8_t b0 = *src++;+    assert(utf8_lead_p(b0));++    if (likely(!(b0 & 0x80))) { /* 0_______ */+      if (unlikely(!b0))+        break;++      *dst++ = b0;+      continue;+    }++    switch(b0 >> 4) {+    case 0xf: /* 11110___ */+      assert(!utf8_lead_p(src[0]));+      assert(!utf8_lead_p(src[1]));+      assert(!utf8_lead_p(src[2]));+      *dst++ = b0;+      *dst++ = *src++;+      *dst++ = *src++;+      *dst++ = *src++;+      break;++    case 0xe: { /* 1110____ */+      const uint8_t b1 = *src++;+      const uint8_t b2 = *src++;+      assert(!utf8_lead_p(b1));+      assert(!utf8_lead_p(b2));+      if (unlikely((b0 == 0xed) && (b1 & 0x20))) {+        /* UTF16 Surrogate pairs [U+D800 .. U+DFFF]+         * -> translate into U+FFFD+         */+        *dst++ = 0xef;+        *dst++ = 0xbf;+        *dst++ = 0xbd;+      } else {+        *dst++ = b0;+        *dst++ = b1;+        *dst++ = b2;+      }+      break;+    }+    default: { /* 110_____ */+      const uint8_t b1 = *src++;+      assert(!utf8_lead_p(b1));+      if (unlikely((b0 == 0xc0) && (b1 == 0x80))) {+        /* escaped/denormal U+0000 -> normalize */+        *dst++ = 0x00;+      } else {+        *dst++ = b0;+        *dst++ = b1;+      }+      break;+    }+    } /* switch */+  } /* for */++  assert(labs(hs_text_short_mutf8_strlen(src0)) == (dst - dst0)); }
+ cbits/memcmp.c view
@@ -0,0 +1,12 @@+#include <string.h>++int+hs_text_short_memcmp(const void *s1, const size_t s1ofs, const void *s2, const size_t s2ofs, const size_t n)+{+  if (!n) return 0;++  const void *s1_ = s1+s1ofs;+  const void *s2_ = s2+s2ofs;++  return (s1_ == s2_) ? 0 : memcmp(s1_, s2_, n);+}
+ src-ghc708/PrimOps.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE MagicHash        #-}+{-# LANGUAGE UnliftedFFITypes #-}+{-# LANGUAGE Unsafe           #-}++module PrimOps ( compareByteArrays# ) where++import           Foreign.C.Types  (CInt (..), CSize (..))+import           GHC.Exts         (Int (I#))+import           GHC.Exts         (ByteArray#, Int#)+import           System.IO.Unsafe (unsafeDupablePerformIO)++-- | Emulate GHC 8.4's 'GHC.Prim.compareByteArrays#'+compareByteArrays# :: ByteArray# -> Int# -> ByteArray# -> Int# -> Int# -> Int#+compareByteArrays# ba1# ofs1# ba2# ofs2# n#+    = unI (fromIntegral (unsafeDupablePerformIO (c_memcmp ba1# ofs1 ba2# ofs2 n)))+  where+    unI (I# i#) = i#+    ofs1 = fromIntegral (I# ofs1#)+    ofs2 = fromIntegral (I# ofs2#)+    n    = fromIntegral (I# n#)++foreign import ccall unsafe "hs_text_short_memcmp"+   c_memcmp :: ByteArray# -> CSize -> ByteArray# -> CSize -> CSize -> IO CInt
+ src-ghc804/PrimOps.hs view
@@ -0,0 +1,6 @@+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE Unsafe    #-}++module PrimOps ( compareByteArrays# ) where++import           GHC.Exts (compareByteArrays#)
src-test/Tests.hs view
@@ -1,46 +1,224 @@+{-# LANGUAGE OverloadedLists   #-} {-# LANGUAGE OverloadedStrings #-}  module Main(main) where -import qualified Data.Text.Short as IUT-import Test.Tasty-import Test.Tasty.QuickCheck as QC-import Test.Tasty.HUnit-import Test.QuickCheck.Instances ()-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.String as D.S-import Data.Binary-import Data.Char-import Data.Monoid+import           Data.Binary+import           Data.Char+import           Data.Maybe+import           Data.Monoid+import qualified Data.String               as D.S+import qualified Data.Text                 as T+import qualified Data.Text.Encoding        as T+import qualified Data.Text.Short           as IUT+import qualified Data.Text.Short.Partial   as IUT+import           Test.QuickCheck.Instances ()+import           Test.Tasty+import           Test.Tasty.HUnit+import           Test.Tasty.QuickCheck     as QC+import           Text.Show.Functions       ()  fromByteStringRef = either (const Nothing) (Just . IUT.fromText) . T.decodeUtf8'  main :: IO ()-main = defaultMain tests+main = defaultMain (adjustOption (QuickCheckTests 50000 `max`) $ tests)  tests :: TestTree tests = testGroup "Tests" [unitTests,qcProps] +-- ShortText w/ in-bounds index+data STI = STI IUT.ShortText Int+         deriving (Eq,Show)++newtype ST = ST IUT.ShortText+           deriving (Eq,Show)++instance Arbitrary STI where+  arbitrary = do+    t <- arbitrary+    i <- choose (0, T.length t - 1)+    return $! STI (IUT.fromText t) i++instance Arbitrary ST where+  arbitrary = fmap (ST . IUT.fromText) arbitrary+  shrink (ST st) = map (ST . IUT.fromText) (shrink (IUT.toText st))+ qcProps :: TestTree qcProps = testGroup "Properties"   [ QC.testProperty "length/fromText"   $ \t -> IUT.length (IUT.fromText t) == T.length t   , QC.testProperty "length/fromString" $ \s -> IUT.length (IUT.fromString s) == length s+  , QC.testProperty "length/append"     $ \(ST t1) (ST t2) -> IUT.length t1 + IUT.length t2 == IUT.length (IUT.append t1 t2)+  , QC.testProperty "compare" $ \t1 t2 -> IUT.fromText t1 `compare` IUT.fromText t2  == t1 `compare` t2+  , QC.testProperty "(==)" $ \t1 t2 -> (IUT.fromText t1 == IUT.fromText t2)  == (t1 == t2)+  , QC.testProperty "(!?)" $ \t ->+      let t' = IUT.fromText t+      in and ([ mapMaybe (t' IUT.!?) ([0 .. T.length t -1 ] :: [Int]) == T.unpack t+              , mapMaybe (t' IUT.!?) [-5 .. -1] == []+              , mapMaybe (t' IUT.!?) [T.length t .. T.length t + 5] == []+              ] :: [Bool])+  , QC.testProperty "indexEndMaybe" $ \t ->+      let t' = IUT.fromText t+      in and ([ mapMaybe (IUT.indexEndMaybe t') [0 .. T.length t -1 ] == T.unpack (T.reverse t)+              , mapMaybe (IUT.indexEndMaybe t') [-5 .. -1] == []+              , mapMaybe (IUT.indexEndMaybe t') [T.length t .. T.length t + 5] == []+              ] :: [Bool])   , QC.testProperty "toText.fromText"   $ \t -> (IUT.toText . IUT.fromText) t == t   , QC.testProperty "fromByteString"    $ \b -> IUT.fromByteString b == fromByteStringRef b   , QC.testProperty "fromByteString.toByteString" $ \t -> let ts = IUT.fromText t in (IUT.fromByteString . IUT.toByteString) ts == Just ts   , QC.testProperty "toString.fromString" $ \s -> (IUT.toString . IUT.fromString) s == s   , QC.testProperty "isAscii"  $ \s -> IUT.isAscii (IUT.fromString s) == all isAscii s   , QC.testProperty "isAscii2" $ \t -> IUT.isAscii (IUT.fromText t)   == T.all isAscii t+  , QC.testProperty "splitAt" $ \t ->+      let t' = IUT.fromText t+          mapBoth f (x,y) = (f x, f y)+      in and [ mapBoth IUT.toText (IUT.splitAt i t') == T.splitAt i t | i <- [-5 .. 5+T.length t ] ]++  , QC.testProperty "intersperse" $ \t c -> IUT.intersperse c (IUT.fromText t) == IUT.fromText (T.intersperse c t)+  , QC.testProperty "intercalate" $ \t1 t2 -> IUT.intercalate (IUT.fromText t1) (map IUT.fromText t2) == IUT.fromText (T.intercalate t1 t2)+  , QC.testProperty "reverse.singleton" $ \c -> IUT.reverse (IUT.singleton c) == IUT.singleton c+  , QC.testProperty "reverse"     $ \t -> IUT.reverse (IUT.fromText t) == IUT.fromText (T.reverse t)+  , QC.testProperty "filter"      $ \p t -> IUT.filter p (IUT.fromText t) == IUT.fromText (T.filter p t)+  , QC.testProperty "replicate"   $ \n t -> IUT.replicate n (IUT.fromText t) == IUT.fromText (T.replicate n t)+  , QC.testProperty "dropAround"  $ \p t -> IUT.dropAround p (IUT.fromText t) == IUT.fromText (T.dropAround p t)++  , QC.testProperty "foldl"       $ \f z t -> IUT.foldl f (z :: Char) (IUT.fromText t) == T.foldl f (z :: Char) t+  , QC.testProperty "foldl #2"    $ \t -> IUT.foldl (\n _ -> (n+1)) 0 (IUT.fromText t) == T.length t+  , QC.testProperty "foldl #3"    $ \t -> IUT.foldl (\s c -> c : s) [] (IUT.fromText t) == T.unpack (T.reverse t)++  , QC.testProperty "foldl'"      $ \f z t -> IUT.foldl' f (z :: Char) (IUT.fromText t) == T.foldl' f (z :: Char) t+  , QC.testProperty "foldl' #2"   $ \t -> IUT.foldl' (\n _ -> (n+1)) 0 (IUT.fromText t) == T.length t+  , QC.testProperty "foldl' #3"   $ \t -> IUT.foldl' (\s c -> c : s) [] (IUT.fromText t) == T.unpack (T.reverse t)++  , QC.testProperty "foldr"       $ \f z t -> IUT.foldr f (z :: Char) (IUT.fromText t) == T.foldr f (z :: Char) t+  , QC.testProperty "foldr #2"    $ \t -> IUT.foldr (\_ n -> (n+1)) 0 (IUT.fromText t) == T.length t+  , QC.testProperty "foldr #3"    $ \t -> IUT.foldr (:) [] (IUT.fromText t) == T.unpack t++  , QC.testProperty "foldr1"      $ \f t -> (not (T.null t)) ==> IUT.foldr1 f (IUT.fromText t) == T.foldr1 f t+  , QC.testProperty "foldl1"      $ \f t -> (not (T.null t)) ==> IUT.foldl1 f (IUT.fromText t) == T.foldl1 f t+  , QC.testProperty "foldl1'"     $ \f t -> (not (T.null t)) ==> IUT.foldl1' f (IUT.fromText t) == T.foldl1' f t++  , QC.testProperty "splitAtEnd" $ \t ->+      let t' = IUT.fromText t+          n' = IUT.length t'+      in and [ (IUT.splitAt (n'-i) t') == IUT.splitAtEnd i t' | i <- [-5 .. 5+n' ] ]++  , QC.testProperty "find" $ \t -> IUT.find Data.Char.isAscii (IUT.fromText t) == T.find Data.Char.isAscii t+  , QC.testProperty "findIndex" $ \t -> IUT.findIndex Data.Char.isAscii (IUT.fromText t) == T.findIndex Data.Char.isAscii t++  , QC.testProperty "isSuffixOf" $ \t1 t2 -> IUT.fromText t1 `IUT.isSuffixOf` IUT.fromText t2  == t1 `T.isSuffixOf` t2+  , QC.testProperty "isPrefixOf" $ \t1 t2 -> IUT.fromText t1 `IUT.isPrefixOf` IUT.fromText t2  == t1 `T.isPrefixOf` t2++  , QC.testProperty "stripPrefix" $ \t1 t2 -> IUT.stripPrefix (IUT.fromText t1) (IUT.fromText t2) ==+                                                fmap IUT.fromText (T.stripPrefix t1 t2)++  , QC.testProperty "stripSuffix" $ \t1 t2 -> IUT.stripSuffix (IUT.fromText t1) (IUT.fromText t2) ==+                                                fmap IUT.fromText (T.stripSuffix t1 t2)++  , QC.testProperty "stripPrefix 2" $ \(STI t i) ->+      let (pfx,sfx) = IUT.splitAt i t+      in IUT.stripPrefix pfx t == Just sfx++  , QC.testProperty "stripSuffix 2" $ \(STI t i) ->+      let (pfx,sfx) = IUT.splitAt i t+      in IUT.stripSuffix sfx t == Just pfx++  , QC.testProperty "cons" $ \c t -> IUT.singleton c <> IUT.fromText t == IUT.cons c (IUT.fromText t)+  , QC.testProperty "snoc" $ \c t -> IUT.fromText t <> IUT.singleton c == IUT.snoc (IUT.fromText t) c++  , QC.testProperty "uncons" $ \c t -> IUT.uncons (IUT.singleton c <> IUT.fromText t) == Just (c, IUT.fromText t)++  , QC.testProperty "unsnoc" $ \c t -> IUT.unsnoc (IUT.fromText t <> IUT.singleton c) == Just (IUT.fromText t, c)++  , QC.testProperty "break" $ \t -> let (l,r)   = IUT.break Data.Char.isAscii (IUT.fromText t)+                                    in  T.break Data.Char.isAscii t == (IUT.toText l,IUT.toText r)++  , QC.testProperty "span"  $ \t -> let (l,r)   = IUT.span Data.Char.isAscii (IUT.fromText t)+                                    in  T.span Data.Char.isAscii t == (IUT.toText l,IUT.toText r)++  , QC.testProperty "breakEnd" $ \t -> let (l,r)   = IUT.breakEnd Data.Char.isAscii (IUT.fromText t)+                                       in  t_breakEnd Data.Char.isAscii t == (IUT.toText l,IUT.toText r)++  , QC.testProperty "spanEnd"  $ \t -> let (l,r)   = IUT.spanEnd Data.Char.isAscii (IUT.fromText t)+                                       in  t_spanEnd Data.Char.isAscii t == (IUT.toText l,IUT.toText r)++  , QC.testProperty "splitAt/isPrefixOf" $ \t ->+      let t' = IUT.fromText t+      in and [ IUT.isPrefixOf (fst (IUT.splitAt i t')) t' | i <- [-5 .. 5+T.length t ] ]+  , QC.testProperty "splitAt/isSuffixOf" $ \t ->+      let t' = IUT.fromText t+      in and [ IUT.isSuffixOf (snd (IUT.splitAt i t')) t' | i <- [-5 .. 5+T.length t ] ]   ] +t_breakEnd p t = t_spanEnd (not . p) t+t_spanEnd  p t = (T.dropWhileEnd p t, T.takeWhileEnd p t)+ unitTests = testGroup "Unit-tests"   [ testCase "fromText mempty" $ IUT.fromText mempty @?= mempty   , testCase "fromShortByteString [0xc0,0x80]" $ IUT.fromShortByteString "\xc0\x80" @?= Nothing   , testCase "fromByteString [0xc0,0x80]" $ IUT.fromByteString "\xc0\x80" @?= Nothing+  , testCase "fromByteString [0xf0,0x90,0x80,0x80]" $ IUT.fromByteString "\xf0\x90\x80\x80" @?= Just "\x10000"+  , testCase "fromByteString [0xf4,0x90,0x80,0x80]" $ IUT.fromByteString "\244\144\128\128" @?= Nothing   , testCase "IsString U+D800" $ "\xFFFD" @?= (IUT.fromString "\xD800") --  , testCase "IsString U+D800" $ (IUT.fromString "\xD800") @?= IUT.fromText ("\xD800" :: T.Text)    , testCase "Binary.encode" $ encode ("Hello \8364 & \171581!\NUL" :: IUT.ShortText) @?= "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\DC2Hello \226\130\172 & \240\169\184\189!\NUL"   , testCase "Binary.decode" $ decode ("\NUL\NUL\NUL\NUL\NUL\NUL\NUL\DC2Hello \226\130\172 & \240\169\184\189!\NUL") @?= ("Hello \8364 & \171581!\NUL" :: IUT.ShortText)-  ]+  , testCase "singleton" $ [ c | c <- [minBound..maxBound], IUT.singleton c /= IUT.fromText (T.singleton c) ] @?= []++  , testCase "splitAtEnd" $ IUT.splitAtEnd 1 "€€" @?= ("€","€")++  , testCase "literal0" $ IUT.unpack testLit0 @?= []+  , testCase "literal1" $ IUT.unpack testLit1 @?= ['€','\0','€','\0']+  , testCase "literal2" $ IUT.unpack testLit2 @?= ['\xFFFD','\xD7FF','\xFFFD','\xE000']+  , testCase "literal3" $ IUT.unpack testLit3 @?= ['\1'..'\x7f']+  , testCase "literal4" $ IUT.unpack testLit4 @?= map toEnum [0,1,126,127,128,129,130,256,2046,2047,2048,2049,2050,65530,65531,65532,65533,65534,65533,65535,65536,65537,65538,1114110,1114111]+  , testCase "literal5" $ IUT.unpack testLit5 @?= map toEnum [28961]+  , testCase "literal6" $ IUT.unpack testLit6 @?= map toEnum [0]+  , testCase "literal7" $ IUT.unpack testLit7 @?= map toEnum [66328]+  , testCase "literal8" $ IUT.unpack testLit8 @?= map toEnum [127]++    -- list literals+  , testCase "literal9"  $ [] @?= ("" :: IUT.ShortText)+  , testCase "literal10" $ ['¤','€','$'] @?= ("¤€$" :: IUT.ShortText)+  , testCase "literal12" $ IUT.unpack ['\xD800','\xD7FF','\xDFFF','\xE000'] @?= ['\xFFFD','\xD7FF','\xFFFD','\xE000']+ ]++-- isScalar :: Char -> Bool+-- isScalar c = c < '\xD800' || c >= '\xE000'+++{-# NOINLINE testLit0 #-}+testLit0 :: IUT.ShortText+testLit0 = ""++{-# NOINLINE testLit1 #-}+testLit1 :: IUT.ShortText+testLit1 = "€\NUL€\NUL"++{-# NOINLINE testLit2 #-}+testLit2 :: IUT.ShortText+testLit2 = "\xD800\xD7FF\xDFFF\xE000"++{-# NOINLINE testLit3 #-}+testLit3 :: IUT.ShortText+testLit3 = "\SOH\STX\ETX\EOT\ENQ\ACK\a\b\t\n\v\f\r\SO\SI\DLE\DC1\DC2\DC3\DC4\NAK\SYN\ETB\CAN\EM\SUB\ESC\FS\GS\RS\US !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\DEL"++{-# NOINLINE testLit4 #-}+testLit4 :: IUT.ShortText+testLit4 = "\NUL\SOH~\DEL\128\129\130\256\2046\2047\2048\2049\2050\65530\65531\65532\65533\65534\65533\65535\65536\65537\65538\1114110\1114111"++{-# NOINLINE testLit5 #-}+testLit5 :: IUT.ShortText+testLit5 = "無"++{-# NOINLINE testLit6 #-}+testLit6 :: IUT.ShortText+testLit6 = "\NUL"++{-# NOINLINE testLit7 #-}+testLit7 :: IUT.ShortText+testLit7 = "𐌘"++{-# NOINLINE testLit8 #-}+testLit8 :: IUT.ShortText+testLit8 = "\x7f"
src/Data/Text/Short.hs view
@@ -8,25 +8,99 @@ -- Stability   : stable -- -- Memory-efficient representation of Unicode text strings.+--+-- This module is intended to be imported @qualified@, to avoid name+-- clashes with "Prelude" functions, e.g.+--+-- > import qualified Data.Text.Short as TS+-- > import qualified Data.Text.Short (ShortText)+--+-- This modules deliberately omits (common) partial functions, which+-- can be found in "Data.Text.Short.Partial" instead.+--+-- @since 0.1 module Data.Text.Short     ( -- * The 'ShortText' type       ShortText        -- * Basic operations+      -- ** Construction+    , empty+    , singleton+    , pack+    , append+    , concat+    , cons+    , snoc+    , replicate++      -- ** Deconstruction+    , unpack+    , uncons+    , unsnoc++      -- ** Querying & predicates     , null     , length     , isAscii+    , all+    , any+    , find+    , isPrefixOf+    , isSuffixOf +      -- ** Lookup & indexing+    , (!?)+    , indexMaybe+    , indexEndMaybe+    , findIndex++      -- * Splitting 'ShortText's+      -- ** Basic functions+    , take+    , takeEnd+    , drop+    , dropEnd+    , takeWhile+    , takeWhileEnd+    , dropWhile+    , dropWhileEnd++    , dropAround++      -- ** Pair-valued functions+    , splitAt+    , splitAtEnd+    , span+    , break+    , spanEnd+    , breakEnd++      -- ** Suffix & Prefix operations+    , stripPrefix+    , stripSuffix++      -- * Transformations+    , intersperse+    , intercalate+    , reverse+    , filter++      -- * Folds+    , foldl+    , foldl'+    , foldr+       -- * Conversions       -- ** 'String'     , fromString     , toString -      -- ** 'T.Text'+      -- ** 'Text'     , fromText     , toText -      -- ** 'BS.ByteString'+      -- ** 'ByteString'     , fromShortByteString     , toShortByteString @@ -37,5 +111,219 @@      ) where -import Data.Text.Short.Internal-import Prelude ()+import           Data.Semigroup+import           Data.Text.Short.Internal+import           Prelude                  ()++-- | \(\mathcal{O}(n)\) Variant of 'span' with negated predicate.+--+-- >>> break (> 'c') "abcdabcd"+-- ("abc","dabcd")+--+-- prop> break p t == span (not . p) t+--+-- prop> fst (break p t) <> snd (break p t) == t+--+-- @since 0.1.2+break :: (Char -> Bool) -> ShortText -> (ShortText,ShortText)+break p st = span (not . p) st++-- | \(\mathcal{O}(n)\) Variant of 'spanEnd' with negated predicate.+--+-- >>> breakEnd (< 'c') "abcdabcd"+-- ("abcdab","cd")+--+-- prop> breakEnd p t == spanEnd (not . p) t+--+-- prop> fst (breakEnd p t) <> snd (breakEnd p t) == t+--+-- @since 0.1.2+breakEnd :: (Char -> Bool) -> ShortText -> (ShortText,ShortText)+breakEnd p st = spanEnd (not . p) st++-- | \(\mathcal{O}(n)\) Index /i/-th code-point in 'ShortText'.+--+-- Infix operator alias of 'indexMaybe'+--+-- >>> "abcdefg" !? 2+-- Just 'c'+--+-- @since 0.1.2+(!?) :: ShortText -> Int -> Maybe Char+(!?) = indexMaybe++-- | \(\mathcal{O}(n)\) Test whether /any/ code points in 'ShortText' satisfy a predicate.+--+-- >>> any (> 'c') "abcdabcd"+-- True+--+-- >>> any (const True) ""+-- False+--+-- >>> any (== 'c') "abdabd"+-- False+--+-- prop> any p t == not (all (not . p) t)+--+-- @since 0.1.2+any :: (Char -> Bool) -> ShortText -> Bool+any p st = case find p st of+             Nothing -> False+             Just _  -> True++-- | \(\mathcal{O}(n)\) Concatenate two 'ShortText's+--+-- This is a type-specialised alias of '<>'.+--+-- >>> append "foo" "bar"+-- "foobar"+--+-- prop> length (append t1 t2) == length t1 + length t2+--+-- @since 0.1.2+append :: ShortText -> ShortText -> ShortText+append = (<>)++-- | \(\mathcal{O}(n)\) Concatenate list of 'ShortText's+--+-- This is a type-specialised alias of 'mconcat'.+--+-- >>> concat []+-- ""+--+-- >>> concat ["foo","bar","doo"]+-- "foobardoo"+--+-- @since 0.1.2+concat :: [ShortText] -> ShortText+concat = mconcat++-- | \(\mathcal{O}(0)\) The /empty/ 'ShortText'.+--+-- This is a type-specialised alias of 'mempty'.+--+-- >>> empty+-- ""+--+-- >>> null empty+-- True+--+-- @since 0.1.2+empty :: ShortText+empty = mempty++-- | \(\mathcal{O}(n)\) Construct a 'ShortText' from a list of 'Char's.+--+-- This is an alias for 'fromString'.+--+-- @since 0.1.2+pack :: [Char] -> ShortText+pack = fromString++-- | \(\mathcal{O}(n)\) Convert 'ShortText' into a list of 'Char's.+--+-- This is an alias for 'toString'.+--+-- prop> (pack . unpack) t == t+--+-- @since 0.1.2+unpack :: ShortText -> [Char]+unpack = toString++-- | \(\mathcal{O}(n)\) Take prefix of given length or return whole 'ShortText' if too short.+--+-- >>> take 3 "abcdef"+-- "abc"+--+-- >>> take 3 "ab"+-- "ab"+--+-- @since 0.1.2+take :: Int -> ShortText -> ShortText+take n = fst . splitAt n++-- | \(\mathcal{O}(n)\) Take suffix of given length or return whole 'ShortText' if too short.+--+-- >>> takeEnd 3 "abcdefg"+-- "efg"+--+-- >>> takeEnd 3 "ab"+-- "ab"+--+-- @since 0.1.2+takeEnd :: Int -> ShortText -> ShortText+takeEnd n = snd . splitAtEnd n++-- | \(\mathcal{O}(n)\) Take remove prefix of given length from 'ShortText' or return 'empty' 'ShortText' if too short.+--+-- >>> drop 4 "abcdef"+-- "ef"+--+-- >>> drop 4 "ab"+-- ""+--+-- @since 0.1.2+drop :: Int -> ShortText -> ShortText+drop n = snd . splitAt n++-- | \(\mathcal{O}(n)\) Take remove suffix of given length from 'ShortText' or return 'empty' 'ShortText' if too short.+--+-- >>> drop 4 "abcdefghi"+-- "efghi"+--+-- >>> drop 4 "ab"+-- ""+--+-- @since 0.1.2+dropEnd :: Int -> ShortText -> ShortText+dropEnd n = fst . splitAtEnd n++-- | \(\mathcal{O}(n)\) Take longest prefix satisfying given predicate.+--+-- prop> takeWhile p t == fst (span p t)+--+-- >>> takeWhile (< 'c') "abcdabcd"+-- "ab"+--+-- @since 0.1.2+takeWhile :: (Char -> Bool) -> ShortText -> ShortText+takeWhile p = fst . span p++-- | \(\mathcal{O}(n)\) Take longest suffix satisfying given predicate.+--+-- prop> takeWhileEnd p t == snd (spanEnd p t)+--+-- >>> takeWhileEnd (>= 'c') "abcdabcd"+-- "cd"+--+-- @since 0.1.2+takeWhileEnd :: (Char -> Bool) -> ShortText -> ShortText+takeWhileEnd p = snd . spanEnd p++-- | \(\mathcal{O}(n)\) Remove longest prefix satisfying given predicate.+--+-- prop> dropWhile p t == snd (span p t)+--+-- >>> dropWhile (< 'c') "abcdabcd"+-- "cdabcd"+--+-- @since 0.1.2+dropWhile :: (Char -> Bool) -> ShortText -> ShortText+dropWhile p = snd . span p++-- | \(\mathcal{O}(n)\) Remove longest suffix satisfying given predicate.+--+-- prop> dropWhileEnd p t == fst (spanEnd p t)+--+-- >>> dropWhileEnd (>= 'c') "abcdabcd"+-- "abcdab"+--+-- @since 0.1.2+dropWhileEnd :: (Char -> Bool) -> ShortText -> ShortText+dropWhileEnd p = fst . spanEnd p++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Text.Show.Functions ()+-- >>> import qualified Test.QuickCheck.Arbitrary as QC+-- >>> instance QC.Arbitrary ShortText where { arbitrary = fmap fromString QC.arbitrary }
src/Data/Text/Short/Internal.hs view
@@ -1,246 +1,1517 @@-{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, MagicHash, UnliftedFFITypes, Unsafe #-}---- |--- Module      : Data.Text.Short.Internal--- Copyright   : © Herbert Valerio Riedel 2017--- License     : BSD3------ Maintainer  : hvr@gnu.org--- Stability   : stable------ Memory-efficient representation of Unicode text strings.-module Data.Text.Short.Internal-    ( -- * The 'ShortText' type-      ShortText(..)--      -- * Basic operations-    , Data.Text.Short.Internal.null-    , Data.Text.Short.Internal.length-    , Data.Text.Short.Internal.isAscii--      -- * Conversions-      -- ** 'String'-    , Data.Text.Short.Internal.fromString-    , toString--      -- ** 'T.Text'-    , fromText-    , toText--      -- ** 'BS.ByteString'-    , fromShortByteString-    , fromShortByteStringUnsafe-    , toShortByteString--    , fromByteString-    , fromByteStringUnsafe-    , toByteString--    , toBuilder--    ) where--import           Control.DeepSeq (NFData)--- import           Control.Exception as E-import qualified Data.ByteString as BS-import           Data.ByteString.Short (ShortByteString)-import qualified Data.ByteString.Short as BSS-import qualified Data.ByteString.Short.Internal as BSSI-import           Data.Char-import           Data.Hashable (Hashable)-import           Data.Semigroup-import qualified Data.String as S-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import           Foreign.C-import           GHC.Exts (ByteArray#)-import qualified GHC.Foreign as GHC-import           GHC.IO.Encoding-import           System.IO.Unsafe-import Data.Binary-import qualified Data.ByteString.Builder as BB---- | A compact representation of Unicode strings.------ This type relates to 'T.Text' as 'ShortByteString' relates to 'BS.ByteString' by providing a more compact type. Please consult the documentation of "Data.ByteString.Short" for more information.------ Currently, a boxed unshared 'T.Text' has a memory footprint of 6 words (i.e. 48 bytes on 64-bit systems) plus 2 or 4 bytes per code-point (due to the internal UTF-16 representation). Each 'T.Text' value which can share its payload with another 'T.Text' requires only 4 words additionally. Unlike 'BS.ByteString', 'T.Text' use unpinned memory.------ In comparison, the footprint of a boxed 'ShortText' is only 4 words (i.e. 32 bytes on 64-bit systems) plus 1/2/3/4 bytes per code-point (due to the internal UTF-8 representation).--- It can be shown that for realistic data <http://utf8everywhere.org/#asian UTF-16 has a space overhead of 50% over UTF-8>.----newtype ShortText = ShortText ShortByteString-                  deriving (Eq,Ord,Monoid,Semigroup,Hashable,NFData)--instance Show ShortText where-    showsPrec p (ShortText b) = showsPrec p (decodeStringShort' utf8 b)-    show (ShortText b)        = show        (decodeStringShort' utf8 b)--instance Read ShortText where-    readsPrec p = map (\(x,s) -> (ShortText $ encodeStringShort utf8 x,s)) . readsPrec p---- | Behaviour for @[U+D800 .. U+DFFF]@ matches the 'IsString' instance for 'T.Text'-instance S.IsString ShortText where-    fromString = fromString---- | The 'Binary' encoding matches the one for 'T.Text'-#if MIN_VERSION_binary(0,8,1)-instance Binary ShortText where-    put = put . toShortByteString-    get = do-        sbs <- get-        case fromShortByteString sbs of-          Nothing -> fail "Binary.get(ShortText): Invalid UTF-8 stream"-          Just st -> return st-#else--- fallback via 'ByteString' instance-instance Binary ShortText where-    put = put . toByteString-    get = do-        bs <- get-        case fromByteString bs of-          Nothing -> fail "Binary.get(ShortText): Invalid UTF-8 stream"-          Just st -> return st-#endif---- | \(\mathcal{O}(1)\) Test whether a 'ShortText' is empty.-null :: ShortText -> Bool-null = BSS.null . toShortByteString---- | \(\mathcal{O}(n)\) Count the number of Unicode code-points in a 'ShortText'.-length :: ShortText -> Int-length st = fromIntegral $ unsafePerformIO (c_text_short_length (toByteArray# st) (toCSize st))--foreign import ccall unsafe "hs_text_short_length" c_text_short_length :: ByteArray# -> CSize -> IO CSize---- | \(\mathcal{O}(n)\) Test whether 'ShortText' contains only ASCII code-points (i.e. only U+0000 through U+007F).-isAscii :: ShortText -> Bool-isAscii st = (== sz) $ unsafePerformIO (c_text_short_is_ascii (toByteArray# st) sz)-  where-    sz = toCSize st--foreign import ccall unsafe "hs_text_short_is_ascii" c_text_short_is_ascii :: ByteArray# -> CSize -> IO CSize--------------------------------------------------------------------------------toCSize :: ShortText -> CSize-toCSize = fromIntegral . BSS.length . toShortByteString--toByteArray# :: ShortText -> ByteArray#-toByteArray# (ShortText (BSSI.SBS ba#)) = ba#---- | \(\mathcal{O}(0)\) Converts to UTF-8 encoded 'ShortByteString'------ This operation has effectively no overhead, as it's currently merely a @newtype@-cast.-toShortByteString :: ShortText -> ShortByteString-toShortByteString (ShortText b) = b---- | \(\mathcal{O}(n)\) Converts to UTF-8 encoded 'BS.ByteString'-toByteString :: ShortText -> BS.ByteString-toByteString = BSS.fromShort . toShortByteString---- | Construct a 'BB.Builder' that encodes 'ShortText' as UTF-8.-toBuilder :: ShortText -> BB.Builder-toBuilder = BB.shortByteString . toShortByteString---- | \(\mathcal{O}(n)\) Convert to 'String'-toString :: ShortText -> String-toString = decodeStringShort' utf8 . toShortByteString---- | \(\mathcal{O}(n)\) Convert to 'T.Text'------ This is currently not \(\mathcal{O}(1)\) because currently 'T.Text' uses UTF-16 as its internal representation.--- In the event that 'T.Text' will change its internal representation to UTF-8 this operation will become \(\mathcal{O}(1)\).-toText :: ShortText -> T.Text-toText = T.decodeUtf8 . toByteString---------- | \(\mathcal{O}(n)\) Construct/pack from 'String'------ Note: This function is total because it replaces the (invalid) code-points U+D800 through U+DFFF with the replacement character U+FFFD.-fromString :: String -> ShortText-fromString = ShortText . encodeStringShort utf8 . map r-  where-    r c | 0xd800 <= x && x < 0xe000 = '\xFFFD'-        | otherwise                 = c-      where-        x = ord c---- | \(\mathcal{O}(n)\) Construct 'ShortText' from 'T.Text'------ This is currently not \(\mathcal{O}(1)\) because currently 'T.Text' uses UTF-16 as its internal representation.--- In the event that 'T.Text' will change its internal representation to UTF-8 this operation will become \(\mathcal{O}(1)\).-fromText :: T.Text -> ShortText-fromText = fromByteStringUnsafe . T.encodeUtf8---- | \(\mathcal{O}(n)\) Construct 'ShortText' from UTF-8 encoded 'ShortByteString'------ This operation doesn't copy the input 'ShortByteString' but it--- cannot be \(\mathcal{O}(1)\) because we need to validate the UTF-8 encoding.------ Returns 'Nothing' in case of invalid UTF-8 encoding.-fromShortByteString :: ShortByteString -> Maybe ShortText-fromShortByteString sbs-  | isValidUtf8 st  = Just st-  | otherwise       = Nothing-  where-    st = ShortText sbs---- | \(\mathcal{O}(0)\) Construct 'ShortText' from UTF-8 encoded 'ShortByteString'------ This operation has effectively no overhead, as it's currently merely a @newtype@-cast.------ __WARNING__: Unlike the safe 'fromShortByteString' conversion, this--- conversion is /unsafe/ as it doesn't validate the well-formedness of the--- UTF-8 encoding.-fromShortByteStringUnsafe :: ShortByteString -> ShortText-fromShortByteStringUnsafe = ShortText---- | \(\mathcal{O}(n)\) Construct 'ShortText' from UTF-8 encoded 'BS.ByteString'------ Returns 'Nothing' in case of invalid UTF-8 encoding.-fromByteString :: BS.ByteString -> Maybe ShortText-fromByteString = fromShortByteString . BSS.toShort---- | \(\mathcal{O}(n)\) Construct 'ShortText' from UTF-8 encoded 'BS.ByteString'------ This operation is \(\mathcal{O}(n)\) because the 'BS.ByteString' needs to be--- copied into an unpinned 'ByteArray#'.------ __WARNING__: Unlike the safe 'fromByteString' conversion, this--- conversion is /unsafe/ as it doesn't validate the well-formedness of the--- UTF-8 encoding.-fromByteStringUnsafe :: BS.ByteString -> ShortText-fromByteStringUnsafe = ShortText . BSS.toShort--------------------------------------------------------------------------------encodeString :: TextEncoding -> String -> BS.ByteString-encodeString te str = unsafePerformIO $ GHC.withCStringLen te str BS.packCStringLen---- decodeString :: TextEncoding -> BS.ByteString -> Maybe String--- decodeString te bs = cvtEx $ unsafePerformIO $ try $ BS.useAsCStringLen bs (GHC.peekCStringLen te)---   where---     cvtEx :: Either IOException a -> Maybe a---     cvtEx = either (const Nothing) Just--decodeString' :: TextEncoding -> BS.ByteString -> String-decodeString' te bs = unsafePerformIO $ BS.useAsCStringLen bs (GHC.peekCStringLen te)--decodeStringShort' :: TextEncoding -> ShortByteString -> String-decodeStringShort' te = decodeString' te . BSS.fromShort--encodeStringShort :: TextEncoding -> String -> BSS.ShortByteString-encodeStringShort te = BSS.toShort . encodeString te---isValidUtf8 :: ShortText -> Bool-isValidUtf8 st = (==0) $ unsafePerformIO (c_text_short_is_valid_utf8 (toByteArray# st) (toCSize st))--foreign import ccall unsafe "hs_text_short_is_valid_utf8" c_text_short_is_valid_utf8 :: ByteArray# -> CSize -> IO CInt--{- TODO:-{-# RULES "ShortText strlit" forall s . fromString (unpackCString# s) = fromAddr# #-}-...--}+{-# LANGUAGE BangPatterns               #-}+{-# LANGUAGE CPP                        #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MagicHash                  #-}+{-# LANGUAGE RankNTypes                 #-}+{-# LANGUAGE TypeFamilies               #-}+{-# LANGUAGE UnboxedTuples              #-}+{-# LANGUAGE UnliftedFFITypes           #-}+{-# LANGUAGE Unsafe                     #-}+{-# LANGUAGE ViewPatterns               #-}++-- |+-- Module      : Data.Text.Short.Internal+-- Copyright   : © Herbert Valerio Riedel 2017+-- License     : BSD3+--+-- Maintainer  : hvr@gnu.org+-- Stability   : stable+--+-- Memory-efficient representation of Unicode text strings.+--+-- @since 0.1+module Data.Text.Short.Internal+    ( -- * The 'ShortText' type+      ShortText(..)++      -- * Basic operations+    , null+    , length+    , isAscii+    , splitAt+    , splitAtEnd+    , indexEndMaybe+    , indexMaybe+    , isPrefixOf+    , stripPrefix+    , isSuffixOf+    , stripSuffix++    , cons+    , snoc+    , uncons+    , unsnoc++    , findIndex+    , find+    , all++    , span+    , spanEnd++    , intersperse+    , intercalate+    , reverse+    , replicate++    , filter+    , dropAround++    , foldl+    , foldl'+    , foldr+    , foldl1+    , foldl1'+    , foldr1++      -- * Conversions+      -- ** 'Char'+    , singleton++      -- ** 'String'+    , Data.Text.Short.Internal.fromString+    , toString++      -- ** 'T.Text'+    , fromText+    , toText++      -- ** 'BS.ByteString'+    , fromShortByteString+    , fromShortByteStringUnsafe+    , toShortByteString++    , fromByteString+    , fromByteStringUnsafe+    , toByteString++    , toBuilder++      -- * misc+      -- ** For Haddock++    , BS.ByteString+    , T.Text+    , module Prelude++      -- ** Internals+    , isValidUtf8+    ) where++import           Control.DeepSeq                (NFData)+import           Control.Monad.ST               (stToIO)+import           Data.Binary+import           Data.Bits+import qualified Data.ByteString                as BS+import qualified Data.ByteString.Builder        as BB+import           Data.ByteString.Short          (ShortByteString)+import qualified Data.ByteString.Short          as BSS+import qualified Data.ByteString.Short.Internal as BSSI+import           Data.Char                      (ord)+import           Data.Hashable                  (Hashable)+import qualified Data.List                      as List+import           Data.Maybe                     (fromMaybe, isNothing)+import           Data.Semigroup+import qualified Data.String                    as S+import qualified Data.Text                      as T+import qualified Data.Text.Encoding             as T+import           Foreign.C+import           GHC.Base                       (assert, unsafeChr)+import qualified GHC.CString                    as GHC+import           GHC.Exts                       (Addr#, ByteArray#, Int (I#),+                                                 Int#, MutableByteArray#,+                                                 Ptr (..), RealWorld, Word (W#))+import qualified GHC.Exts+import qualified GHC.Foreign                    as GHC+import           GHC.IO.Encoding+import           GHC.ST+import           Prelude                        hiding (all, any, break, concat,+                                                 drop, dropWhile, filter, foldl,+                                                 foldl1, foldr, foldr1, head,+                                                 init, last, length, null,+                                                 replicate, reverse, span,+                                                 splitAt, tail, take, takeWhile)+import           System.IO.Unsafe+import           Text.Printf                    (PrintfArg, formatArg,+                                                 formatString)++import qualified PrimOps++-- | A compact representation of Unicode strings.+--+-- A 'ShortText' value is a sequence of Unicode scalar values, as defined in+-- <http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#page=35 §3.9, definition D76 of the Unicode 5.2 standard >;+-- This means that a 'ShortText' is a list of (scalar) Unicode code-points (i.e. code-points in the range @[U+00 .. U+D7FF] ∪ [U+E000 .. U+10FFFF]@).+--+-- This type relates to 'T.Text' as 'ShortByteString' relates to 'BS.ByteString' by providing a more compact type. Please consult the documentation of "Data.ByteString.Short" for more information.+--+-- Currently, a boxed unshared 'T.Text' has a memory footprint of 6 words (i.e. 48 bytes on 64-bit systems) plus 2 or 4 bytes per code-point (due to the internal UTF-16 representation). Each 'T.Text' value which can share its payload with another 'T.Text' requires only 4 words additionally. Unlike 'BS.ByteString', 'T.Text' use unpinned memory.+--+-- In comparison, the footprint of a boxed 'ShortText' is only 4 words (i.e. 32 bytes on 64-bit systems) plus 1, 2, 3, or 4 bytes per code-point (due to the internal UTF-8 representation).+-- It can be shown that for realistic data <http://utf8everywhere.org/#asian UTF-16 has a space overhead of 50% over UTF-8>.+--+-- @since 0.1+newtype ShortText = ShortText ShortByteString+                  deriving (Monoid,Data.Semigroup.Semigroup,Hashable,NFData)++instance Eq ShortText where+  {-# INLINE (==) #-}+  (==) x y+    | lx /= ly  = False+    | lx ==  0  = True+    | otherwise = case PrimOps.compareByteArrays# (toByteArray# x) 0# (toByteArray# y) 0# n# of+                    0# -> True+                    _  -> False+    where+      !lx@(I# n#) = toLength x+      !ly = toLength y++instance Ord ShortText where+  compare t1 t2+    | n == 0  = compare n1 n2+    | otherwise = case PrimOps.compareByteArrays# ba1# 0# ba2# 0# n# of+        r# | I# r# < 0 -> LT+           | I# r# > 0 -> GT+           | n1 < n2   -> LT+           | n1 > n2   -> GT+           | otherwise -> EQ+    where+      ba1# = toByteArray# t1+      ba2# = toByteArray# t2+      !n1 = toLength t1+      !n2 = toLength t2+      !n@(I# n#) = n1 `min` n2++instance Show ShortText where+    showsPrec p (ShortText b) = showsPrec p (decodeStringShort' utf8 b)+    show (ShortText b)        = show        (decodeStringShort' utf8 b)++instance Read ShortText where+    readsPrec p = map (\(x,s) -> (ShortText $ encodeStringShort utf8 x,s)) . readsPrec p++-- | @since 0.1.2+instance PrintfArg ShortText where+  formatArg txt = formatString $ toString txt++-- | The 'Binary' encoding matches the one for 'T.Text'+#if MIN_VERSION_binary(0,8,1)+instance Binary ShortText where+    put = put . toShortByteString+    get = do+        sbs <- get+        case fromShortByteString sbs of+          Nothing -> fail "Binary.get(ShortText): Invalid UTF-8 stream"+          Just st -> return st+#else+-- fallback via 'ByteString' instance+instance Binary ShortText where+    put = put . toByteString+    get = do+        bs <- get+        case fromByteString bs of+          Nothing -> fail "Binary.get(ShortText): Invalid UTF-8 stream"+          Just st -> return st+#endif++-- | \(\mathcal{O}(1)\) Test whether a 'ShortText' is empty.+--+-- >>> null ""+-- True+--+-- prop> null (singleton c) == False+--+-- prop> null t == (length t == 0)+--+-- @since 0.1+null :: ShortText -> Bool+null = BSS.null . toShortByteString++-- | \(\mathcal{O}(n)\) Count the number of Unicode code-points in a 'ShortText'.+--+-- >>> length "abcd€"+-- 5+--+-- >>> length ""+-- 0+--+-- prop> length t >= 0+--+-- @since 0.1+length :: ShortText -> Int+length st = fromIntegral $ unsafeDupablePerformIO (c_text_short_length (toByteArray# st) (toCSize st))++foreign import ccall unsafe "hs_text_short_length" c_text_short_length :: ByteArray# -> CSize -> IO CSize++-- | \(\mathcal{O}(n)\) Test whether 'ShortText' contains only ASCII code-points (i.e. only U+0000 through U+007F).+--+-- This is a more efficient version of @'all' 'Data.Char.isAscii'@.+--+-- >>> isAscii ""+-- True+--+-- >>> isAscii "abc\NUL"+-- True+--+-- >>> isAscii "abcd€"+-- False+--+-- prop> isAscii t == all (< '\x80') t+--+-- @since 0.1+isAscii :: ShortText -> Bool+isAscii st = (/= 0) $ unsafeDupablePerformIO (c_text_short_is_ascii (toByteArray# st) sz)+  where+    sz = toCSize st++foreign import ccall unsafe "hs_text_short_is_ascii" c_text_short_is_ascii :: ByteArray# -> CSize -> IO CInt++-- | \(\mathcal{O}(n)\) Test whether /all/ code points in 'ShortText' satisfy a predicate.+--+-- >>> all (const False) ""+-- True+--+-- >>> all (> 'c') "abcdabcd"+-- False+--+-- >>> all (/= 'c') "abdabd"+-- True+--+-- @since 0.1.2+all :: (Char -> Bool) -> ShortText -> Bool+all p st = isNothing (findOfs (not . p) st (B 0))++-- | \(\mathcal{O}(n)\) Return the left-most codepoint in 'ShortText' that satisfies the given predicate.+--+-- >>> find (> 'b') "abcdabcd"+-- Just 'c'+--+-- >>> find (> 'b') "ababab"+-- Nothing+--+-- @since 0.1.2+find :: (Char -> Bool) -> ShortText -> Maybe Char+find p st = go 0+  where+    go !ofs+      | ofs >= sz  = Nothing+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq`+                        if p c+                        then Just c+                        else go ofs'++    !sz = toB st++-- | \(\mathcal{O}(n)\) Return the index of the left-most codepoint in 'ShortText' that satisfies the given predicate.+--+-- >>> findIndex (> 'b') "abcdabcdef"+-- Just 2+--+-- >>> findIndex (> 'b') "ababab"+-- Nothing+--+-- prop> (indexMaybe t =<< findIndex p t) == find p t+--+-- @since 0.1.2+findIndex :: (Char -> Bool) -> ShortText -> Maybe Int+findIndex p st = go 0 0+  where+    go !ofs !i+      | ofs >= sz  = Nothing+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq`+                        if p c+                        then Just i+                        else go ofs' (i+1)++    !sz = toB st++-- internal helper+{-# INLINE findOfs #-}+findOfs :: (Char -> Bool) -> ShortText -> B -> Maybe B+findOfs p st = go+  where+    go :: B -> Maybe B+    go !ofs | ofs >= sz  = Nothing+    go !ofs | p c       = Just ofs+            | otherwise = go ofs'+      where+        (c,ofs') = decodeCharAtOfs st ofs++    !sz = toB st++{-# INLINE findOfsRev #-}+findOfsRev :: (Char -> Bool) -> ShortText -> B -> Maybe B+findOfsRev p st = go+  where+    go (B 0) = Nothing+    go !ofs+      | p (cp2ch cp) = Just ofs+      | otherwise    = go (ofs-cpLen cp)+      where+        !cp = readCodePointRev st ofs++-- | \(\mathcal{O}(n)\) Split 'ShortText' into longest prefix satisfying the given predicate and the remaining suffix.+--+-- >>> span (< 'c') "abcdabcd"+-- ("ab","cdabcd")+--+-- prop> fst (span p t) <> snd (span p t) == t+--+-- @since 0.1.2+span :: (Char -> Bool) -> ShortText -> (ShortText,ShortText)+span p st+  | Just ofs <- findOfs (not . p) st (B 0) = splitAtOfs ofs st+  | otherwise = (st,mempty)++-- | \(\mathcal{O}(n)\) Split 'ShortText' into longest suffix satisfying the given predicate and the preceding prefix.+--+-- >>> spanEnd (> 'c') "abcdabcd"+-- ("abcdabc","d")+--+-- prop> fst (spanEnd p t) <> snd (spanEnd p t) == t+--+-- @since 0.1.2+spanEnd :: (Char -> Bool) -> ShortText -> (ShortText,ShortText)+spanEnd p st+  | Just ofs <- findOfsRev (not . p) st (toB st) = splitAtOfs ofs st+  | otherwise = (mempty,st)++----------------------------------------------------------------------------++toCSize :: ShortText -> CSize+toCSize = fromIntegral . BSS.length . toShortByteString++toB :: ShortText -> B+toB = fromIntegral . BSS.length . toShortByteString++toLength :: ShortText -> Int+toLength st = I# (toLength# st)++toLength# :: ShortText -> Int#+toLength# st = GHC.Exts.sizeofByteArray# (toByteArray# st)++toByteArray# :: ShortText -> ByteArray#+toByteArray# (ShortText (BSSI.SBS ba#)) = ba#++-- | \(\mathcal{O}(0)\) Converts to UTF-8 encoded 'ShortByteString'+--+-- This operation has effectively no overhead, as it's currently merely a @newtype@-cast.+--+-- @since 0.1+toShortByteString :: ShortText -> ShortByteString+toShortByteString (ShortText b) = b++-- | \(\mathcal{O}(n)\) Converts to UTF-8 encoded 'BS.ByteString'+--+-- @since 0.1+toByteString :: ShortText -> BS.ByteString+toByteString = BSS.fromShort . toShortByteString++-- | Construct a 'BB.Builder' that encodes 'ShortText' as UTF-8.+--+-- @since 0.1+toBuilder :: ShortText -> BB.Builder+toBuilder = BB.shortByteString . toShortByteString++-- | \(\mathcal{O}(n)\) Convert to 'String'+--+-- prop> (fromString . toString) t == t+--+-- __Note__: See documentation of 'fromString' for why @('toString' . 'fromString')@ is not an identity function.+--+-- @since 0.1+toString :: ShortText -> String+-- NOTE: impl below beats+--   toString = decodeStringShort' utf8 . toShortByteString+-- except for smallish strings+toString st = go 0+  where+    go !ofs+      | ofs >= sz  = []+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq` (c : go ofs')++    !sz = toB st++----------------------------------------------------------------------------+-- Folds++-- | \(\mathcal{O}(n)\) Reduces the characters of the 'ShortText' with+-- the binary operator and an initial in forward direction (i.e. from+-- left to right).+--+-- >>> foldl (\_ _ -> True) False ""+-- False+--+-- >>> foldl (\s c -> c : s) ['.'] "abcd"+-- "dcba."+--+-- @since 0.1.2+foldl :: (a -> Char -> a) -> a -> ShortText -> a+foldl f z st = go 0 z+  where+    go !ofs acc+      | ofs >= sz  = acc+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq` go ofs' (f acc c)++    !sz = toB st++-- | \(\mathcal{O}(n)\) Reduces the characters of the 'ShortText' with the binary operator.+--+-- >>> foldl1 max "abcdcba"+-- 'd'+--+-- >>> foldl1 const "abcd"+-- 'a'+--+-- >>> foldl1 (flip const) "abcd"+-- 'd'+--+-- __Note__: Will throw an 'error' exception if index is out of bounds.+--+-- @since 0.1.2+foldl1 :: (Char -> Char -> Char) -> ShortText -> Char+foldl1 f st+  | sz == 0    = error "foldl1: empty ShortText"+  | otherwise  = go c0sz c0+  where+    go !ofs acc+      | ofs >= sz  = acc+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq` go ofs' (f acc c)+    !sz = toB st+    (c0,c0sz) = decodeCharAtOfs st (B 0)++-- | \(\mathcal{O}(n)\) Strict version of 'foldl'.+--+-- @since 0.1.2+foldl' :: (a -> Char -> a) -> a -> ShortText -> a+foldl' f !z st = go 0 z+  where+    go !ofs !acc+      | ofs >= sz  = acc+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq` go ofs' (f acc c)++    !sz = toB st++-- | \(\mathcal{O}(n)\) Strict version of 'foldl1'.+--+-- @since 0.1.2+foldl1' :: (Char -> Char -> Char) -> ShortText -> Char+foldl1' f st+  | sz == 0    = error "foldl1: empty ShortText"+  | otherwise  = go c0sz c0+  where+    go !ofs !acc+      | ofs >= sz  = acc+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq` go ofs' (f acc c)+    !sz = toB st+    (c0,c0sz) = decodeCharAtOfs st (B 0)++-- | \(\mathcal{O}(n)\) Reduces the characters of the 'ShortText' with+-- the binary operator and an initial in reverse direction (i.e. from+-- right to left).+--+-- >>> foldr (\_ _ -> True) False ""+-- False+--+-- >>> foldr (:) ['.'] "abcd"+-- "abcd."+--+-- @since 0.1.2+foldr :: (Char -> a -> a) -> a -> ShortText -> a+foldr f z st = go 0+  where+    go !ofs+      | ofs >= sz  = z+      | otherwise  = let (c,ofs') = decodeCharAtOfs st ofs+                     in c `seq` ofs' `seq` f c (go ofs')++    !sz = toB st++-- | \(\mathcal{O}(n)\) Reduces the characters of the 'ShortText' with the binary operator.+--+-- >>> foldr1 max "abcdcba"+-- 'd'+--+-- >>> foldr1 const "abcd"+-- 'a'+--+-- >>> foldr1 (flip const) "abcd"+-- 'd'+--+-- __Note__: Will throw an 'error' exception if index is out of bounds.+--+-- @since 0.1.2+foldr1 :: (Char -> Char -> Char) -> ShortText -> Char+foldr1 f st+  | sz == 0    = error "foldr1: empty ShortText"+  | otherwise  = go 0+  where+    go !ofs = let (c,ofs') = decodeCharAtOfs st ofs+              in c `seq` ofs' `seq`+                 (if ofs' >= sz then c else f c (go ofs'))++    !sz = toB st++-- | \(\mathcal{O}(n)\) Convert to 'T.Text'+--+-- prop> (fromText . toText) t == t+--+-- prop> (toText . fromText) t == t+--+-- This is currently not \(\mathcal{O}(1)\) because currently 'T.Text' uses UTF-16 as its internal representation.+-- In the event that 'T.Text' will change its internal representation to UTF-8 this operation will become \(\mathcal{O}(1)\).+--+-- @since 0.1+toText :: ShortText -> T.Text+toText = T.decodeUtf8 . toByteString++----++-- | \(\mathcal{O}(n)\) Construct/pack from 'String'+--+-- >>> fromString []+-- ""+--+-- >>> fromString ['a','b','c']+-- "abc"+--+-- >>> fromString ['\55295','\55296','\57343','\57344'] -- U+D7FF U+D800 U+DFFF U+E000+-- "\55295\65533\65533\57344"+--+-- __Note__: This function is total because it replaces the (invalid) code-points U+D800 through U+DFFF with the replacement character U+FFFD.+--+-- @since 0.1+fromString :: String -> ShortText+fromString []  = mempty+fromString [c] = singleton c+fromString s = ShortText . encodeStringShort utf8 . map r $ s+  where+    r c | isSurr (ord c) = '\xFFFD'+        | otherwise      = c++-- | \(\mathcal{O}(n)\) Construct 'ShortText' from 'T.Text'+--+-- This is currently not \(\mathcal{O}(1)\) because currently 'T.Text' uses UTF-16 as its internal representation.+-- In the event that 'T.Text' will change its internal representation to UTF-8 this operation will become \(\mathcal{O}(1)\).+--+-- @since 0.1+fromText :: T.Text -> ShortText+fromText = fromByteStringUnsafe . T.encodeUtf8++-- | \(\mathcal{O}(n)\) Construct 'ShortText' from UTF-8 encoded 'ShortByteString'+--+-- This operation doesn't copy the input 'ShortByteString' but it+-- cannot be \(\mathcal{O}(1)\) because we need to validate the UTF-8 encoding.+--+-- Returns 'Nothing' in case of invalid UTF-8 encoding.+--+-- >>> fromShortByteString "\x00\x38\xF0\x90\x8C\x9A" -- U+00 U+38 U+1031A+-- Just "\NUL8\66330"+--+-- >>> fromShortByteString "\xC0\x80" -- invalid denormalised U+00+-- Nothing+--+-- >>> fromShortByteString "\xED\xA0\x80" -- U+D800 (non-scalar code-point)+-- Nothing+--+-- >>> fromShortByteString "\xF4\x8f\xbf\xbf" -- U+10FFFF+-- Just "\1114111"+--+-- >>> fromShortByteString "\xF4\x90\x80\x80" -- U+110000 (invalid)+-- Nothing+--+-- prop> fromShortByteString (toShortByteString t) == Just t+--+-- @since 0.1+fromShortByteString :: ShortByteString -> Maybe ShortText+fromShortByteString sbs+  | isValidUtf8 st  = Just st+  | otherwise       = Nothing+  where+    st = ShortText sbs++-- | \(\mathcal{O}(0)\) Construct 'ShortText' from UTF-8 encoded 'ShortByteString'+--+-- This operation has effectively no overhead, as it's currently merely a @newtype@-cast.+--+-- __WARNING__: Unlike the safe 'fromShortByteString' conversion, this+-- conversion is /unsafe/ as it doesn't validate the well-formedness of the+-- UTF-8 encoding.+--+-- @since 0.1.1+fromShortByteStringUnsafe :: ShortByteString -> ShortText+fromShortByteStringUnsafe = ShortText++-- | \(\mathcal{O}(n)\) Construct 'ShortText' from UTF-8 encoded 'BS.ByteString'+--+-- 'fromByteString' accepts (or rejects) the same input data as 'fromShortByteString'.+--+-- Returns 'Nothing' in case of invalid UTF-8 encoding.+--+-- @since 0.1+fromByteString :: BS.ByteString -> Maybe ShortText+fromByteString = fromShortByteString . BSS.toShort++-- | \(\mathcal{O}(n)\) Construct 'ShortText' from UTF-8 encoded 'BS.ByteString'+--+-- This operation is \(\mathcal{O}(n)\) because the 'BS.ByteString' needs to be+-- copied into an unpinned 'ByteArray#'.+--+-- __WARNING__: Unlike the safe 'fromByteString' conversion, this+-- conversion is /unsafe/ as it doesn't validate the well-formedness of the+-- UTF-8 encoding.+--+-- @since 0.1.1+fromByteStringUnsafe :: BS.ByteString -> ShortText+fromByteStringUnsafe = ShortText . BSS.toShort++----------------------------------------------------------------------------++encodeString :: TextEncoding -> String -> BS.ByteString+encodeString te str = unsafePerformIO $ GHC.withCStringLen te str BS.packCStringLen++-- decodeString :: TextEncoding -> BS.ByteString -> Maybe String+-- decodeString te bs = cvtEx $ unsafePerformIO $ try $ BS.useAsCStringLen bs (GHC.peekCStringLen te)+--   where+--     cvtEx :: Either IOException a -> Maybe a+--     cvtEx = either (const Nothing) Just++decodeString' :: TextEncoding -> BS.ByteString -> String+decodeString' te bs = unsafePerformIO $ BS.useAsCStringLen bs (GHC.peekCStringLen te)++decodeStringShort' :: TextEncoding -> ShortByteString -> String+decodeStringShort' te = decodeString' te . BSS.fromShort++encodeStringShort :: TextEncoding -> String -> BSS.ShortByteString+encodeStringShort te = BSS.toShort . encodeString te++-- isValidUtf8' :: ShortText -> Int+-- isValidUtf8' st = fromIntegral $ unsafeDupablePerformIO (c_text_short_is_valid_utf8 (toByteArray# st) (toCSize st))++isValidUtf8 :: ShortText -> Bool+isValidUtf8 st = (==0) $ unsafeDupablePerformIO (c_text_short_is_valid_utf8 (toByteArray# st) (toCSize st))++type CCodePoint = Word++foreign import ccall unsafe "hs_text_short_is_valid_utf8" c_text_short_is_valid_utf8 :: ByteArray# -> CSize -> IO CInt++foreign import ccall unsafe "hs_text_short_index_cp" c_text_short_index :: ByteArray# -> CSize -> CSize -> IO CCodePoint++-- | \(\mathcal{O}(n)\) Lookup /i/-th code-point in 'ShortText'.+--+-- Returns 'Nothing' if out of bounds.+--+-- prop> indexMaybe (singleton c) 0 == Just c+--+-- prop> indexMaybe t 0 == fmap fst (uncons t)+--+-- prop> indexMaybe mempty i == Nothing+--+-- @since 0.1.2+indexMaybe :: ShortText -> Int -> Maybe Char+indexMaybe st i+  | i < 0      = Nothing+  | otherwise  = cp2chSafe cp+  where+    cp = CP $ unsafeDupablePerformIO (c_text_short_index (toByteArray# st) (toCSize st) (fromIntegral i))++-- | \(\mathcal{O}(n)\) Lookup /i/-th code-point from the end of 'ShortText'.+--+-- Returns 'Nothing' if out of bounds.+--+-- prop> indexEndMaybe (singleton c) 0 == Just c+--+-- prop> indexEndMaybe t 0 == fmap snd (unsnoc t)+--+-- prop> indexEndMaybe mempty i == Nothing+--+-- @since 0.1.2+indexEndMaybe :: ShortText -> Int -> Maybe Char+indexEndMaybe st i+  | i < 0      = Nothing+  | otherwise  = cp2chSafe cp+  where+    cp = CP $ unsafeDupablePerformIO (c_text_short_index_rev (toByteArray# st) (toCSize st) (fromIntegral i))++foreign import ccall unsafe "hs_text_short_index_cp_rev" c_text_short_index_rev :: ByteArray# -> CSize -> CSize -> IO CCodePoint+++-- | \(\mathcal{O}(n)\) Split 'ShortText' into two halves.+--+-- @'splitAtOfs n t@ returns a pair of 'ShortText' with the following properties:+--+-- prop> length (fst (splitAt n t)) == min (length t) (max 0 n)+--+-- prop> fst (splitAt n t) <> snd (splitAt n t) == t+--+-- >>> splitAt 2 "abcdef"+-- ("ab","cdef")+--+-- >>> splitAt 10 "abcdef"+-- ("abcdef","")+--+-- >>> splitAt (-1) "abcdef"+-- ("","abcdef")+--+-- @since 0.1.2+splitAt :: Int -> ShortText -> (ShortText,ShortText)+splitAt i st+  | i <= 0    = (mempty,st)+  | otherwise = splitAtOfs ofs st+  where+    ofs   = csizeToB $+            unsafeDupablePerformIO (c_text_short_index_ofs (toByteArray# st) stsz (fromIntegral i))+    stsz  = toCSize st++-- | \(\mathcal{O}(n)\) Split 'ShortText' into two halves.+--+-- @'splitAtEnd' n t@ returns a pair of 'ShortText' with the following properties:+--+-- prop> length (snd (splitAtEnd n t)) == min (length t) (max 0 n)+--+-- prop> fst (splitAtEnd n t) <> snd (splitAtEnd n t) == t+--+-- prop> splitAtEnd n t == splitAt (length t - n) t+--+-- >>> splitAtEnd 2 "abcdef"+-- ("abcd","ef")+--+-- >>> splitAtEnd 10 "abcdef"+-- ("","abcdef")+--+-- >>> splitAtEnd (-1) "abcdef"+-- ("abcdef","")+--+-- @since 0.1.2+splitAtEnd :: Int -> ShortText -> (ShortText,ShortText)+splitAtEnd i st+  | i <= 0      = (st,mempty)+  | ofs >= stsz = (mempty,st)+  | otherwise   = splitAtOfs ofs st+  where+    ofs   = csizeToB $+            unsafeDupablePerformIO (c_text_short_index_ofs_rev (toByteArray# st) (toCSize st) (fromIntegral (i-1)))+    stsz  = toB st++{-# INLINE splitAtOfs #-}+splitAtOfs :: B -> ShortText -> (ShortText,ShortText)+splitAtOfs ofs st+  | ofs  == 0    = (mempty,st)+  | ofs  >  stsz = (st,mempty)+  | otherwise    = (slice st 0 ofs, slice st ofs (stsz-ofs))+  where+    !stsz  = toB st++foreign import ccall unsafe "hs_text_short_index_ofs" c_text_short_index_ofs :: ByteArray# -> CSize -> CSize -> IO CSize++foreign import ccall unsafe "hs_text_short_index_ofs_rev" c_text_short_index_ofs_rev :: ByteArray# -> CSize -> CSize -> IO CSize+++-- | \(\mathcal{O}(n)\) Inverse operation to 'cons'+--+-- Returns 'Nothing' for empty input 'ShortText'.+--+-- prop> uncons (cons c t) == Just (c,t)+--+-- >>> uncons ""+-- Nothing+--+-- >>> uncons "fmap"+-- Just ('f',"map")+--+-- @since 0.1.2+uncons :: ShortText -> Maybe (Char,ShortText)+uncons st+  | null st    = Nothing+  | len2 == 0  = Just (c0, mempty)+  | otherwise  = Just (c0, slice st ofs len2)+  where+    c0  = cp2ch cp0+    cp0 = readCodePoint st 0+    ofs = cpLen cp0+    len2 = toB st - ofs++-- | \(\mathcal{O}(n)\) Inverse operation to 'snoc'+--+-- Returns 'Nothing' for empty input 'ShortText'.+--+-- prop> unsnoc (snoc t c) == Just (t,c)+--+-- >>> unsnoc ""+-- Nothing+--+-- >>> unsnoc "fmap"+-- Just ("fma",'p')+--+-- @since 0.1.2+unsnoc :: ShortText -> Maybe (ShortText,Char)+unsnoc st+  | null st    = Nothing+  | len1 == 0  = Just (mempty, c0)+  | otherwise  = Just (slice st 0 len1, c0)+  where+    c0  = cp2ch cp0+    cp0 = readCodePointRev st stsz+    stsz = toB st+    len1 = stsz - cpLen cp0++-- | \(\mathcal{O}(n)\) Tests whether the first 'ShortText' is a prefix of the second 'ShortText'+--+-- >>> isPrefixOf "ab" "abcdef"+-- True+--+-- >>> isPrefixOf "ac" "abcdef"+-- False+--+-- prop> isPrefixOf "" t == True+--+-- prop> isPrefixOf t t == True+--+-- @since 0.1.2+isPrefixOf :: ShortText -> ShortText -> Bool+isPrefixOf x y+  | lx > ly = False+  | lx == 0 = True+  | otherwise = case PrimOps.compareByteArrays# (toByteArray# x) 0# (toByteArray# y) 0# n# of+                  0# -> True+                  _  -> False+  where+    !lx@(I# n#) = toLength x+    !ly = toLength y++-- | \(\mathcal{O}(n)\) Strip prefix from second 'ShortText' argument.+--+-- Returns 'Nothing' if first argument is not a prefix of the second argument.+--+-- >>> stripPrefix "text-" "text-short"+-- Just "short"+--+-- >>> stripPrefix "test-" "text-short"+-- Nothing+--+-- @since 0.1.2+stripPrefix :: ShortText -> ShortText -> Maybe ShortText+stripPrefix pfx t+  | isPrefixOf pfx t = Just $! snd (splitAtOfs (toB pfx) t)+  | otherwise        = Nothing++-- | \(\mathcal{O}(n)\) Tests whether the first 'ShortText' is a suffix of the second 'ShortText'+--+-- >>> isSuffixOf "ef" "abcdef"+-- True+--+-- >>> isPrefixOf "df" "abcdef"+-- False+--+-- prop> isSuffixOf "" t == True+--+-- prop> isSuffixOf t t == True+--+-- @since 0.1.2+isSuffixOf :: ShortText -> ShortText -> Bool+isSuffixOf x y+  | lx > ly = False+  | lx == 0 = True+  | otherwise = case PrimOps.compareByteArrays# (toByteArray# x) 0# (toByteArray# y) ofs2# n# of+                  0# -> True+                  _  -> False+  where+    !(I# ofs2#) = ly - lx+    !lx@(I# n#) = toLength x+    !ly = toLength y++-- | \(\mathcal{O}(n)\) Strip suffix from second 'ShortText' argument.+--+-- Returns 'Nothing' if first argument is not a suffix of the second argument.+--+-- >>> stripSuffix "-short" "text-short"+-- Just "text"+--+-- >>> stripSuffix "-utf8" "text-short"+-- Nothing+--+-- @since 0.1.2+stripSuffix :: ShortText -> ShortText -> Maybe ShortText+stripSuffix sfx t+  | isSuffixOf sfx t = Just $! fst (splitAtOfs pfxLen t)+  | otherwise        = Nothing+  where+    pfxLen = toB t - toB sfx++----------------------------------------------------------------------------++-- | \(\mathcal{O}(n)\) Insert character between characters of 'ShortText'.+--+-- >>> intersperse '*' "_"+-- "_"+--+-- >>> intersperse '*' "MASH"+-- "M*A*S*H"+--+-- @since 0.1.2+intersperse :: Char -> ShortText -> ShortText+intersperse c st+  | null st = mempty+  | sn == 1 = st+  | otherwise = create newsz $ \mba -> do+      let !cp0 = readCodePoint st 0+          !cp0sz = cpLen cp0+      writeCodePointN cp0sz mba 0 cp0+      go mba (sn - 1) cp0sz cp0sz+  where+    newsz = ssz + ((sn-1) `mulB` csz)+    ssz = toB st+    sn  = length st+    csz = cpLen cp+    cp  = ch2cp c++    go :: MBA s -> Int -> B -> B -> ST s ()+    go _   0 !_  !_   = return ()+    go mba n ofs ofs2 = do+      let !cp1 = readCodePoint st ofs2+          !cp1sz = cpLen cp1+      writeCodePointN csz   mba ofs cp+      writeCodePointN cp1sz mba (ofs+csz) cp1+      go mba (n-1) (ofs+csz+cp1sz) (ofs2+cp1sz)++-- | \(\mathcal{O}(n)\) Insert 'ShortText' inbetween list of 'ShortText's.+--+-- >>> intercalate ", " []+-- ""+--+-- >>> intercalate ", " ["foo"]+-- "foo"+--+-- >>> intercalate ", " ["foo","bar","doo"]+-- "foo, bar, doo"+--+-- prop> intercalate "" ts == concat ts+--+-- @since 0.1.2+intercalate :: ShortText -> [ShortText] -> ShortText+intercalate _ []  = mempty+intercalate _ [t] = t+intercalate sep ts+  | null sep   = mconcat ts+  | otherwise  = mconcat (List.intersperse sep ts)++-- | \(\mathcal{O}(n*m)\) Replicate a 'ShortText'.+--+-- A repetition count smaller than 1 results in an empty string result.+--+-- >>> replicate 3 "jobs!"+-- "jobs!jobs!jobs!"+--+-- >>> replicate 10000 ""+-- ""+--+-- >>> replicate 0 "nothing"+-- ""+--+-- prop> length (replicate n t) == max 0 n * length t+--+-- @since 0.1.2+replicate :: Int -> ShortText -> ShortText+replicate n0 t+  | n0 < 1     = mempty+  | null t    = mempty+  | otherwise = create (n0 `mulB` sz) (go 0)+  where+    go :: Int -> MBA s -> ST s ()+    go j mba+      | j == n0    = return ()+      | otherwise  = do+          copyByteArray t 0 mba (j `mulB` sz) sz+          go (j+1) mba++    sz = toB t++-- | \(\mathcal{O}(n)\) Reverse characters in 'ShortText'.+--+-- >>> reverse "star live desserts"+-- "stressed evil rats"+--+-- prop> reverse (singleton c) == singleton c+--+-- prop> reverse (reverse t) == t+--+-- @since 0.1.2+reverse :: ShortText -> ShortText+reverse st+  | null st   = mempty+  | sn == 1   = st+  | otherwise = create sz $ go sn 0+  where+    sz = toB st+    sn = length st++    go :: Int -> B -> MBA s -> ST s ()+    go 0 !_  _   = return ()+    go i ofs mba = do+      let !cp   = readCodePoint st ofs+          !cpsz = cpLen cp+          !ofs' = ofs+cpsz+      writeCodePointN cpsz mba (sz - ofs') cp+      go (i-1) ofs' mba+++-- | \(\mathcal{O}(n)\) Remove characters from 'ShortText' which don't satisfy given predicate.+--+-- >>> filter (`notElem` ['a','e','i','o','u']) "You don't need vowels to convey information!"+-- "Y dn't nd vwls t cnvy nfrmtn!"+--+-- prop> filter (const False) t == ""+--+-- prop> filter (const True) t == t+--+-- prop> length (filter p t) <= length t+--+-- prop> filter p t == pack [ c | c <- unpack t, p c ]+--+-- @since 0.1.2+filter :: (Char -> Bool) -> ShortText -> ShortText+filter p t+  = case (mofs1,mofs2) of+      (Nothing,   _)       -> t -- no non-accepted characters found+      (Just 0,    Nothing) -> mempty -- no accepted characters found+      (Just ofs1, Nothing) -> slice t 0 ofs1 -- only prefix accepted+      (Just ofs1, Just ofs2) -> createShrink (t0sz-(ofs2-ofs1)) $ \mba -> do+        -- copy accepted prefix+        copyByteArray t 0 mba 0 ofs1+        -- [ofs1 .. ofs2) are a non-accepted region+        -- filter rest after ofs2+        t1sz <- go mba ofs2 ofs1+        return t1sz+  where+    mofs1 = findOfs (not . p) t (B 0) -- first non-accepted Char+    mofs2 = findOfs p t (fromMaybe (B 0) mofs1) -- first accepted Char++    t0sz = toB t++    go :: MBA s -> B -> B -> ST s B+    go mba !t0ofs !t1ofs+      | t0ofs >= t0sz = return t1ofs+      | otherwise = let !cp = readCodePoint t t0ofs+                        !cpsz = cpLen cp+                    in if p (cp2ch cp)+                       then writeCodePointN cpsz mba t1ofs cp >>+                            go mba (t0ofs+cpsz) (t1ofs+cpsz)+                       else go mba (t0ofs+cpsz) t1ofs -- skip code-point++-- | \(\mathcal{O}(n)\) Strip characters from the beginning end and of 'ShortText' which satisfy given predicate.+--+-- >>> dropAround (== ' ') "   white   space   "+-- "white   space"+--+-- >>> dropAround (> 'a') "bcdefghi"+-- ""+--+-- @since 0.1.2+dropAround :: (Char -> Bool) -> ShortText -> ShortText+dropAround p t0 = case (mofs1,mofs2) of+                    (Nothing,_) -> mempty+                    (Just ofs1,Just ofs2)+                      | ofs1 == 0, ofs2 == t0sz  -> t0+                      | ofs1 < ofs2  -> create (ofs2-ofs1) $ \mba -> do+                          copyByteArray t0 ofs1 mba (B 0) (ofs2-ofs1)+                    (_,_) -> error "dropAround: the impossible happened"+  where+    mofs1 = findOfs    (not . p) t0 (B 0)+    mofs2 = findOfsRev (not . p) t0 t0sz+    t0sz = toB t0++----------------------------------------------------------------------------++-- | Construct a new 'ShortText' from an existing one by slicing+--+-- NB: The 'CSize' arguments refer to byte-offsets+slice :: ShortText -> B -> B -> ShortText+slice st ofs len+  | ofs < 0    = error "invalid offset"+  | len < 0    = error "invalid length"+  | len' == 0  = mempty+  | otherwise  = create len' $ \mba -> copyByteArray st ofs' mba 0 len'+  where+    len0 = toB st+    len' = max 0 (min len (len0-ofs))+    ofs' = max 0 ofs++----------------------------------------------------------------------------+-- low-level MutableByteArray# helpers++-- | Byte offset (or size) in bytes+--+-- This currently wraps an 'Int' because this is what GHC's primops+-- currently use for byte offsets/sizes.+newtype B = B { unB :: Int }+          deriving (Ord,Eq,Num)++{- TODO: introduce operators for 'B' to avoid 'Num' -}++mulB :: Int -> B -> B+mulB n (B b) = B (n*b)++csizeFromB :: B -> CSize+csizeFromB = fromIntegral . unB++csizeToB :: CSize -> B+csizeToB = B . fromIntegral++data MBA s = MBA# { unMBA# :: MutableByteArray# s }++{-# INLINE create #-}+create :: B -> (forall s. MBA s -> ST s ()) -> ShortText+create n go = runST $ do+  mba <- newByteArray n+  go mba+  unsafeFreeze mba++{-# INLINE createShrink #-}+createShrink :: B -> (forall s. MBA s -> ST s B) -> ShortText+createShrink n go = runST $ do+  mba <- newByteArray n+  n' <- go mba+  if n' < n+    then unsafeFreezeShrink mba n'+    else unsafeFreeze mba++{-# INLINE unsafeFreeze #-}+unsafeFreeze :: MBA s -> ST s ShortText+unsafeFreeze (MBA# mba#)+  = ST $ \s -> case GHC.Exts.unsafeFreezeByteArray# mba# s of+                 (# s', ba# #) -> (# s', ShortText (BSSI.SBS ba#) #)++{-# INLINE copyByteArray #-}+copyByteArray :: ShortText -> B -> MBA s -> B -> B -> ST s ()+copyByteArray (ShortText (BSSI.SBS src#)) (B (I# src_off#)) (MBA# dst#) (B (I# dst_off#)) (B (I# len#))+  = ST $ \s -> case GHC.Exts.copyByteArray# src# src_off# dst# dst_off# len# s of+                 s' -> (# s', () #)++{-# INLINE newByteArray #-}+newByteArray :: B -> ST s (MBA s)+newByteArray (B (I# n#))+  = ST $ \s -> case GHC.Exts.newByteArray# n# s of+                 (# s', mba# #) -> (# s', MBA# mba# #)++{-# INLINE writeWord8Array #-}+writeWord8Array :: MBA s -> B -> Word -> ST s ()+writeWord8Array (MBA# mba#) (B (I# i#)) (W# w#)+  = ST $ \s -> case GHC.Exts.writeWord8Array# mba# i# w# s of+                 s' -> (# s', () #)+{- not needed yet+{-# INLINE indexWord8Array #-}+indexWord8Array :: ShortText -> B -> Word+indexWord8Array (ShortText (BSSI.SBS ba#)) (B (I# i#)) = W# (GHC.Exts.indexWord8Array# ba# i#)+-}++{-# INLINE copyAddrToByteArray #-}+copyAddrToByteArray :: Ptr a -> MBA RealWorld -> B -> B -> ST RealWorld ()+copyAddrToByteArray (Ptr src#) (MBA# dst#) (B (I# dst_off#)) (B (I# len#))+  = ST $ \s -> case GHC.Exts.copyAddrToByteArray# src# dst# dst_off# len# s of+                 s' -> (# s', () #)++----------------------------------------------------------------------------+-- unsafeFreezeShrink++#if __GLASGOW_HASKELL__ >= 710+-- for GHC versions which have the 'shrinkMutableByteArray#' primop+{-# INLINE unsafeFreezeShrink #-}+unsafeFreezeShrink :: MBA s -> B -> ST s ShortText+unsafeFreezeShrink mba n = do+  shrink mba n+  unsafeFreeze mba++{-# INLINE shrink #-}+shrink :: MBA s -> B -> ST s ()+shrink (MBA# mba#) (B (I# i#))+  = ST $ \s -> case GHC.Exts.shrinkMutableByteArray# mba# i# s of+                 s' -> (# s', () #)+#else+-- legacy code for GHC versions which lack `shrinkMutableByteArray#` primop+{-# INLINE unsafeFreezeShrink #-}+unsafeFreezeShrink :: MBA s -> B -> ST s ShortText+unsafeFreezeShrink mba0 n = do+  mba' <- newByteArray n+  copyByteArray2 mba0 0 mba' 0 n+  unsafeFreeze mba'++{-# INLINE copyByteArray2 #-}+copyByteArray2 :: MBA s -> B -> MBA s -> B -> B -> ST s ()+copyByteArray2 (MBA# src#) (B (I# src_off#)) (MBA# dst#) (B (I# dst_off#)) (B( I# len#))+  = ST $ \s -> case GHC.Exts.copyMutableByteArray# src# src_off# dst# dst_off# len# s of+                 s' -> (# s', () #)+#endif++----------------------------------------------------------------------------+-- Helpers for encoding code points into UTF-8 code units+--+--   7 bits| <    0x80 | 0xxxxxxx+--  11 bits| <   0x800 | 110yyyyx  10xxxxxx+--  16 bits| < 0x10000 | 1110yyyy  10yxxxxx  10xxxxxx+--  21 bits|           | 11110yyy  10yyxxxx  10xxxxxx  10xxxxxx++-- | Unicode Code-point+--+-- Keeping it as a 'Word' is more convenient for bit-ops and FFI+newtype CP = CP Word++{-# INLINE ch2cp #-}+ch2cp :: Char -> CP+ch2cp (ord -> ci)+  | isSurr ci  = CP 0xFFFD+  | otherwise  = CP (fromIntegral ci)++{-# INLINE isSurr #-}+isSurr :: (Num i, Bits i) => i -> Bool+isSurr ci = ci .&. 0xfff800 == 0xd800++{-# INLINE cp2ch #-}+cp2ch :: CP -> Char+cp2ch (CP w) = (w < 0x110000) `assert` unsafeChr (fromIntegral w)++-- used/needed by index-lookup functions to encode out of bounds+cp2chSafe :: CP -> Maybe Char+cp2chSafe cp+  | cpNull cp = Nothing+  | otherwise = Just $! cp2ch cp+  where+    cpNull :: CP -> Bool+    cpNull (CP w) = w >= 0x110000++{-# INLINE cpLen #-}+cpLen :: CP -> B+cpLen (CP cp)+  | cp <    0x80  = B 1+  | cp <   0x800  = B 2+  | cp < 0x10000  = B 3+  | otherwise     = B 4++-- convenience wrapper; unsafe like readCodePoint+{-# INLINE decodeCharAtOfs #-}+decodeCharAtOfs :: ShortText -> B -> (Char,B)+decodeCharAtOfs st ofs = (c,ofs')+  where+    c    = cp2ch cp+    ofs' = ofs + cpLen cp+    cp   = readCodePoint st ofs+{- pure version of decodeCharAtOfs, but unfortunately significantly slower++decodeCharAtOfs st ofs+  | b0 < 0x80 = (cp2ch $ CP b0,ofs + B 1)+  | otherwise = case b0 `unsafeShiftR` 4 of+                  0xf -> (cp2ch $ CP go4, ofs + B 4)+                  0xe -> (cp2ch $ CP go3, ofs + B 3)+                  _   -> (cp2ch $ CP go2, ofs + B 2)+  where+    b0    = buf 0+    buf j = indexWord8Array st (ofs+j)++    go2 =     ((b0    .&. 0x1f) `unsafeShiftL` 6)+          .|.  (buf 1 .&. 0x3f)++    go3 =     ((b0    .&. 0x0f) `unsafeShiftL` (6+6))+          .|. ((buf 1 .&. 0x3f) `unsafeShiftL` 6)+          .|.  (buf 2 .&. 0x3f)++    go4 =     ((b0    .&. 0x07) `unsafeShiftL` (6+6+6))+          .|. ((buf 1 .&. 0x3f) `unsafeShiftL` (6+6))+          .|. ((buf 2 .&. 0x3f) `unsafeShiftL` 6)+          .|.  (buf 3 .&. 0x3f)+-}+++-- | \(\mathcal{O}(1)\) Construct 'ShortText' from single codepoint.+--+-- prop> singleton c == pack [c]+--+-- prop> length (singleton c) == 1+--+-- >>> singleton 'A'+-- "A"+--+-- >>> map singleton ['\55295','\55296','\57343','\57344'] -- U+D7FF U+D800 U+DFFF U+E000+-- ["\55295","\65533","\65533","\57344"]+--+-- __Note__: This function is total because it replaces the (invalid) code-points U+D800 through U+DFFF with the replacement character U+FFFD.+--+-- @since 0.1.2+singleton :: Char -> ShortText+singleton = singleton' . ch2cp++singleton' :: CP -> ShortText+singleton' cp@(CP cpw)+  | cpw <    0x80  = create 1 $ \mba -> writeCodePoint1 mba 0 cp+  | cpw <   0x800  = create 2 $ \mba -> writeCodePoint2 mba 0 cp+  | cpw < 0x10000  = create 3 $ \mba -> writeCodePoint3 mba 0 cp+  | otherwise      = create 4 $ \mba -> writeCodePoint4 mba 0 cp++-- | \(\mathcal{O}(n)\) Prepend a character to a 'ShortText'.+--+-- prop> cons c t == singleton c <> t+--+-- @since 0.1.2+cons :: Char -> ShortText -> ShortText+cons (ch2cp -> cp@(CP cpw)) sfx+  | n == 0         = singleton' cp+  | cpw <    0x80  = create (n+1) $ \mba -> writeCodePoint1 mba 0 cp >> copySfx 1 mba+  | cpw <   0x800  = create (n+2) $ \mba -> writeCodePoint2 mba 0 cp >> copySfx 2 mba+  | cpw < 0x10000  = create (n+3) $ \mba -> writeCodePoint3 mba 0 cp >> copySfx 3 mba+  | otherwise      = create (n+4) $ \mba -> writeCodePoint4 mba 0 cp >> copySfx 4 mba+  where+    !n = toB sfx++    copySfx :: B -> MBA s -> ST s ()+    copySfx ofs mba = copyByteArray sfx 0 mba ofs n++-- | \(\mathcal{O}(n)\) Append a character to the ond of a 'ShortText'.+--+-- prop> snoc t c == t <> singleton c+--+-- @since 0.1.2+snoc :: ShortText -> Char -> ShortText+snoc pfx (ch2cp -> cp@(CP cpw))+  | n == 0         = singleton' cp+  | cpw <    0x80  = create (n+1) $ \mba -> copyPfx mba >> writeCodePoint1 mba n cp+  | cpw <   0x800  = create (n+2) $ \mba -> copyPfx mba >> writeCodePoint2 mba n cp+  | cpw < 0x10000  = create (n+3) $ \mba -> copyPfx mba >> writeCodePoint3 mba n cp+  | otherwise      = create (n+4) $ \mba -> copyPfx mba >> writeCodePoint4 mba n cp+  where+    !n = toB pfx++    copyPfx :: MBA s -> ST s ()+    copyPfx mba = copyByteArray pfx 0 mba 0 n++{-+writeCodePoint :: MBA s -> Int -> Word -> ST s ()+writeCodePoint mba ofs cp+  | cp <    0x80  = writeCodePoint1 mba ofs cp+  | cp <   0x800  = writeCodePoint2 mba ofs cp+  | cp < 0x10000  = writeCodePoint3 mba ofs cp+  | otherwise     = writeCodePoint4 mba ofs cp+-}++writeCodePointN :: B -> MBA s -> B -> CP -> ST s ()+writeCodePointN 1 = writeCodePoint1+writeCodePointN 2 = writeCodePoint2+writeCodePointN 3 = writeCodePoint3+writeCodePointN 4 = writeCodePoint4+writeCodePointN _ = undefined++writeCodePoint1 :: MBA s -> B -> CP -> ST s ()+writeCodePoint1 mba ofs (CP cp) =+  writeWord8Array mba ofs cp++writeCodePoint2 :: MBA s -> B -> CP -> ST s ()+writeCodePoint2 mba ofs (CP cp) = do+  writeWord8Array mba  ofs    (0xc0 .|. (cp `unsafeShiftR` 6))+  writeWord8Array mba (ofs+1) (0x80 .|. (cp                     .&. 0x3f))++writeCodePoint3 :: MBA s -> B -> CP -> ST s ()+writeCodePoint3 mba ofs (CP cp) = do+  writeWord8Array mba  ofs    (0xe0 .|.  (cp `unsafeShiftR` 12))+  writeWord8Array mba (ofs+1) (0x80 .|. ((cp `unsafeShiftR` 6)  .&. 0x3f))+  writeWord8Array mba (ofs+2) (0x80 .|.  (cp                    .&. 0x3f))++writeCodePoint4 :: MBA s -> B -> CP -> ST s ()+writeCodePoint4 mba ofs (CP cp) = do+  writeWord8Array mba  ofs    (0xf0 .|.  (cp `unsafeShiftR` 18))+  writeWord8Array mba (ofs+1) (0x80 .|. ((cp `unsafeShiftR` 12) .&. 0x3f))+  writeWord8Array mba (ofs+2) (0x80 .|. ((cp `unsafeShiftR` 6)  .&. 0x3f))+  writeWord8Array mba (ofs+3) (0x80 .|. (cp                     .&. 0x3f))++-- beware: UNSAFE!+readCodePoint :: ShortText -> B -> CP+readCodePoint st (csizeFromB -> ofs)+  = CP $ unsafeDupablePerformIO (c_text_short_ofs_cp (toByteArray# st) ofs)++foreign import ccall unsafe "hs_text_short_ofs_cp" c_text_short_ofs_cp :: ByteArray# -> CSize -> IO CCodePoint++readCodePointRev :: ShortText -> B -> CP+readCodePointRev st (csizeFromB -> ofs)+  = CP $ unsafeDupablePerformIO (c_text_short_ofs_cp_rev (toByteArray# st) ofs)++foreign import ccall unsafe "hs_text_short_ofs_cp_rev" c_text_short_ofs_cp_rev :: ByteArray# -> CSize -> IO CCodePoint++----------------------------------------------------------------------------+-- string & list literals++-- | __Note__: Surrogate pairs (@[U+D800 .. U+DFFF]@) character literals are replaced by U+FFFD.+--+-- @since 0.1.2+instance GHC.Exts.IsList ShortText where+    type (Item ShortText) = Char+    fromList = fromString+    toList   = toString++-- | __Note__: Surrogate pairs (@[U+D800 .. U+DFFF]@) in string literals are replaced by U+FFFD.+--+-- This matches the behaviour of 'IsString' instance for 'T.Text'.+instance S.IsString ShortText where+    fromString = fromStringLit++-- i.e., don't inline before Phase 0+{-# INLINE [0] fromStringLit #-}+fromStringLit :: String -> ShortText+fromStringLit = fromString++{-# RULES "ShortText empty literal" fromStringLit "" = mempty #-}++-- TODO: this doesn't seem to fire+{-# RULES "ShortText singleton literal" forall c . fromStringLit [c] = singleton c #-}++{-# RULES "ShortText literal ASCII" forall s . fromStringLit (GHC.unpackCString# s) = fromLitAsciiAddr# s #-}++{-# RULES "ShortText literal UTF-8" forall s . fromStringLit (GHC.unpackCStringUtf8# s) = fromLitMUtf8Addr# s #-}++{-# NOINLINE fromLitAsciiAddr# #-}+fromLitAsciiAddr# :: Addr# -> ShortText+fromLitAsciiAddr# (Ptr -> ptr) = unsafeDupablePerformIO $ do+  sz <- csizeToB `fmap` c_strlen ptr++  case sz `compare` 0 of+    EQ -> return mempty -- should not happen if rules fire correctly+    GT -> stToIO $ do+      mba <- newByteArray sz+      copyAddrToByteArray ptr mba 0 sz+      unsafeFreeze mba+    LT -> return (error "fromLitAsciiAddr#")+          -- NOTE: should never happen unless strlen(3) overflows (NB: CSize+          -- is unsigned; the overflow would occur when converting to+          -- 'B')++foreign import ccall unsafe "strlen" c_strlen :: CString -> IO CSize++-- GHC uses an encoding resembling Modified UTF-8 for non-ASCII string-literals+{-# NOINLINE fromLitMUtf8Addr# #-}+fromLitMUtf8Addr# :: Addr# -> ShortText+fromLitMUtf8Addr# (Ptr -> ptr) = unsafeDupablePerformIO $ do+  sz <- B `fmap` c_text_short_mutf8_strlen ptr++  case sz `compare` 0 of+    EQ -> return mempty -- should not happen if rules fire correctly+    GT -> stToIO $ do+      mba <- newByteArray sz+      copyAddrToByteArray ptr mba 0 sz+      unsafeFreeze mba+    LT -> do+      mba <- stToIO (newByteArray (abs sz))+      c_text_short_mutf8_trans ptr (unMBA# mba)+      stToIO (unsafeFreeze mba)++foreign import ccall unsafe "hs_text_short_mutf8_strlen" c_text_short_mutf8_strlen :: CString -> IO Int++foreign import ccall unsafe "hs_text_short_mutf8_trans" c_text_short_mutf8_trans :: CString -> MutableByteArray# RealWorld -> IO ()++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Data.Text.Short (pack, unpack, concat)+-- >>> import Text.Show.Functions ()+-- >>> import qualified Test.QuickCheck.Arbitrary as QC+-- >>> import Test.QuickCheck.Instances ()+-- >>> instance QC.Arbitrary ShortText where { arbitrary = fmap fromString QC.arbitrary }
+ src/Data/Text/Short/Partial.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE Trustworthy #-}++-- |+-- Module      : Data.Text.Short.Partial+-- Copyright   : © Herbert Valerio Riedel 2018+-- License     : BSD3+--+-- Maintainer  : hvr@gnu.org+-- Stability   : stable+--+-- Partial functions vocabulary+--+-- This module provides common partial functions for operating on 'ShortText'.+--+-- The use of these functions is discouraged as they tend to be error-prone.+--+-- @since 0.1.2+module Data.Text.Short.Partial+    ( head+    , tail+    , init+    , last+    , index++    , foldl1+    , foldl1'+    , foldr1+    ) where++import           Data.Text.Short+import           Data.Text.Short.Internal+import           Prelude                  ()++-- | \(\mathcal{O}(1)\) Returns first character of a non-empty 'ShortText'+--+-- >>> head "abcd"+-- 'a'+--+-- __Note__: Will throw an 'error' exception for empty 'ShortText's.+-- Consider using the total functions 'uncons' or 'indexMaybe'+-- instead.+--+-- @since 0.1.2+head :: ShortText -> Char+head = maybe (error "head: empty ShortText") fst . uncons++-- | \(\mathcal{O}(n)\) Drop first character from non-empty 'ShortText'.+--+-- >>> tail "abcd"+-- "bcd"+--+-- __Note__: Will throw an 'error' exception for empty 'ShortText's.+-- Consider using the total functions 'uncons' or 'drop' instead.+--+-- @since 0.1.2+tail :: ShortText -> ShortText+tail = maybe (error "tail: empty ShortText") snd . uncons++-- | \(\mathcal{O}(n)\) Drop last character from non-empty 'ShortText'.+--+-- >>> tail "abcd"+-- "bcd"+--+-- __Note__: Will throw an 'error' exception for empty 'ShortText's.+-- Consider using the total functions 'unsnoc' or 'dropEnd' instead.+--+-- @since 0.1.2+init :: ShortText -> ShortText+init = maybe (error "init: empty ShortText") fst . unsnoc++-- | \(\mathcal{O}(1)\) Return last character from non-empty 'ShortText'.+--+-- >>> last "abcd"+-- 'd'+--+-- __Note__: Will throw an 'error' exception for empty 'ShortText's.+-- Consider using the total functions 'unsnoc' or 'indexEndMaybe'+-- instead.+--+-- @since 0.1.2+last :: ShortText -> Char+last = maybe (error "last: empty ShortText") snd . unsnoc++-- | \(\mathcal{O}(n)\) Retrieve \(i\)-th character (code-point)+--+-- >>> index "abcd" 1+-- 'b'+--+-- __Note__: Will throw an 'error' exception if index is out of+-- bounds.  Consider using the total functions 'indexMaybe' or+-- 'indexEndMaybe' instead.+--+-- @since 0.1.2+index :: ShortText -> Int -> Char+index st i = case indexMaybe st i of+               Nothing -> error "index: not within ShortText"+               Just c  -> c++-- $setup+-- >>> :set -XOverloadedStrings
text-short.cabal view
@@ -1,7 +1,7 @@-cabal-version:       1.12+cabal-version:       1.18  name:                text-short-version:             0.1.1+version:             0.1.2 synopsis:            Memory-efficient representation of Unicode text strings license:             BSD3 license-file:        LICENSE@@ -12,32 +12,46 @@ build-type:          Simple description:         This package provides the 'ShortText' type which is suitable for keeping many short strings in memory. This is similiar to how 'ShortByteString' relates to 'ByteString'.                      .-                     The main difference between 'Text' and 'ShortText' is that 'ShortText' uses UTF-8 instead of UTF-16 internally and also doesn't support slicing (thereby saving 2 words). Consequently, the memory footprint of a (boxed) 'ShortText' value is 4 words (2 words when unboxed) plus the length of the UTF-8 encoded payload.+                     The main difference between 'Text' and 'ShortText' is that 'ShortText' uses UTF-8 instead of UTF-16 internally and also doesn't support zero-copy slicing (thereby saving 2 words). Consequently, the memory footprint of a (boxed) 'ShortText' value is 4 words (2 words when unboxed) plus the length of the UTF-8 encoded payload. +tested-with:         GHC==8.4.1, GHC==8.2.2, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4 extra-source-files:  ChangeLog.md  Source-Repository head     Type:              git     Location:          https://github.com/hvr/text-short.git -library-  default-language:    Haskell2010+flag asserts+  description: Enable runtime-checks via @assert@+  default: False+  manual: True +library   exposed-modules:     Data.Text.Short+                       Data.Text.Short.Partial                        Data.Text.Short.Unsafe    other-modules:       Data.Text.Short.Internal -  build-depends:       base        >= 4.7    && < 4.11+  build-depends:       base        >= 4.7    && < 4.12                      , bytestring  >= 0.10.4 && < 0.11                      , hashable    >= 1.2.6  && < 1.3                      , deepseq     >= 1.3    && < 1.5                      , text        >= 1.0    && < 1.3                      , binary      >= 0.7.1  && < 0.9+                     , ghc-prim    >= 0.3.1  && < 0.6    if !impl(ghc >= 8.0)      build-depends: semigroups >= 0.18.2 && < 0.19 +  -- GHC version specific PrimOps+  if impl(ghc >= 8.4)+     hs-source-dirs:   src-ghc804+  else+     c-sources:        cbits/memcmp.c+     hs-source-dirs:   src-ghc708+  other-modules:       PrimOps+   hs-source-dirs:      src    default-language:    Haskell2010@@ -49,10 +63,16 @@                      , Unsafe    c-sources: cbits/cbits.c++  if flag(asserts)+     ghc-options: -fno-ignore-asserts+  else+     cc-options: -DNDEBUG=1+   ghc-options: -Wall+  cc-options: -O3 -Wall  test-suite tests-  default-language:    Haskell2010   type:                exitcode-stdio-1.0   hs-source-dirs:      src-test   main-is:             Tests.hs@@ -62,7 +82,9 @@                , text                , text-short                  -- deps which don't inherit constraints from library stanza:-               , tasty                >= 0.11.2 && < 0.12-               , tasty-quickcheck     >= 0.8.4  && < 0.9-               , tasty-hunit          >= 0.9.2  && < 0.10+               , tasty                >= 1.0.0  && < 1.1+               , tasty-quickcheck     >= 0.10   && < 0.11+               , tasty-hunit          >= 0.10.0 && < 0.11                , quickcheck-instances >= 0.3.14 && < 0.4++  default-language:    Haskell2010