// The latest version of this library is available on GitHub;
// https://github.com/sheredom/utf8.h

// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>

#ifndef SHEREDOM_UTF8_H_INCLUDED
#define SHEREDOM_UTF8_H_INCLUDED

#if defined(_MSC_VER)
#pragma warning(push)

/* disable warning: no function prototype given: converting '()' to '(void)' */
#pragma warning(disable : 4255)

/* disable warning: '__cplusplus' is not defined as a preprocessor macro,
 * replacing with '0' for '#if/#elif' */
#pragma warning(disable : 4668)

/* disable warning: bytes padding added after construct */
#pragma warning(disable : 4820)
#endif

#include <stddef.h>
#include <stdlib.h>

#if defined(_MSC_VER)
#pragma warning(pop)
#endif

#if defined(_MSC_VER) && (_MSC_VER < 1920)
typedef __int32 utf8_int32_t;
#else
#include <stdint.h>
typedef int32_t utf8_int32_t;
#endif

#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
#pragma clang diagnostic ignored "-Wcast-qual"
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if defined(_MSC_VER)
#define utf8_nonnull
#define utf8_pure
#define utf8_restrict __restrict
#define utf8_weak __inline
#elif defined(__clang__) || defined(__GNUC__)
#define utf8_nonnull __attribute__((nonnull))
#define utf8_pure __attribute__((pure))
#define utf8_restrict __restrict__
#define utf8_weak __attribute__((weak))
#else
#error Non clang, non gcc, non MSVC compiler found!
#endif

#ifdef __cplusplus
#define utf8_null NULL
#else
#define utf8_null 0
#endif

#if (defined(__cplusplus) && __cplusplus >= 201402L)
#define utf8_constexpr14 constexpr
#define utf8_constexpr14_impl constexpr
#else
// constexpr and weak are incompatible. so only enable one of them
#define utf8_constexpr14 utf8_weak
#define utf8_constexpr14_impl
#endif

#if defined(__cplusplus) && __cplusplus >= 202002L
using utf8_int8_t = char8_t; // Introduced in C++20
#else
typedef char utf8_int8_t;
#endif

// Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 >
// src2 respectively, case insensitive.
utf8_constexpr14 utf8_nonnull utf8_pure int
utf8casecmp(const utf8_int8_t *src1, const utf8_int8_t *src2);

// Append the utf8 string src onto the utf8 string dst.
utf8_nonnull utf8_weak utf8_int8_t *
utf8cat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src);

// Find the first match of the utf8 codepoint chr in the utf8 string src.
utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t *
utf8chr(const utf8_int8_t *src, utf8_int32_t chr);

// Return less than 0, 0, greater than 0 if src1 < src2,
// src1 == src2, src1 > src2 respectively.
utf8_constexpr14 utf8_nonnull utf8_pure int utf8cmp(const utf8_int8_t *src1,
                                                    const utf8_int8_t *src2);

// Copy the utf8 string src onto the memory allocated in dst.
utf8_nonnull utf8_weak utf8_int8_t *
utf8cpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src);

// Number of utf8 codepoints in the utf8 string src that consists entirely
// of utf8 codepoints not from the utf8 string reject.
utf8_constexpr14 utf8_nonnull utf8_pure size_t
utf8cspn(const utf8_int8_t *src, const utf8_int8_t *reject);

// Duplicate the utf8 string src by getting its size, malloc'ing a new buffer
// copying over the data, and returning that. Or 0 if malloc failed.
utf8_weak utf8_int8_t *utf8dup(const utf8_int8_t *src);

// Number of utf8 codepoints in the utf8 string str,
// excluding the null terminating byte.
utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8len(const utf8_int8_t *str);

// Similar to utf8len, except that only at most n bytes of src are looked.
utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8nlen(const utf8_int8_t *str,
                                                        size_t n);

// Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 >
// src2 respectively, case insensitive. Checking at most n bytes of each utf8
// string.
utf8_constexpr14 utf8_nonnull utf8_pure int
utf8ncasecmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n);

// Append the utf8 string src onto the utf8 string dst,
// writing at most n+1 bytes. Can produce an invalid utf8
// string if n falls partway through a utf8 codepoint.
utf8_nonnull utf8_weak utf8_int8_t *
utf8ncat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src,
         size_t n);

// Return less than 0, 0, greater than 0 if src1 < src2,
// src1 == src2, src1 > src2 respectively. Checking at most n
// bytes of each utf8 string.
utf8_constexpr14 utf8_nonnull utf8_pure int
utf8ncmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n);

// Copy the utf8 string src onto the memory allocated in dst.
// Copies at most n bytes. If n falls partway through a utf8
// codepoint, or if dst doesn't have enough room for a null
// terminator, the final string will be cut short to preserve
// utf8 validity.

utf8_nonnull utf8_weak utf8_int8_t *
utf8ncpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src,
         size_t n);

// Similar to utf8dup, except that at most n bytes of src are copied. If src is
// longer than n, only n bytes are copied and a null byte is added.
//
// Returns a new string if successful, 0 otherwise
utf8_weak utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n);

// Locates the first occurrence in the utf8 string str of any byte in the
// utf8 string accept, or 0 if no match was found.
utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t *
utf8pbrk(const utf8_int8_t *str, const utf8_int8_t *accept);

// Find the last match of the utf8 codepoint chr in the utf8 string src.
utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t *
utf8rchr(const utf8_int8_t *src, int chr);

// Number of bytes in the utf8 string str,
// including the null terminating byte.
utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8size(const utf8_int8_t *str);

// Similar to utf8size, except that the null terminating byte is excluded.
utf8_constexpr14 utf8_nonnull utf8_pure size_t
utf8size_lazy(const utf8_int8_t *str);

// Similar to utf8size, except that only at most n bytes of src are looked and
// the null terminating byte is excluded.
utf8_constexpr14 utf8_nonnull utf8_pure size_t
utf8nsize_lazy(const utf8_int8_t *str, size_t n);

// Number of utf8 codepoints in the utf8 string src that consists entirely
// of utf8 codepoints from the utf8 string accept.
utf8_constexpr14 utf8_nonnull utf8_pure size_t
utf8spn(const utf8_int8_t *src, const utf8_int8_t *accept);

// The position of the utf8 string needle in the utf8 string haystack.
utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t *
utf8str(const utf8_int8_t *haystack, const utf8_int8_t *needle);

// The position of the utf8 string needle in the utf8 string haystack, case
// insensitive.
utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t *
utf8casestr(const utf8_int8_t *haystack, const utf8_int8_t *needle);

// Return 0 on success, or the position of the invalid
// utf8 codepoint on failure.
utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t *
utf8valid(const utf8_int8_t *str);

// Similar to utf8valid, except that only at most n bytes of src are looked.
utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t *
utf8nvalid(const utf8_int8_t *str, size_t n);

// Given a null-terminated string, makes the string valid by replacing invalid
// codepoints with a 1-byte replacement. Returns 0 on success.
utf8_nonnull utf8_weak int utf8makevalid(utf8_int8_t *str,
                                         const utf8_int32_t replacement);

// Sets out_codepoint to the current utf8 codepoint in str, and returns the
// address of the next utf8 codepoint after the current one in str.
utf8_constexpr14 utf8_nonnull utf8_int8_t *
utf8codepoint(const utf8_int8_t *utf8_restrict str,
              utf8_int32_t *utf8_restrict out_codepoint);

// Calculates the size of the next utf8 codepoint in str.
utf8_constexpr14 utf8_nonnull size_t
utf8codepointcalcsize(const utf8_int8_t *str);

// Returns the size of the given codepoint in bytes.
utf8_constexpr14 size_t utf8codepointsize(utf8_int32_t chr);

// Write a codepoint to the given string, and return the address to the next
// place after the written codepoint. Pass how many bytes left in the buffer to
// n. If there is not enough space for the codepoint, this function returns
// null.
utf8_nonnull utf8_weak utf8_int8_t *
utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n);

// Returns 1 if the given character is lowercase, or 0 if it is not.
utf8_constexpr14 int utf8islower(utf8_int32_t chr);

// Returns 1 if the given character is uppercase, or 0 if it is not.
utf8_constexpr14 int utf8isupper(utf8_int32_t chr);

// Transform the given string into all lowercase codepoints.
utf8_nonnull utf8_weak void utf8lwr(utf8_int8_t *utf8_restrict str);

// Transform the given string into all uppercase codepoints.
utf8_nonnull utf8_weak void utf8upr(utf8_int8_t *utf8_restrict str);

// Make a codepoint lower case if possible.
utf8_constexpr14 utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp);

// Make a codepoint upper case if possible.
utf8_constexpr14 utf8_int32_t utf8uprcodepoint(utf8_int32_t cp);

// Sets out_codepoint to the current utf8 codepoint in str, and returns the
// address of the previous utf8 codepoint before the current one in str.
utf8_constexpr14 utf8_nonnull utf8_int8_t *
utf8rcodepoint(const utf8_int8_t *utf8_restrict str,
               utf8_int32_t *utf8_restrict out_codepoint);

// Duplicate the utf8 string src by getting its size, calling alloc_func_ptr to
// copy over data to a new buffer, and returning that. Or 0 if alloc_func_ptr
// returned null.
utf8_weak utf8_int8_t *utf8dup_ex(const utf8_int8_t *src,
                                  utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *,
                                                                 size_t),
                                  utf8_int8_t *user_data);

// Similar to utf8dup, except that at most n bytes of src are copied. If src is
// longer than n, only n bytes are copied and a null byte is added.
//
// Returns a new string if successful, 0 otherwise.
utf8_weak utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n,
                                   utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *,
                                                                  size_t),
                                   utf8_int8_t *user_data);

#undef utf8_weak
#undef utf8_pure
#undef utf8_nonnull

utf8_constexpr14_impl int utf8casecmp(const utf8_int8_t *src1,
                                      const utf8_int8_t *src2) {
  utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0,
               src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0;

  for (;;) {
    src1 = utf8codepoint(src1, &src1_orig_cp);
    src2 = utf8codepoint(src2, &src2_orig_cp);

    // lower the srcs if required
    src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp);
    src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp);

    // lower the srcs if required
    src1_upr_cp = utf8uprcodepoint(src1_orig_cp);
    src2_upr_cp = utf8uprcodepoint(src2_orig_cp);

    // check if the lowered codepoints match
    if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) {
      return 0;
    } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) {
      continue;
    }

    // if they don't match, then we return the difference between the characters
    return src1_lwr_cp - src2_lwr_cp;
  }
}

utf8_int8_t *utf8cat(utf8_int8_t *utf8_restrict dst,
                     const utf8_int8_t *utf8_restrict src) {
  utf8_int8_t *d = dst;
  // find the null terminating byte in dst
  while ('\0' != *d) {
    d++;
  }

  // overwriting the null terminating byte in dst, append src byte-by-byte
  while ('\0' != *src) {
    *d++ = *src++;
  }

  // write out a new null terminating byte into dst
  *d = '\0';

  return dst;
}

utf8_constexpr14_impl utf8_int8_t *utf8chr(const utf8_int8_t *src,
                                           utf8_int32_t chr) {
  utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'};

  if (0 == chr) {
    // being asked to return position of null terminating byte, so
    // just run s to the end, and return!
    while ('\0' != *src) {
      src++;
    }
    return (utf8_int8_t *)src;
  } else if (0 == ((utf8_int32_t)0xffffff80 & chr)) {
    // 1-byte/7-bit ascii
    // (0b0xxxxxxx)
    c[0] = (utf8_int8_t)chr;
  } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) {
    // 2-byte/11-bit utf8 code point
    // (0b110xxxxx 0b10xxxxxx)
    c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6));
    c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
  } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) {
    // 3-byte/16-bit utf8 code point
    // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx)
    c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12));
    c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f));
    c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
  } else { // if (0 == ((int)0xffe00000 & chr)) {
    // 4-byte/21-bit utf8 code point
    // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx)
    c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18));
    c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f));
    c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f));
    c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
  }

  // we've made c into a 2 utf8 codepoint string, one for the chr we are
  // seeking, another for the null terminating byte. Now use utf8str to
  // search
  return utf8str(src, c);
}

utf8_constexpr14_impl int utf8cmp(const utf8_int8_t *src1,
                                  const utf8_int8_t *src2) {
  while (('\0' != *src1) || ('\0' != *src2)) {
    if (*src1 < *src2) {
      return -1;
    } else if (*src1 > *src2) {
      return 1;
    }

    src1++;
    src2++;
  }

  // both utf8 strings matched
  return 0;
}

utf8_constexpr14_impl int utf8coll(const utf8_int8_t *src1,
                                   const utf8_int8_t *src2);

utf8_int8_t *utf8cpy(utf8_int8_t *utf8_restrict dst,
                     const utf8_int8_t *utf8_restrict src) {
  utf8_int8_t *d = dst;

  // overwriting anything previously in dst, write byte-by-byte
  // from src
  while ('\0' != *src) {
    *d++ = *src++;
  }

  // append null terminating byte
  *d = '\0';

  return dst;
}

utf8_constexpr14_impl size_t utf8cspn(const utf8_int8_t *src,
                                      const utf8_int8_t *reject) {
  size_t chars = 0;

  while ('\0' != *src) {
    const utf8_int8_t *r = reject;
    size_t offset = 0;

    while ('\0' != *r) {
      // checking that if *r is the start of a utf8 codepoint
      // (it is not 0b10xxxxxx) and we have successfully matched
      // a previous character (0 < offset) - we found a match
      if ((0x80 != (0xc0 & *r)) && (0 < offset)) {
        return chars;
      } else {
        if (*r == src[offset]) {
          // part of a utf8 codepoint matched, so move our checking
          // onwards to the next byte
          offset++;
          r++;
        } else {
          // r could be in the middle of an unmatching utf8 code point,
          // so we need to march it on to the next character beginning,

          do {
            r++;
          } while (0x80 == (0xc0 & *r));

          // reset offset too as we found a mismatch
          offset = 0;
        }
      }
    }

    // found a match at the end of *r, so didn't get a chance to test it
    if (0 < offset) {
      return chars;
    }

    // the current utf8 codepoint in src did not match reject, but src
    // could have been partway through a utf8 codepoint, so we need to
    // march it onto the next utf8 codepoint starting byte
    do {
      src++;
    } while ((0x80 == (0xc0 & *src)));
    chars++;
  }

  return chars;
}

utf8_int8_t *utf8dup(const utf8_int8_t *src) {
  return utf8dup_ex(src, utf8_null, utf8_null);
}

utf8_int8_t *utf8dup_ex(const utf8_int8_t *src,
                        utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t),
                        utf8_int8_t *user_data) {
  utf8_int8_t *n = utf8_null;

  // figure out how many bytes (including the terminator) we need to copy first
  size_t bytes = utf8size(src);

  if (alloc_func_ptr) {
    n = alloc_func_ptr(user_data, bytes);
  } else {
    n = (utf8_int8_t *)malloc(bytes);
  }

  if (utf8_null == n) {
    // out of memory so we bail
    return utf8_null;
  } else {
    bytes = 0;

    // copy src byte-by-byte into our new utf8 string
    while ('\0' != src[bytes]) {
      n[bytes] = src[bytes];
      bytes++;
    }

    // append null terminating byte
    n[bytes] = '\0';
    return n;
  }
}

utf8_constexpr14_impl utf8_int8_t *utf8fry(const utf8_int8_t *str);

utf8_constexpr14_impl size_t utf8len(const utf8_int8_t *str) {
  return utf8nlen(str, SIZE_MAX);
}

utf8_constexpr14_impl size_t utf8nlen(const utf8_int8_t *str, size_t n) {
  const utf8_int8_t *t = str;
  size_t length = 0;

  while ((size_t)(str - t) < n && '\0' != *str) {
    if (0xf0 == (0xf8 & *str)) {
      // 4-byte utf8 code point (began with 0b11110xxx)
      str += 4;
    } else if (0xe0 == (0xf0 & *str)) {
      // 3-byte utf8 code point (began with 0b1110xxxx)
      str += 3;
    } else if (0xc0 == (0xe0 & *str)) {
      // 2-byte utf8 code point (began with 0b110xxxxx)
      str += 2;
    } else { // if (0x00 == (0x80 & *s)) {
      // 1-byte ascii (began with 0b0xxxxxxx)
      str += 1;
    }

    // no matter the bytes we marched s forward by, it was
    // only 1 utf8 codepoint
    length++;
  }

  if ((size_t)(str - t) > n) {
    length--;
  }
  return length;
}

utf8_constexpr14_impl int utf8ncasecmp(const utf8_int8_t *src1,
                                       const utf8_int8_t *src2, size_t n) {
  utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0,
               src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0;

  do {
    const utf8_int8_t *const s1 = src1;
    const utf8_int8_t *const s2 = src2;

    // first check that we have enough bytes left in n to contain an entire
    // codepoint
    if (0 == n) {
      return 0;
    }

    if ((1 == n) && ((0xc0 == (0xe0 & *s1)) || (0xc0 == (0xe0 & *s2)))) {
      const utf8_int32_t c1 = (0xe0 & *s1);
      const utf8_int32_t c2 = (0xe0 & *s2);

      if (c1 < c2) {
        return c1 - c2;
      } else {
        return 0;
      }
    }

    if ((2 >= n) && ((0xe0 == (0xf0 & *s1)) || (0xe0 == (0xf0 & *s2)))) {
      const utf8_int32_t c1 = (0xf0 & *s1);
      const utf8_int32_t c2 = (0xf0 & *s2);

      if (c1 < c2) {
        return c1 - c2;
      } else {
        return 0;
      }
    }

    if ((3 >= n) && ((0xf0 == (0xf8 & *s1)) || (0xf0 == (0xf8 & *s2)))) {
      const utf8_int32_t c1 = (0xf8 & *s1);
      const utf8_int32_t c2 = (0xf8 & *s2);

      if (c1 < c2) {
        return c1 - c2;
      } else {
        return 0;
      }
    }

    src1 = utf8codepoint(src1, &src1_orig_cp);
    src2 = utf8codepoint(src2, &src2_orig_cp);
    n -= utf8codepointsize(src1_orig_cp);

    src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp);
    src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp);

    src1_upr_cp = utf8uprcodepoint(src1_orig_cp);
    src2_upr_cp = utf8uprcodepoint(src2_orig_cp);

    // check if the lowered codepoints match
    if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) {
      return 0;
    } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) {
      continue;
    }

    // if they don't match, then we return the difference between the characters
    return src1_lwr_cp - src2_lwr_cp;
  } while (0 < n);

  // both utf8 strings matched
  return 0;
}

utf8_int8_t *utf8ncat(utf8_int8_t *utf8_restrict dst,
                      const utf8_int8_t *utf8_restrict src, size_t n) {
  utf8_int8_t *d = dst;

  // find the null terminating byte in dst
  while ('\0' != *d) {
    d++;
  }

  // overwriting the null terminating byte in dst, append src byte-by-byte
  // stopping if we run out of space
  do {
    *d++ = *src++;
  } while (('\0' != *src) && (0 != --n));

  // write out a new null terminating byte into dst
  *d = '\0';

  return dst;
}

utf8_constexpr14_impl int utf8ncmp(const utf8_int8_t *src1,
                                   const utf8_int8_t *src2, size_t n) {
  while ((0 != n--) && (('\0' != *src1) || ('\0' != *src2))) {
    if (*src1 < *src2) {
      return -1;
    } else if (*src1 > *src2) {
      return 1;
    }

    src1++;
    src2++;
  }

  // both utf8 strings matched
  return 0;
}

utf8_int8_t *utf8ncpy(utf8_int8_t *utf8_restrict dst,
                      const utf8_int8_t *utf8_restrict src, size_t n) {
  utf8_int8_t *d = dst;
  size_t index = 0, check_index = 0;

  if (n == 0) {
    return dst;
  }

  // overwriting anything previously in dst, write byte-by-byte
  // from src
  for (index = 0; index < n; index++) {
    d[index] = src[index];
    if ('\0' == src[index]) {
      break;
    }
  }

  for (check_index = index - 1;
       check_index > 0 && 0x80 == (0xc0 & d[check_index]); check_index--) {
    // just moving the index
  }

  if (check_index < index &&
      (index - check_index) < utf8codepointsize(d[check_index])) {
    index = check_index;
  }

  // append null terminating byte
  for (; index < n; index++) {
    d[index] = 0;
  }

  return dst;
}

utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n) {
  return utf8ndup_ex(src, n, utf8_null, utf8_null);
}

utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n,
                         utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t),
                         utf8_int8_t *user_data) {
  utf8_int8_t *c = utf8_null;
  size_t bytes = 0;

  // Find the end of the string or stop when n is reached
  while ('\0' != src[bytes] && bytes < n) {
    bytes++;
  }

  // In case bytes is actually less than n, we need to set it
  // to be used later in the copy byte by byte.
  n = bytes;

  if (alloc_func_ptr) {
    c = alloc_func_ptr(user_data, bytes + 1);
  } else {
    c = (utf8_int8_t *)malloc(bytes + 1);
  }

  if (utf8_null == c) {
    // out of memory so we bail
    return utf8_null;
  }

  bytes = 0;

  // copy src byte-by-byte into our new utf8 string
  while ('\0' != src[bytes] && bytes < n) {
    c[bytes] = src[bytes];
    bytes++;
  }

  // append null terminating byte
  c[bytes] = '\0';
  return c;
}

utf8_constexpr14_impl utf8_int8_t *utf8rchr(const utf8_int8_t *src, int chr) {

  utf8_int8_t *match = utf8_null;
  utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'};

  if (0 == chr) {
    // being asked to return position of null terminating byte, so
    // just run s to the end, and return!
    while ('\0' != *src) {
      src++;
    }
    return (utf8_int8_t *)src;
  } else if (0 == ((int)0xffffff80 & chr)) {
    // 1-byte/7-bit ascii
    // (0b0xxxxxxx)
    c[0] = (utf8_int8_t)chr;
  } else if (0 == ((int)0xfffff800 & chr)) {
    // 2-byte/11-bit utf8 code point
    // (0b110xxxxx 0b10xxxxxx)
    c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6));
    c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
  } else if (0 == ((int)0xffff0000 & chr)) {
    // 3-byte/16-bit utf8 code point
    // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx)
    c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12));
    c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f));
    c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
  } else { // if (0 == ((int)0xffe00000 & chr)) {
    // 4-byte/21-bit utf8 code point
    // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx)
    c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18));
    c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f));
    c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f));
    c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
  }

  // we've created a 2 utf8 codepoint string in c that is
  // the utf8 character asked for by chr, and a null
  // terminating byte

  while ('\0' != *src) {
    size_t offset = 0;

    while (src[offset] == c[offset]) {
      offset++;
    }

    if ('\0' == c[offset]) {
      // we found a matching utf8 code point
      match = (utf8_int8_t *)src;
      src += offset;
    } else {
      src += offset;

      // need to march s along to next utf8 codepoint start
      // (the next byte that doesn't match 0b10xxxxxx)
      if ('\0' != *src) {
        do {
          src++;
        } while (0x80 == (0xc0 & *src));
      }
    }
  }

  // return the last match we found (or 0 if no match was found)
  return match;
}

utf8_constexpr14_impl utf8_int8_t *utf8pbrk(const utf8_int8_t *str,
                                            const utf8_int8_t *accept) {
  while ('\0' != *str) {
    const utf8_int8_t *a = accept;
    size_t offset = 0;

    while ('\0' != *a) {
      // checking that if *a is the start of a utf8 codepoint
      // (it is not 0b10xxxxxx) and we have successfully matched
      // a previous character (0 < offset) - we found a match
      if ((0x80 != (0xc0 & *a)) && (0 < offset)) {
        return (utf8_int8_t *)str;
      } else {
        if (*a == str[offset]) {
          // part of a utf8 codepoint matched, so move our checking
          // onwards to the next byte
          offset++;
          a++;
        } else {
          // r could be in the middle of an unmatching utf8 code point,
          // so we need to march it on to the next character beginning,

          do {
            a++;
          } while (0x80 == (0xc0 & *a));

          // reset offset too as we found a mismatch
          offset = 0;
        }
      }
    }

    // we found a match on the last utf8 codepoint
    if (0 < offset) {
      return (utf8_int8_t *)str;
    }

    // the current utf8 codepoint in src did not match accept, but src
    // could have been partway through a utf8 codepoint, so we need to
    // march it onto the next utf8 codepoint starting byte
    do {
      str++;
    } while ((0x80 == (0xc0 & *str)));
  }

  return utf8_null;
}

utf8_constexpr14_impl size_t utf8size(const utf8_int8_t *str) {
  return utf8size_lazy(str) + 1;
}

utf8_constexpr14_impl size_t utf8size_lazy(const utf8_int8_t *str) {
  return utf8nsize_lazy(str, SIZE_MAX);
}

utf8_constexpr14_impl size_t utf8nsize_lazy(const utf8_int8_t *str, size_t n) {
  size_t size = 0;
  while (size < n && '\0' != str[size]) {
    size++;
  }
  return size;
}

utf8_constexpr14_impl size_t utf8spn(const utf8_int8_t *src,
                                     const utf8_int8_t *accept) {
  size_t chars = 0;

  while ('\0' != *src) {
    const utf8_int8_t *a = accept;
    size_t offset = 0;

    while ('\0' != *a) {
      // checking that if *r is the start of a utf8 codepoint
      // (it is not 0b10xxxxxx) and we have successfully matched
      // a previous character (0 < offset) - we found a match
      if ((0x80 != (0xc0 & *a)) && (0 < offset)) {
        // found a match, so increment the number of utf8 codepoints
        // that have matched and stop checking whether any other utf8
        // codepoints in a match
        chars++;
        src += offset;
        offset = 0;
        break;
      } else {
        if (*a == src[offset]) {
          offset++;
          a++;
        } else {
          // a could be in the middle of an unmatching utf8 codepoint,
          // so we need to march it on to the next character beginning,
          do {
            a++;
          } while (0x80 == (0xc0 & *a));

          // reset offset too as we found a mismatch
          offset = 0;
        }
      }
    }

    // found a match at the end of *a, so didn't get a chance to test it
    if (0 < offset) {
      chars++;
      src += offset;
      continue;
    }

    // if a got to its terminating null byte, then we didn't find a match.
    // Return the current number of matched utf8 codepoints
    if ('\0' == *a) {
      return chars;
    }
  }

  return chars;
}

utf8_constexpr14_impl utf8_int8_t *utf8str(const utf8_int8_t *haystack,
                                           const utf8_int8_t *needle) {
  utf8_int32_t throwaway_codepoint = 0;

  // if needle has no utf8 codepoints before the null terminating
  // byte then return haystack
  if ('\0' == *needle) {
    return (utf8_int8_t *)haystack;
  }

  while ('\0' != *haystack) {
    const utf8_int8_t *maybeMatch = haystack;
    const utf8_int8_t *n = needle;

    while (*haystack == *n && (*haystack != '\0' && *n != '\0')) {
      n++;
      haystack++;
    }

    if ('\0' == *n) {
      // we found the whole utf8 string for needle in haystack at
      // maybeMatch, so return it
      return (utf8_int8_t *)maybeMatch;
    } else {
      // h could be in the middle of an unmatching utf8 codepoint,
      // so we need to march it on to the next character beginning
      // starting from the current character
      haystack = utf8codepoint(maybeMatch, &throwaway_codepoint);
    }
  }

  // no match
  return utf8_null;
}

utf8_constexpr14_impl utf8_int8_t *utf8casestr(const utf8_int8_t *haystack,
                                               const utf8_int8_t *needle) {
  // if needle has no utf8 codepoints before the null terminating
  // byte then return haystack
  if ('\0' == *needle) {
    return (utf8_int8_t *)haystack;
  }

  for (;;) {
    const utf8_int8_t *maybeMatch = haystack;
    const utf8_int8_t *n = needle;
    utf8_int32_t h_cp = 0, n_cp = 0;

    // Get the next code point and track it
    const utf8_int8_t *nextH = haystack = utf8codepoint(haystack, &h_cp);
    n = utf8codepoint(n, &n_cp);

    while ((0 != h_cp) && (0 != n_cp)) {
      h_cp = utf8lwrcodepoint(h_cp);
      n_cp = utf8lwrcodepoint(n_cp);

      // if we find a mismatch, bail out!
      if (h_cp != n_cp) {
        break;
      }

      haystack = utf8codepoint(haystack, &h_cp);
      n = utf8codepoint(n, &n_cp);
    }

    if (0 == n_cp) {
      // we found the whole utf8 string for needle in haystack at
      // maybeMatch, so return it
      return (utf8_int8_t *)maybeMatch;
    }

    if (0 == h_cp) {
      // no match
      return utf8_null;
    }

    // Roll back to the next code point in the haystack to test
    haystack = nextH;
  }
}

utf8_constexpr14_impl utf8_int8_t *utf8valid(const utf8_int8_t *str) {
  return utf8nvalid(str, SIZE_MAX);
}

utf8_constexpr14_impl utf8_int8_t *utf8nvalid(const utf8_int8_t *str,
                                              size_t n) {
  const utf8_int8_t *t = str;
  size_t consumed = 0, remained = 0;

  while ((void)(consumed = (size_t)(str - t)), consumed < n && '\0' != *str) {
    remained = n - consumed;

    if (0xf0 == (0xf8 & *str)) {
      // ensure that there's 4 bytes or more remained
      if (remained < 4) {
        return (utf8_int8_t *)str;
      }

      // ensure each of the 3 following bytes in this 4-byte
      // utf8 codepoint began with 0b10xxxxxx
      if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2])) ||
          (0x80 != (0xc0 & str[3]))) {
        return (utf8_int8_t *)str;
      }

      // ensure that our utf8 codepoint ended after 4 bytes
      if (0x80 == (0xc0 & str[4])) {
        return (utf8_int8_t *)str;
      }

      // ensure that the top 5 bits of this 4-byte utf8
      // codepoint were not 0, as then we could have used
      // one of the smaller encodings
      if ((0 == (0x07 & str[0])) && (0 == (0x30 & str[1]))) {
        return (utf8_int8_t *)str;
      }

      // 4-byte utf8 code point (began with 0b11110xxx)
      str += 4;
    } else if (0xe0 == (0xf0 & *str)) {
      // ensure that there's 3 bytes or more remained
      if (remained < 3) {
        return (utf8_int8_t *)str;
      }

      // ensure each of the 2 following bytes in this 3-byte
      // utf8 codepoint began with 0b10xxxxxx
      if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2]))) {
        return (utf8_int8_t *)str;
      }

      // ensure that our utf8 codepoint ended after 3 bytes
      if (0x80 == (0xc0 & str[3])) {
        return (utf8_int8_t *)str;
      }

      // ensure that the top 5 bits of this 3-byte utf8
      // codepoint were not 0, as then we could have used
      // one of the smaller encodings
      if ((0 == (0x0f & str[0])) && (0 == (0x20 & str[1]))) {
        return (utf8_int8_t *)str;
      }

      // 3-byte utf8 code point (began with 0b1110xxxx)
      str += 3;
    } else if (0xc0 == (0xe0 & *str)) {
      // ensure that there's 2 bytes or more remained
      if (remained < 2) {
        return (utf8_int8_t *)str;
      }

      // ensure the 1 following byte in this 2-byte
      // utf8 codepoint began with 0b10xxxxxx
      if (0x80 != (0xc0 & str[1])) {
        return (utf8_int8_t *)str;
      }

      // ensure that our utf8 codepoint ended after 2 bytes
      if (0x80 == (0xc0 & str[2])) {
        return (utf8_int8_t *)str;
      }

      // ensure that the top 4 bits of this 2-byte utf8
      // codepoint were not 0, as then we could have used
      // one of the smaller encodings
      if (0 == (0x1e & str[0])) {
        return (utf8_int8_t *)str;
      }

      // 2-byte utf8 code point (began with 0b110xxxxx)
      str += 2;
    } else if (0x00 == (0x80 & *str)) {
      // 1-byte ascii (began with 0b0xxxxxxx)
      str += 1;
    } else {
      // we have an invalid 0b1xxxxxxx utf8 code point entry
      return (utf8_int8_t *)str;
    }
  }

  return utf8_null;
}

int utf8makevalid(utf8_int8_t *str, const utf8_int32_t replacement) {
  utf8_int8_t *read = str;
  utf8_int8_t *write = read;
  const utf8_int8_t r = (utf8_int8_t)replacement;
  utf8_int32_t codepoint = 0;

  if (replacement > 0x7f) {
    return -1;
  }

  while ('\0' != *read) {
    if (0xf0 == (0xf8 & *read)) {
      // ensure each of the 3 following bytes in this 4-byte
      // utf8 codepoint began with 0b10xxxxxx
      if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2])) ||
          (0x80 != (0xc0 & read[3]))) {
        *write++ = r;
        read++;
        continue;
      }

      // 4-byte utf8 code point (began with 0b11110xxx)
      read = utf8codepoint(read, &codepoint);
      write = utf8catcodepoint(write, codepoint, 4);
    } else if (0xe0 == (0xf0 & *read)) {
      // ensure each of the 2 following bytes in this 3-byte
      // utf8 codepoint began with 0b10xxxxxx
      if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2]))) {
        *write++ = r;
        read++;
        continue;
      }

      // 3-byte utf8 code point (began with 0b1110xxxx)
      read = utf8codepoint(read, &codepoint);
      write = utf8catcodepoint(write, codepoint, 3);
    } else if (0xc0 == (0xe0 & *read)) {
      // ensure the 1 following byte in this 2-byte
      // utf8 codepoint began with 0b10xxxxxx
      if (0x80 != (0xc0 & read[1])) {
        *write++ = r;
        read++;
        continue;
      }

      // 2-byte utf8 code point (began with 0b110xxxxx)
      read = utf8codepoint(read, &codepoint);
      write = utf8catcodepoint(write, codepoint, 2);
    } else if (0x00 == (0x80 & *read)) {
      // 1-byte ascii (began with 0b0xxxxxxx)
      read = utf8codepoint(read, &codepoint);
      write = utf8catcodepoint(write, codepoint, 1);
    } else {
      // if we got here then we've got a dangling continuation (0b10xxxxxx)
      *write++ = r;
      read++;
      continue;
    }
  }

  *write = '\0';

  return 0;
}

utf8_constexpr14_impl utf8_int8_t *
utf8codepoint(const utf8_int8_t *utf8_restrict str,
              utf8_int32_t *utf8_restrict out_codepoint) {
  if (0xf0 == (0xf8 & str[0])) {
    // 4 byte utf8 codepoint
    *out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) |
                     ((0x3f & str[2]) << 6) | (0x3f & str[3]);
    str += 4;
  } else if (0xe0 == (0xf0 & str[0])) {
    // 3 byte utf8 codepoint
    *out_codepoint =
        ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]);
    str += 3;
  } else if (0xc0 == (0xe0 & str[0])) {
    // 2 byte utf8 codepoint
    *out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]);
    str += 2;
  } else {
    // 1 byte utf8 codepoint otherwise
    *out_codepoint = str[0];
    str += 1;
  }

  return (utf8_int8_t *)str;
}

utf8_constexpr14_impl size_t utf8codepointcalcsize(const utf8_int8_t *str) {
  if (0xf0 == (0xf8 & str[0])) {
    // 4 byte utf8 codepoint
    return 4;
  } else if (0xe0 == (0xf0 & str[0])) {
    // 3 byte utf8 codepoint
    return 3;
  } else if (0xc0 == (0xe0 & str[0])) {
    // 2 byte utf8 codepoint
    return 2;
  }

  // 1 byte utf8 codepoint otherwise
  return 1;
}

utf8_constexpr14_impl size_t utf8codepointsize(utf8_int32_t chr) {
  if (0 == ((utf8_int32_t)0xffffff80 & chr)) {
    return 1;
  } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) {
    return 2;
  } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) {
    return 3;
  } else { // if (0 == ((int)0xffe00000 & chr)) {
    return 4;
  }
}

utf8_int8_t *utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n) {
  if (0 == ((utf8_int32_t)0xffffff80 & chr)) {
    // 1-byte/7-bit ascii
    // (0b0xxxxxxx)
    if (n < 1) {
      return utf8_null;
    }
    str[0] = (utf8_int8_t)chr;
    str += 1;
  } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) {
    // 2-byte/11-bit utf8 code point
    // (0b110xxxxx 0b10xxxxxx)
    if (n < 2) {
      return utf8_null;
    }
    str[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)((chr >> 6) & 0x1f));
    str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
    str += 2;
  } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) {
    // 3-byte/16-bit utf8 code point
    // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx)
    if (n < 3) {
      return utf8_null;
    }
    str[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)((chr >> 12) & 0x0f));
    str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f));
    str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
    str += 3;
  } else { // if (0 == ((int)0xffe00000 & chr)) {
    // 4-byte/21-bit utf8 code point
    // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx)
    if (n < 4) {
      return utf8_null;
    }
    str[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)((chr >> 18) & 0x07));
    str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f));
    str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f));
    str[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f));
    str += 4;
  }

  return str;
}

utf8_constexpr14_impl int utf8islower(utf8_int32_t chr) {
  return chr != utf8uprcodepoint(chr);
}

utf8_constexpr14_impl int utf8isupper(utf8_int32_t chr) {
  return chr != utf8lwrcodepoint(chr);
}

void utf8lwr(utf8_int8_t *utf8_restrict str) {
  utf8_int32_t cp = 0;
  utf8_int8_t *pn = utf8codepoint(str, &cp);

  while (cp != 0) {
    const utf8_int32_t lwr_cp = utf8lwrcodepoint(cp);
    const size_t size = utf8codepointsize(lwr_cp);

    if (lwr_cp != cp) {
      utf8catcodepoint(str, lwr_cp, size);
    }

    str = pn;
    pn = utf8codepoint(str, &cp);
  }
}

void utf8upr(utf8_int8_t *utf8_restrict str) {
  utf8_int32_t cp = 0;
  utf8_int8_t *pn = utf8codepoint(str, &cp);

  while (cp != 0) {
    const utf8_int32_t lwr_cp = utf8uprcodepoint(cp);
    const size_t size = utf8codepointsize(lwr_cp);

    if (lwr_cp != cp) {
      utf8catcodepoint(str, lwr_cp, size);
    }

    str = pn;
    pn = utf8codepoint(str, &cp);
  }
}

utf8_constexpr14_impl utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp) {
  if (((0x0041 <= cp) && (0x005a >= cp)) ||
      ((0x00c0 <= cp) && (0x00d6 >= cp)) ||
      ((0x00d8 <= cp) && (0x00de >= cp)) ||
      ((0x0391 <= cp) && (0x03a1 >= cp)) ||
      ((0x03a3 <= cp) && (0x03ab >= cp)) ||
      ((0x0410 <= cp) && (0x042f >= cp))) {
    cp += 32;
  } else if ((0x0400 <= cp) && (0x040f >= cp)) {
    cp += 80;
  } else if (((0x0100 <= cp) && (0x012f >= cp)) ||
             ((0x0132 <= cp) && (0x0137 >= cp)) ||
             ((0x014a <= cp) && (0x0177 >= cp)) ||
             ((0x0182 <= cp) && (0x0185 >= cp)) ||
             ((0x01a0 <= cp) && (0x01a5 >= cp)) ||
             ((0x01de <= cp) && (0x01ef >= cp)) ||
             ((0x01f8 <= cp) && (0x021f >= cp)) ||
             ((0x0222 <= cp) && (0x0233 >= cp)) ||
             ((0x0246 <= cp) && (0x024f >= cp)) ||
             ((0x03d8 <= cp) && (0x03ef >= cp)) ||
             ((0x0460 <= cp) && (0x0481 >= cp)) ||
             ((0x048a <= cp) && (0x04ff >= cp))) {
    cp |= 0x1;
  } else if (((0x0139 <= cp) && (0x0148 >= cp)) ||
             ((0x0179 <= cp) && (0x017e >= cp)) ||
             ((0x01af <= cp) && (0x01b0 >= cp)) ||
             ((0x01b3 <= cp) && (0x01b6 >= cp)) ||
             ((0x01cd <= cp) && (0x01dc >= cp))) {
    cp += 1;
    cp &= ~0x1;
  } else {
    switch (cp) {
    default:
      break;
    case 0x0178:
      cp = 0x00ff;
      break;
    case 0x0243:
      cp = 0x0180;
      break;
    case 0x018e:
      cp = 0x01dd;
      break;
    case 0x023d:
      cp = 0x019a;
      break;
    case 0x0220:
      cp = 0x019e;
      break;
    case 0x01b7:
      cp = 0x0292;
      break;
    case 0x01c4:
      cp = 0x01c6;
      break;
    case 0x01c7:
      cp = 0x01c9;
      break;
    case 0x01ca:
      cp = 0x01cc;
      break;
    case 0x01f1:
      cp = 0x01f3;
      break;
    case 0x01f7:
      cp = 0x01bf;
      break;
    case 0x0187:
      cp = 0x0188;
      break;
    case 0x018b:
      cp = 0x018c;
      break;
    case 0x0191:
      cp = 0x0192;
      break;
    case 0x0198:
      cp = 0x0199;
      break;
    case 0x01a7:
      cp = 0x01a8;
      break;
    case 0x01ac:
      cp = 0x01ad;
      break;
    case 0x01af:
      cp = 0x01b0;
      break;
    case 0x01b8:
      cp = 0x01b9;
      break;
    case 0x01bc:
      cp = 0x01bd;
      break;
    case 0x01f4:
      cp = 0x01f5;
      break;
    case 0x023b:
      cp = 0x023c;
      break;
    case 0x0241:
      cp = 0x0242;
      break;
    case 0x03fd:
      cp = 0x037b;
      break;
    case 0x03fe:
      cp = 0x037c;
      break;
    case 0x03ff:
      cp = 0x037d;
      break;
    case 0x037f:
      cp = 0x03f3;
      break;
    case 0x0386:
      cp = 0x03ac;
      break;
    case 0x0388:
      cp = 0x03ad;
      break;
    case 0x0389:
      cp = 0x03ae;
      break;
    case 0x038a:
      cp = 0x03af;
      break;
    case 0x038c:
      cp = 0x03cc;
      break;
    case 0x038e:
      cp = 0x03cd;
      break;
    case 0x038f:
      cp = 0x03ce;
      break;
    case 0x0370:
      cp = 0x0371;
      break;
    case 0x0372:
      cp = 0x0373;
      break;
    case 0x0376:
      cp = 0x0377;
      break;
    case 0x03f4:
      cp = 0x03b8;
      break;
    case 0x03cf:
      cp = 0x03d7;
      break;
    case 0x03f9:
      cp = 0x03f2;
      break;
    case 0x03f7:
      cp = 0x03f8;
      break;
    case 0x03fa:
      cp = 0x03fb;
      break;
    }
  }

  return cp;
}

utf8_constexpr14_impl utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) {
  if (((0x0061 <= cp) && (0x007a >= cp)) ||
      ((0x00e0 <= cp) && (0x00f6 >= cp)) ||
      ((0x00f8 <= cp) && (0x00fe >= cp)) ||
      ((0x03b1 <= cp) && (0x03c1 >= cp)) ||
      ((0x03c3 <= cp) && (0x03cb >= cp)) ||
      ((0x0430 <= cp) && (0x044f >= cp))) {
    cp -= 32;
  } else if ((0x0450 <= cp) && (0x045f >= cp)) {
    cp -= 80;
  } else if (((0x0100 <= cp) && (0x012f >= cp)) ||
             ((0x0132 <= cp) && (0x0137 >= cp)) ||
             ((0x014a <= cp) && (0x0177 >= cp)) ||
             ((0x0182 <= cp) && (0x0185 >= cp)) ||
             ((0x01a0 <= cp) && (0x01a5 >= cp)) ||
             ((0x01de <= cp) && (0x01ef >= cp)) ||
             ((0x01f8 <= cp) && (0x021f >= cp)) ||
             ((0x0222 <= cp) && (0x0233 >= cp)) ||
             ((0x0246 <= cp) && (0x024f >= cp)) ||
             ((0x03d8 <= cp) && (0x03ef >= cp)) ||
             ((0x0460 <= cp) && (0x0481 >= cp)) ||
             ((0x048a <= cp) && (0x04ff >= cp))) {
    cp &= ~0x1;
  } else if (((0x0139 <= cp) && (0x0148 >= cp)) ||
             ((0x0179 <= cp) && (0x017e >= cp)) ||
             ((0x01af <= cp) && (0x01b0 >= cp)) ||
             ((0x01b3 <= cp) && (0x01b6 >= cp)) ||
             ((0x01cd <= cp) && (0x01dc >= cp))) {
    cp -= 1;
    cp |= 0x1;
  } else {
    switch (cp) {
    default:
      break;
    case 0x00ff:
      cp = 0x0178;
      break;
    case 0x0180:
      cp = 0x0243;
      break;
    case 0x01dd:
      cp = 0x018e;
      break;
    case 0x019a:
      cp = 0x023d;
      break;
    case 0x019e:
      cp = 0x0220;
      break;
    case 0x0292:
      cp = 0x01b7;
      break;
    case 0x01c6:
      cp = 0x01c4;
      break;
    case 0x01c9:
      cp = 0x01c7;
      break;
    case 0x01cc:
      cp = 0x01ca;
      break;
    case 0x01f3:
      cp = 0x01f1;
      break;
    case 0x01bf:
      cp = 0x01f7;
      break;
    case 0x0188:
      cp = 0x0187;
      break;
    case 0x018c:
      cp = 0x018b;
      break;
    case 0x0192:
      cp = 0x0191;
      break;
    case 0x0199:
      cp = 0x0198;
      break;
    case 0x01a8:
      cp = 0x01a7;
      break;
    case 0x01ad:
      cp = 0x01ac;
      break;
    case 0x01b0:
      cp = 0x01af;
      break;
    case 0x01b9:
      cp = 0x01b8;
      break;
    case 0x01bd:
      cp = 0x01bc;
      break;
    case 0x01f5:
      cp = 0x01f4;
      break;
    case 0x023c:
      cp = 0x023b;
      break;
    case 0x0242:
      cp = 0x0241;
      break;
    case 0x037b:
      cp = 0x03fd;
      break;
    case 0x037c:
      cp = 0x03fe;
      break;
    case 0x037d:
      cp = 0x03ff;
      break;
    case 0x03f3:
      cp = 0x037f;
      break;
    case 0x03ac:
      cp = 0x0386;
      break;
    case 0x03ad:
      cp = 0x0388;
      break;
    case 0x03ae:
      cp = 0x0389;
      break;
    case 0x03af:
      cp = 0x038a;
      break;
    case 0x03cc:
      cp = 0x038c;
      break;
    case 0x03cd:
      cp = 0x038e;
      break;
    case 0x03ce:
      cp = 0x038f;
      break;
    case 0x0371:
      cp = 0x0370;
      break;
    case 0x0373:
      cp = 0x0372;
      break;
    case 0x0377:
      cp = 0x0376;
      break;
    case 0x03d1:
      cp = 0x0398;
      break;
    case 0x03d7:
      cp = 0x03cf;
      break;
    case 0x03f2:
      cp = 0x03f9;
      break;
    case 0x03f8:
      cp = 0x03f7;
      break;
    case 0x03fb:
      cp = 0x03fa;
      break;
    }
  }

  return cp;
}

utf8_constexpr14_impl utf8_int8_t *
utf8rcodepoint(const utf8_int8_t *utf8_restrict str,
               utf8_int32_t *utf8_restrict out_codepoint) {
  const utf8_int8_t *s = (const utf8_int8_t *)str;

  if (0xf0 == (0xf8 & s[0])) {
    // 4 byte utf8 codepoint
    *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) |
                     ((0x3f & s[2]) << 6) | (0x3f & s[3]);
  } else if (0xe0 == (0xf0 & s[0])) {
    // 3 byte utf8 codepoint
    *out_codepoint =
        ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]);
  } else if (0xc0 == (0xe0 & s[0])) {
    // 2 byte utf8 codepoint
    *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]);
  } else {
    // 1 byte utf8 codepoint otherwise
    *out_codepoint = s[0];
  }

  do {
    s--;
  } while ((0 != (0x80 & s[0])) && (0x80 == (0xc0 & s[0])));

  return (utf8_int8_t *)s;
}

#undef utf8_restrict
#undef utf8_constexpr14
#undef utf8_null

#ifdef __cplusplus
} // extern "C"
#endif

#if defined(__clang__)
#pragma clang diagnostic pop
#endif

#endif // SHEREDOM_UTF8_H_INCLUDED
