/*
  Copyright (C) 2009-2017  Brazil
  Copyright (C) 2018-2025  Sutou Kouhei <kou@clear-code.com>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include "grn.h"
#include <limits.h>
#include <stdarg.h>
#include <string.h>
#include "grn_db.h"
#include "grn_float.h"
#include "grn_str.h"
#include "grn_table.h"
#include "grn_nfkc.h"

#ifndef _ISOC99_SOURCE
#  define _ISOC99_SOURCE
#endif /* _ISOC99_SOURCE */
#include <math.h>

#if defined(HAVE__GMTIME64_S) && defined(__GNUC__)
#  ifdef _WIN64
#    define gmtime_s(tm, time) _gmtime64_s(tm, time)
#  else /* _WIN64 */
#    define gmtime_s(tm, time) _gmtime32_s(tm, time)
#  endif /* _WIN64 */
#endif   /* defined(HAVE__GMTIME64_S) && defined(__GNUC__) */

static inline int
grn_str_charlen_utf8(grn_ctx *ctx,
                     const unsigned char *str,
                     const unsigned char *end)
{
  /* MEMO: This function allows non-null-terminated string as str. */
  /*       But requires the end of string. */
  if (end <= str || !*str) {
    return 0;
  }
  if (*str & 0x80) {
    grn_bit_scan_rev_result i;
    grn_bit_scan_rev_result len;
    GRN_BIT_SCAN_REV(~(((uint32_t)*str) << 24), len);
    len = 31 - len;
    if ((unsigned int)(len - 2) >= 3) { /* (len == 1 || len >= 5) */
      GRN_LOG(ctx,
              GRN_LOG_WARNING,
              "grn_str_charlen_utf8(): first byte is invalid");
      return 0;
    }
    if (str + len > end) {
      GRN_LOG(ctx,
              GRN_LOG_WARNING,
              "grn_str_charlen_utf8(): incomplete character");
      return 0;
    }
    for (i = 1; i < len; ++i) {
      if ((str[i] & 0xc0) != 0x80) {
        GRN_LOG(ctx,
                GRN_LOG_WARNING,
                "grn_str_charlen_utf8(): <%d>th byte is invalid",
                i + 1);
        return 0;
      }
    }
    return len;
  } else {
    return 1;
  }
}

unsigned int
grn_str_charlen(grn_ctx *ctx, const char *str, grn_encoding encoding)
{
  /* MEMO: This function requires null-terminated string as str.*/
  unsigned char *p = (unsigned char *)str;
  if (!*p) {
    return 0;
  }
  switch (encoding) {
  case GRN_ENC_EUC_JP:
    if (*p & 0x80) {
      if (*(p + 1)) {
        return 2;
      } else {
        /* This is invalid character */
        GRN_LOG(ctx,
                GRN_LOG_WARNING,
                "invalid euc-jp string end on grn_str_charlen");
        return 0;
      }
    }
    return 1;
  case GRN_ENC_UTF8:
    if (*p & 0x80) {
      unsigned int b;
      unsigned int w;
      unsigned int size;
      for (b = 0x40, w = 0; b && (*p & b); b >>= 1, w++)
        ;
      if (w == 0) {
        GRN_LOG(ctx,
                GRN_LOG_WARNING,
                "invalid utf8 string(1) on grn_str_charlen");
        return 0;
      }
      for (size = 1; w--; size++) {
        if (!*++p || (*p & 0xc0) != 0x80) {
          GRN_LOG(ctx,
                  GRN_LOG_WARNING,
                  "invalid utf8 string(2) on grn_str_charlen");
          return 0;
        }
      }
      return size;
    } else {
      return 1;
    }
  case GRN_ENC_SJIS:
    if (*p & 0x80) {
      /* we regard 0xa0 as JIS X 0201 KANA. adjusted to other tools. */
      if (0xa0 <= *p && *p <= 0xdf) {
        /* hankaku-kana */
        return 1;
      } else if (!(*(p + 1))) {
        /* This is invalid character */
        GRN_LOG(ctx,
                GRN_LOG_WARNING,
                "invalid sjis string end on grn_str_charlen");
        return 0;
      } else {
        return 2;
      }
    } else {
      return 1;
    }
  default:
    return 1;
  }
  return 0;
}

int
grn_charlen_(grn_ctx *ctx,
             const char *str,
             const char *end,
             grn_encoding encoding)
{
  /* MEMO: This function allows non-null-terminated string as str. */
  /*       But requires the end of string. */
  unsigned char *p = (unsigned char *)str;
  if (p >= (unsigned char *)end) {
    return 0;
  }
  switch (encoding) {
  case GRN_ENC_EUC_JP:
    if (*p & 0x80) {
      if ((p + 1) < (unsigned char *)end) {
        return 2;
      } else {
        /* This is invalid character */
        GRN_LOG(ctx,
                GRN_LOG_WARNING,
                "invalid euc-jp string end on grn_charlen");
        return 0;
      }
    }
    return 1;
  case GRN_ENC_UTF8:
    return grn_str_charlen_utf8(ctx, p, (unsigned char *)end);
  case GRN_ENC_SJIS:
    if (*p & 0x80) {
      /* we regard 0xa0 as JIS X 0201 KANA. adjusted to other tools. */
      if (0xa0 <= *p && *p <= 0xdf) {
        /* hankaku-kana */
        return 1;
      } else if (++p >= (unsigned char *)end) {
        /* This is invalid character */
        GRN_LOG(ctx, GRN_LOG_WARNING, "invalid sjis string end on grn_charlen");
        return 0;
      } else {
        return 2;
      }
    } else {
      return 1;
    }
  default:
    return 1;
  }
  return 0;
}

int
grn_charlen(grn_ctx *ctx, const char *str, const char *end)
{
  return grn_charlen_(ctx, str, end, ctx->encoding);
}

static unsigned char symbol[] = {
  ',',  '.', 0,   ':', ';', '?', '!', 0,    0,   0,   '`', 0,   '^', '~',
  '_',  0,   0,   0,   0,   0,   0,   0,    0,   0,   0,   '-', '-', '/',
  '\\', 0,   0,   '|', 0,   0,   0,   '\'', 0,   '"', '(', ')', 0,   0,
  '[',  ']', '{', '}', 0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
  '+',  '-', 0,   0,   0,   '=', 0,   '<',  '>', 0,   0,   0,   0,   0,
  0,    0,   0,   0,   0,   0,   '$', 0,    0,   '%', '#', '&', '*', '@',
  0,    0,   0,   0,   0,   0,   0,   0};

static inline grn_rc
normalize_euc(grn_ctx *ctx, grn_str *nstr)
{
  static uint16_t hankana[] = {
    0xa1a1, 0xa1a3, 0xa1d6, 0xa1d7, 0xa1a2, 0xa1a6, 0xa5f2, 0xa5a1,
    0xa5a3, 0xa5a5, 0xa5a7, 0xa5a9, 0xa5e3, 0xa5e5, 0xa5e7, 0xa5c3,
    0xa1bc, 0xa5a2, 0xa5a4, 0xa5a6, 0xa5a8, 0xa5aa, 0xa5ab, 0xa5ad,
    0xa5af, 0xa5b1, 0xa5b3, 0xa5b5, 0xa5b7, 0xa5b9, 0xa5bb, 0xa5bd,
    0xa5bf, 0xa5c1, 0xa5c4, 0xa5c6, 0xa5c8, 0xa5ca, 0xa5cb, 0xa5cc,
    0xa5cd, 0xa5ce, 0xa5cf, 0xa5d2, 0xa5d5, 0xa5d8, 0xa5db, 0xa5de,
    0xa5df, 0xa5e0, 0xa5e1, 0xa5e2, 0xa5e4, 0xa5e6, 0xa5e8, 0xa5e9,
    0xa5ea, 0xa5eb, 0xa5ec, 0xa5ed, 0xa5ef, 0xa5f3, 0xa1ab, 0xa1eb};
  static unsigned char dakuten[] = {
    0xf4, 0,    0,    0,    0,    0xac, 0,    0xae, 0,    0xb0, 0,
    0xb2, 0,    0xb4, 0,    0xb6, 0,    0xb8, 0,    0xba, 0,    0xbc,
    0,    0xbe, 0,    0xc0, 0,    0xc2, 0,    0,    0xc5, 0,    0xc7,
    0,    0xc9, 0,    0,    0,    0,    0,    0,    0xd0, 0,    0,
    0xd3, 0,    0,    0xd6, 0,    0,    0xd9, 0,    0,    0xdc};
  static unsigned char handaku[] =
    {0xd1, 0, 0, 0xd4, 0, 0, 0xd7, 0, 0, 0xda, 0, 0, 0xdd};
  int16_t *ch;
  const unsigned char *s, *s_, *e;
  unsigned char *d, *d0, *d_, b;
  uint_least8_t *cp, *ctypes, ctype;
  unsigned int size = nstr->orig_blen;
  unsigned int length = 0;
  int removeblankp = nstr->flags & GRN_STR_REMOVEBLANK;
  if (!(nstr->norm = GRN_MALLOC(size * 2 + 1))) {
    return GRN_NO_MEMORY_AVAILABLE;
  }
  d0 = (unsigned char *)nstr->norm;
  if (nstr->flags & GRN_STR_WITH_CHECKS) {
    if (!(nstr->checks = GRN_MALLOC(size * 2 * sizeof(int16_t) + 1))) {
      GRN_FREE(nstr->norm);
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  ch = nstr->checks;
  if (nstr->flags & GRN_STR_WITH_CTYPES) {
    if (!(nstr->ctypes = GRN_MALLOC(size + 1))) {
      GRN_FREE(nstr->checks);
      GRN_FREE(nstr->norm);
      nstr->checks = NULL;
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  cp = ctypes = nstr->ctypes;
  e = (unsigned char *)nstr->orig + size;
  for (s = s_ = (unsigned char *)nstr->orig, d = d_ = d0; s < e; s++) {
    if ((*s & 0x80)) {
      if (((s + 1) < e) && (*(s + 1) & 0x80)) {
        unsigned char c1 = *s++, c2 = *s, c3 = 0;
        switch (c1 >> 4) {
        case 0x08:
          if (c1 == 0x8e && 0xa0 <= c2 && c2 <= 0xdf) {
            uint16_t c = hankana[c2 - 0xa0];
            switch (c) {
            case 0xa1ab:
              if (d > d0 + 1 && d[-2] == 0xa5 && 0xa6 <= d[-1] &&
                  d[-1] <= 0xdb && (b = dakuten[d[-1] - 0xa6])) {
                *(d - 1) = b;
                if (ch) {
                  ch[-1] += 2;
                  s_ += 2;
                }
                continue;
              } else {
                *d++ = (unsigned char)(c >> 8);
                *d = (unsigned char)(c & 0xff);
              }
              break;
            case 0xa1eb:
              if (d > d0 + 1 && d[-2] == 0xa5 && 0xcf <= d[-1] &&
                  d[-1] <= 0xdb && (b = handaku[d[-1] - 0xcf])) {
                *(d - 1) = b;
                if (ch) {
                  ch[-1] += 2;
                  s_ += 2;
                }
                continue;
              } else {
                *d++ = (unsigned char)(c >> 8);
                *d = (unsigned char)(c & 0xff);
              }
              break;
            default:
              *d++ = (unsigned char)(c >> 8);
              *d = (unsigned char)(c & 0xff);
              break;
            }
            ctype = GRN_CHAR_KATAKANA;
          } else {
            *d++ = c1;
            *d = c2;
            ctype = GRN_CHAR_OTHERS;
          }
          break;
        case 0x09:
          *d++ = c1;
          *d = c2;
          ctype = GRN_CHAR_OTHERS;
          break;
        case 0x0a:
          switch (c1 & 0x0f) {
          case 1:
            switch (c2) {
            case 0xbc:
              *d++ = c1;
              *d = c2;
              ctype = GRN_CHAR_KATAKANA;
              break;
            case 0xb9:
              *d++ = c1;
              *d = c2;
              ctype = GRN_CHAR_KANJI;
              break;
            case 0xa1:
              if (removeblankp) {
                if (cp > ctypes) {
                  *(cp - 1) |= GRN_STR_BLANK;
                }
                continue;
              } else {
                *d = ' ';
                ctype = GRN_STR_BLANK | GRN_CHAR_SYMBOL;
              }
              break;
            default:
              if (c2 >= 0xa4 && (c3 = symbol[c2 - 0xa4])) {
                *d = c3;
                ctype = GRN_CHAR_SYMBOL;
              } else {
                *d++ = c1;
                *d = c2;
                ctype = GRN_CHAR_OTHERS;
              }
              break;
            }
            break;
          case 2:
            *d++ = c1;
            *d = c2;
            ctype = GRN_CHAR_SYMBOL;
            break;
          case 3:
            c3 = c2 - 0x80;
            if ('a' <= c3 && c3 <= 'z') {
              ctype = GRN_CHAR_ALPHA;
              *d = c3;
            } else if ('A' <= c3 && c3 <= 'Z') {
              ctype = GRN_CHAR_ALPHA;
              *d = c3 + 0x20;
            } else if ('0' <= c3 && c3 <= '9') {
              ctype = GRN_CHAR_DIGIT;
              *d = c3;
            } else {
              ctype = GRN_CHAR_OTHERS;
              *d++ = c1;
              *d = c2;
            }
            break;
          case 4:
            *d++ = c1;
            *d = c2;
            ctype = GRN_CHAR_HIRAGANA;
            break;
          case 5:
            *d++ = c1;
            *d = c2;
            ctype = GRN_CHAR_KATAKANA;
            break;
          case 6:
          case 7:
          case 8:
            *d++ = c1;
            *d = c2;
            ctype = GRN_CHAR_SYMBOL;
            break;
          default:
            *d++ = c1;
            *d = c2;
            ctype = GRN_CHAR_OTHERS;
            break;
          }
          break;
        default:
          *d++ = c1;
          *d = c2;
          ctype = GRN_CHAR_KANJI;
          break;
        }
      } else {
        /* skip invalid character */
        continue;
      }
    } else {
      unsigned char c = *s;
      switch (c >> 4) {
      case 0:
      case 1:
        /* skip unprintable ascii */
        if (cp > ctypes) {
          *(cp - 1) |= GRN_STR_BLANK;
        }
        continue;
      case 2:
        if (c == 0x20) {
          if (removeblankp) {
            if (cp > ctypes) {
              *(cp - 1) |= GRN_STR_BLANK;
            }
            continue;
          } else {
            *d = ' ';
            ctype = GRN_STR_BLANK | GRN_CHAR_SYMBOL;
          }
        } else {
          *d = c;
          ctype = GRN_CHAR_SYMBOL;
        }
        break;
      case 3:
        *d = c;
        ctype = (c <= 0x39) ? GRN_CHAR_DIGIT : GRN_CHAR_SYMBOL;
        break;
      case 4:
        *d = ('A' <= c) ? c + 0x20 : c;
        ctype = (c == 0x40) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
        break;
      case 5:
        *d = (c <= 'Z') ? c + 0x20 : c;
        ctype = (c <= 0x5a) ? GRN_CHAR_ALPHA : GRN_CHAR_SYMBOL;
        break;
      case 6:
        *d = c;
        ctype = (c == 0x60) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
        break;
      case 7:
        *d = c;
        ctype = (c <= 0x7a) ? GRN_CHAR_ALPHA
                            : (c == 0x7f ? GRN_CHAR_OTHERS : GRN_CHAR_SYMBOL);
        break;
      default:
        *d = c;
        ctype = GRN_CHAR_OTHERS;
        break;
      }
    }
    d++;
    length++;
    if (cp) {
      *cp++ = ctype;
    }
    if (ch) {
      *ch++ = (int16_t)(s + 1 - s_);
      s_ = s + 1;
      while (++d_ < d) {
        *ch++ = 0;
      }
    }
  }
  if (cp) {
    *cp = GRN_CHAR_NULL;
  }
  *d = '\0';
  nstr->length = length;
  nstr->norm_blen = (unsigned int)(d - (unsigned char *)nstr->norm);
  return GRN_SUCCESS;
}

#ifdef GRN_WITH_NFKC
static inline grn_rc
normalize_utf8(grn_ctx *ctx, grn_str *nstr)
{
  int16_t *ch;
  const unsigned char *s, *s_, *s__ = NULL, *p, *p2, *pe, *e;
  unsigned char *d, *d_, *de;
  uint_least8_t *cp;
  unsigned int length = 0;
  int ls;
  int lp;
  unsigned int size = nstr->orig_blen;
  unsigned int ds = size * 3;
  int removeblankp = nstr->flags & GRN_STR_REMOVEBLANK;
  if (!(nstr->norm = GRN_MALLOC(ds + 1))) {
    return GRN_NO_MEMORY_AVAILABLE;
  }
  if (nstr->flags & GRN_STR_WITH_CHECKS) {
    if (!(nstr->checks = GRN_MALLOC(ds * sizeof(int16_t) + 1))) {
      GRN_FREE(nstr->norm);
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  ch = nstr->checks;
  if (nstr->flags & GRN_STR_WITH_CTYPES) {
    if (!(nstr->ctypes = GRN_MALLOC(ds + 1))) {
      if (nstr->checks) {
        GRN_FREE(nstr->checks);
        nstr->checks = NULL;
      }
      GRN_FREE(nstr->norm);
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  cp = nstr->ctypes;
  d = (unsigned char *)nstr->norm;
  de = d + ds;
  d_ = NULL;
  e = (unsigned char *)nstr->orig + size;
  for (s = s_ = (unsigned char *)nstr->orig;; s += ls) {
    if (!(ls = grn_str_charlen_utf8(ctx, s, e))) {
      break;
    }
    if ((p = (unsigned char *)grn_nfkc_decompose(s))) {
      pe = p + strlen((char *)p);
    } else {
      p = s;
      pe = p + ls;
    }
    if (d_ && (p2 = (unsigned char *)grn_nfkc_compose(d_, p))) {
      p = p2;
      pe = p + strlen((char *)p);
      if (cp) {
        cp--;
      }
      if (ch) {
        ch -= (d - d_);
        s_ = s__;
      }
      d = d_;
      length--;
    }
    for (;; p += lp) {
      if (!(lp = grn_str_charlen_utf8(ctx, p, pe))) {
        break;
      }
      if ((*p == ' ' && removeblankp) ||
          *p < 0x20 /* skip unprintable ascii */) {
        if (cp > nstr->ctypes) {
          *(cp - 1) |= GRN_STR_BLANK;
        }
      } else {
        if (de <= d + lp) {
          unsigned char *norm;
          ds += (ds >> 1) + (unsigned int)lp;
          if (!(norm = GRN_REALLOC(nstr->norm, ds + 1))) {
            if (nstr->ctypes) {
              GRN_FREE(nstr->ctypes);
              nstr->ctypes = NULL;
            }
            if (nstr->checks) {
              GRN_FREE(nstr->checks);
              nstr->checks = NULL;
            }
            GRN_FREE(nstr->norm);
            nstr->norm = NULL;
            return GRN_NO_MEMORY_AVAILABLE;
          }
          de = norm + ds;
          d = norm + (d - (unsigned char *)nstr->norm);
          nstr->norm = (char *)norm;
          if (ch) {
            int16_t *checks;
            if (!(checks =
                    GRN_REALLOC(nstr->checks, ds * sizeof(int16_t) + 1))) {
              if (nstr->ctypes) {
                GRN_FREE(nstr->ctypes);
                nstr->ctypes = NULL;
              }
              GRN_FREE(nstr->checks);
              nstr->checks = NULL;
              GRN_FREE(nstr->norm);
              nstr->norm = NULL;
              return GRN_NO_MEMORY_AVAILABLE;
            }
            ch = checks + (ch - nstr->checks);
            nstr->checks = checks;
          }
          if (cp) {
            uint_least8_t *ctypes;
            if (!(ctypes = GRN_REALLOC(nstr->ctypes, ds + 1))) {
              GRN_FREE(nstr->ctypes);
              nstr->ctypes = NULL;
              if (nstr->checks) {
                GRN_FREE(nstr->checks);
                nstr->checks = NULL;
              }
              GRN_FREE(nstr->norm);
              nstr->norm = NULL;
              return GRN_NO_MEMORY_AVAILABLE;
            }
            cp = ctypes + (cp - nstr->ctypes);
            nstr->ctypes = ctypes;
          }
        }
        grn_memcpy(d, p, (size_t)lp);
        d_ = d;
        d += lp;
        length++;
        if (cp) {
          *cp++ = grn_nfkc_char_type(p);
        }
        if (ch) {
          int i;
          if (s_ == s + ls) {
            *ch++ = -1;
          } else {
            *ch++ = (int16_t)(s + ls - s_);
            s__ = s_;
            s_ = s + ls;
          }
          for (i = lp; i > 1; i--) {
            *ch++ = 0;
          }
        }
      }
    }
  }
  if (cp) {
    *cp = GRN_CHAR_NULL;
  }
  *d = '\0';
  nstr->length = length;
  nstr->norm_blen = (unsigned int)(d - (unsigned char *)nstr->norm);
  return GRN_SUCCESS;
}
#endif /* GRN_WITH_NFKC */

static inline grn_rc
normalize_sjis(grn_ctx *ctx, grn_str *nstr)
{
  static uint16_t hankana[] = {
    0x8140, 0x8142, 0x8175, 0x8176, 0x8141, 0x8145, 0x8392, 0x8340,
    0x8342, 0x8344, 0x8346, 0x8348, 0x8383, 0x8385, 0x8387, 0x8362,
    0x815b, 0x8341, 0x8343, 0x8345, 0x8347, 0x8349, 0x834a, 0x834c,
    0x834e, 0x8350, 0x8352, 0x8354, 0x8356, 0x8358, 0x835a, 0x835c,
    0x835e, 0x8360, 0x8363, 0x8365, 0x8367, 0x8369, 0x836a, 0x836b,
    0x836c, 0x836d, 0x836e, 0x8371, 0x8374, 0x8377, 0x837a, 0x837d,
    0x837e, 0x8380, 0x8381, 0x8382, 0x8384, 0x8386, 0x8388, 0x8389,
    0x838a, 0x838b, 0x838c, 0x838d, 0x838f, 0x8393, 0x814a, 0x814b};
  static unsigned char dakuten[] = {
    0x94, 0,    0,    0,    0,    0x4b, 0,    0x4d, 0,    0x4f, 0,
    0x51, 0,    0x53, 0,    0x55, 0,    0x57, 0,    0x59, 0,    0x5b,
    0,    0x5d, 0,    0x5f, 0,    0x61, 0,    0,    0x64, 0,    0x66,
    0,    0x68, 0,    0,    0,    0,    0,    0,    0x6f, 0,    0,
    0x72, 0,    0,    0x75, 0,    0,    0x78, 0,    0,    0x7b};
  static unsigned char handaku[] =
    {0x70, 0, 0, 0x73, 0, 0, 0x76, 0, 0, 0x79, 0, 0, 0x7c};
  int16_t *ch;
  const unsigned char *s, *s_;
  unsigned char *d, *d0, *d_, b, *e;
  uint_least8_t *cp, *ctypes, ctype;
  unsigned int size = nstr->orig_blen;
  unsigned int length = 0;
  int removeblankp = nstr->flags & GRN_STR_REMOVEBLANK;
  if (!(nstr->norm = GRN_MALLOC(size * 2 + 1))) {
    return GRN_NO_MEMORY_AVAILABLE;
  }
  d0 = (unsigned char *)nstr->norm;
  if (nstr->flags & GRN_STR_WITH_CHECKS) {
    if (!(nstr->checks = GRN_MALLOC(size * 2 * sizeof(int16_t) + 1))) {
      GRN_FREE(nstr->norm);
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  ch = nstr->checks;
  if (nstr->flags & GRN_STR_WITH_CTYPES) {
    if (!(nstr->ctypes = GRN_MALLOC(size + 1))) {
      GRN_FREE(nstr->checks);
      GRN_FREE(nstr->norm);
      nstr->checks = NULL;
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  cp = ctypes = nstr->ctypes;
  e = (unsigned char *)nstr->orig + size;
  for (s = s_ = (unsigned char *)nstr->orig, d = d_ = d0; s < e; s++) {
    if ((*s & 0x80)) {
      if (0xa0 <= *s && *s <= 0xdf) {
        uint16_t c = hankana[*s - 0xa0];
        switch (c) {
        case 0x814a:
          if (d > d0 + 1 && d[-2] == 0x83 && 0x45 <= d[-1] && d[-1] <= 0x7a &&
              (b = dakuten[d[-1] - 0x45])) {
            *(d - 1) = b;
            if (ch) {
              ch[-1]++;
              s_++;
            }
            continue;
          } else {
            *d++ = (unsigned char)(c >> 8);
            *d = (unsigned char)(c & 0xff);
          }
          break;
        case 0x814b:
          if (d > d0 + 1 && d[-2] == 0x83 && 0x6e <= d[-1] && d[-1] <= 0x7a &&
              (b = handaku[d[-1] - 0x6e])) {
            *(d - 1) = b;
            if (ch) {
              ch[-1]++;
              s_++;
            }
            continue;
          } else {
            *d++ = (unsigned char)(c >> 8);
            *d = (unsigned char)(c & 0xff);
          }
          break;
        default:
          *d++ = (unsigned char)(c >> 8);
          *d = (unsigned char)(c & 0xff);
          break;
        }
        ctype = GRN_CHAR_KATAKANA;
      } else {
        if ((s + 1) < e && 0x40 <= *(s + 1) && *(s + 1) <= 0xfc) {
          unsigned char c1 = *s++, c2 = *s, c3 = 0;
          if (0x81 <= c1 && c1 <= 0x87) {
            switch (c1 & 0x0f) {
            case 1:
              switch (c2) {
              case 0x5b:
                *d++ = c1;
                *d = c2;
                ctype = GRN_CHAR_KATAKANA;
                break;
              case 0x58:
                *d++ = c1;
                *d = c2;
                ctype = GRN_CHAR_KANJI;
                break;
              case 0x40:
                if (removeblankp) {
                  if (cp > ctypes) {
                    *(cp - 1) |= GRN_STR_BLANK;
                  }
                  continue;
                } else {
                  *d = ' ';
                  ctype = GRN_STR_BLANK | GRN_CHAR_SYMBOL;
                }
                break;
              default:
                if (0x43 <= c2 && c2 <= 0x7e && (c3 = symbol[c2 - 0x43])) {
                  *d = c3;
                  ctype = GRN_CHAR_SYMBOL;
                } else if (0x7f <= c2 && c2 <= 0x97 &&
                           (c3 = symbol[c2 - 0x44])) {
                  *d = c3;
                  ctype = GRN_CHAR_SYMBOL;
                } else {
                  *d++ = c1;
                  *d = c2;
                  ctype = GRN_CHAR_OTHERS;
                }
                break;
              }
              break;
            case 2:
              c3 = c2 - 0x1f;
              if (0x4f <= c2 && c2 <= 0x58) {
                ctype = GRN_CHAR_DIGIT;
                *d = c2 - 0x1f;
              } else if (0x60 <= c2 && c2 <= 0x79) {
                ctype = GRN_CHAR_ALPHA;
                *d = c2 + 0x01;
              } else if (0x81 <= c2 && c2 <= 0x9a) {
                ctype = GRN_CHAR_ALPHA;
                *d = c2 - 0x20;
              } else if (0x9f <= c2 && c2 <= 0xf1) {
                *d++ = c1;
                *d = c2;
                ctype = GRN_CHAR_HIRAGANA;
              } else {
                *d++ = c1;
                *d = c2;
                ctype = GRN_CHAR_OTHERS;
              }
              break;
            case 3:
              if (0x40 <= c2 && c2 <= 0x96) {
                *d++ = c1;
                *d = c2;
                ctype = GRN_CHAR_KATAKANA;
              } else {
                *d++ = c1;
                *d = c2;
                ctype = GRN_CHAR_SYMBOL;
              }
              break;
            case 4:
            case 7:
              *d++ = c1;
              *d = c2;
              ctype = GRN_CHAR_SYMBOL;
              break;
            default:
              *d++ = c1;
              *d = c2;
              ctype = GRN_CHAR_OTHERS;
              break;
            }
          } else {
            *d++ = c1;
            *d = c2;
            ctype = GRN_CHAR_KANJI;
          }
        } else {
          /* skip invalid character */
          continue;
        }
      }
    } else {
      unsigned char c = *s;
      switch (c >> 4) {
      case 0:
      case 1:
        /* skip unprintable ascii */
        if (cp > ctypes) {
          *(cp - 1) |= GRN_STR_BLANK;
        }
        continue;
      case 2:
        if (c == 0x20) {
          if (removeblankp) {
            if (cp > ctypes) {
              *(cp - 1) |= GRN_STR_BLANK;
            }
            continue;
          } else {
            *d = ' ';
            ctype = GRN_STR_BLANK | GRN_CHAR_SYMBOL;
          }
        } else {
          *d = c;
          ctype = GRN_CHAR_SYMBOL;
        }
        break;
      case 3:
        *d = c;
        ctype = (c <= 0x39) ? GRN_CHAR_DIGIT : GRN_CHAR_SYMBOL;
        break;
      case 4:
        *d = ('A' <= c) ? c + 0x20 : c;
        ctype = (c == 0x40) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
        break;
      case 5:
        *d = (c <= 'Z') ? c + 0x20 : c;
        ctype = (c <= 0x5a) ? GRN_CHAR_ALPHA : GRN_CHAR_SYMBOL;
        break;
      case 6:
        *d = c;
        ctype = (c == 0x60) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
        break;
      case 7:
        *d = c;
        ctype = (c <= 0x7a) ? GRN_CHAR_ALPHA
                            : (c == 0x7f ? GRN_CHAR_OTHERS : GRN_CHAR_SYMBOL);
        break;
      default:
        *d = c;
        ctype = GRN_CHAR_OTHERS;
        break;
      }
    }
    d++;
    length++;
    if (cp) {
      *cp++ = ctype;
    }
    if (ch) {
      *ch++ = (int16_t)(s + 1 - s_);
      s_ = s + 1;
      while (++d_ < d) {
        *ch++ = 0;
      }
    }
  }
  if (cp) {
    *cp = GRN_CHAR_NULL;
  }
  *d = '\0';
  nstr->length = length;
  nstr->norm_blen = (unsigned int)(d - (unsigned char *)nstr->norm);
  return GRN_SUCCESS;
}

static inline grn_rc
normalize_none(grn_ctx *ctx, grn_str *nstr)
{
  int16_t *ch;
  const unsigned char *s, *s_, *e;
  unsigned char *d, *d0, *d_;
  uint_least8_t *cp, *ctypes, ctype;
  unsigned int size = nstr->orig_blen;
  unsigned int length = 0;
  int removeblankp = nstr->flags & GRN_STR_REMOVEBLANK;
  if (!(nstr->norm = GRN_MALLOC(size + 1))) {
    return GRN_NO_MEMORY_AVAILABLE;
  }
  d0 = (unsigned char *)nstr->norm;
  if (nstr->flags & GRN_STR_WITH_CHECKS) {
    if (!(nstr->checks = GRN_MALLOC(size * sizeof(int16_t) + 1))) {
      GRN_FREE(nstr->norm);
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  ch = nstr->checks;
  if (nstr->flags & GRN_STR_WITH_CTYPES) {
    if (!(nstr->ctypes = GRN_MALLOC(size + 1))) {
      GRN_FREE(nstr->checks);
      GRN_FREE(nstr->norm);
      nstr->checks = NULL;
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  cp = ctypes = nstr->ctypes;
  e = (unsigned char *)nstr->orig + size;
  for (s = s_ = (unsigned char *)nstr->orig, d = d_ = d0; s < e; s++) {
    unsigned char c = *s;
    switch (c >> 4) {
    case 0:
    case 1:
      /* skip unprintable ascii */
      if (cp > ctypes) {
        *(cp - 1) |= GRN_STR_BLANK;
      }
      continue;
    case 2:
      if (c == 0x20) {
        if (removeblankp) {
          if (cp > ctypes) {
            *(cp - 1) |= GRN_STR_BLANK;
          }
          continue;
        } else {
          *d = ' ';
          ctype = GRN_STR_BLANK | GRN_CHAR_SYMBOL;
        }
      } else {
        *d = c;
        ctype = GRN_CHAR_SYMBOL;
      }
      break;
    case 3:
      *d = c;
      ctype = (c <= 0x39) ? GRN_CHAR_DIGIT : GRN_CHAR_SYMBOL;
      break;
    case 4:
      *d = ('A' <= c) ? c + 0x20 : c;
      ctype = (c == 0x40) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    case 5:
      *d = (c <= 'Z') ? c + 0x20 : c;
      ctype = (c <= 0x5a) ? GRN_CHAR_ALPHA : GRN_CHAR_SYMBOL;
      break;
    case 6:
      *d = c;
      ctype = (c == 0x60) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    case 7:
      *d = c;
      ctype = (c <= 0x7a) ? GRN_CHAR_ALPHA
                          : (c == 0x7f ? GRN_CHAR_OTHERS : GRN_CHAR_SYMBOL);
      break;
    default:
      *d = c;
      ctype = GRN_CHAR_OTHERS;
      break;
    }
    d++;
    length++;
    if (cp) {
      *cp++ = ctype;
    }
    if (ch) {
      *ch++ = (int16_t)(s + 1 - s_);
      s_ = s + 1;
      while (++d_ < d) {
        *ch++ = 0;
      }
    }
  }
  if (cp) {
    *cp = GRN_CHAR_NULL;
  }
  *d = '\0';
  nstr->length = length;
  nstr->norm_blen = (unsigned int)(d - (unsigned char *)nstr->norm);
  return GRN_SUCCESS;
}

/* use cp1252 as latin1 */
static inline grn_rc
normalize_latin1(grn_ctx *ctx, grn_str *nstr)
{
  int16_t *ch;
  const unsigned char *s, *s_, *e;
  unsigned char *d, *d0, *d_;
  uint_least8_t *cp, *ctypes, ctype;
  unsigned int size = nstr->orig_blen;
  unsigned int length = 0;
  int removeblankp = nstr->flags & GRN_STR_REMOVEBLANK;
  if (!(nstr->norm = GRN_MALLOC(size + 1))) {
    return GRN_NO_MEMORY_AVAILABLE;
  }
  d0 = (unsigned char *)nstr->norm;
  if (nstr->flags & GRN_STR_WITH_CHECKS) {
    if (!(nstr->checks = GRN_MALLOC(size * sizeof(int16_t) + 1))) {
      GRN_FREE(nstr->norm);
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  ch = nstr->checks;
  if (nstr->flags & GRN_STR_WITH_CTYPES) {
    if (!(nstr->ctypes = GRN_MALLOC(size + 1))) {
      GRN_FREE(nstr->checks);
      GRN_FREE(nstr->norm);
      nstr->checks = NULL;
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  cp = ctypes = nstr->ctypes;
  e = (unsigned char *)nstr->orig + size;
  for (s = s_ = (unsigned char *)nstr->orig, d = d_ = d0; s < e; s++) {
    unsigned char c = *s;
    switch (c >> 4) {
    case 0:
    case 1:
      /* skip unprintable ascii */
      if (cp > ctypes) {
        *(cp - 1) |= GRN_STR_BLANK;
      }
      continue;
    case 2:
      if (c == 0x20) {
        if (removeblankp) {
          if (cp > ctypes) {
            *(cp - 1) |= GRN_STR_BLANK;
          }
          continue;
        } else {
          *d = ' ';
          ctype = GRN_STR_BLANK | GRN_CHAR_SYMBOL;
        }
      } else {
        *d = c;
        ctype = GRN_CHAR_SYMBOL;
      }
      break;
    case 3:
      *d = c;
      ctype = (c <= 0x39) ? GRN_CHAR_DIGIT : GRN_CHAR_SYMBOL;
      break;
    case 4:
      *d = ('A' <= c) ? c + 0x20 : c;
      ctype = (c == 0x40) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    case 5:
      *d = (c <= 'Z') ? c + 0x20 : c;
      ctype = (c <= 0x5a) ? GRN_CHAR_ALPHA : GRN_CHAR_SYMBOL;
      break;
    case 6:
      *d = c;
      ctype = (c == 0x60) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    case 7:
      *d = c;
      ctype = (c <= 0x7a) ? GRN_CHAR_ALPHA
                          : (c == 0x7f ? GRN_CHAR_OTHERS : GRN_CHAR_SYMBOL);
      break;
    case 8:
      if (c == 0x8a || c == 0x8c || c == 0x8e) {
        *d = c + 0x10;
        ctype = GRN_CHAR_ALPHA;
      } else {
        *d = c;
        ctype = GRN_CHAR_SYMBOL;
      }
      break;
    case 9:
      if (c == 0x9a || c == 0x9c || c == 0x9e || c == 0x9f) {
        *d = (c == 0x9f) ? c + 0x60 : c;
        ctype = GRN_CHAR_ALPHA;
      } else {
        *d = c;
        ctype = GRN_CHAR_SYMBOL;
      }
      break;
    case 0x0c:
      *d = c + 0x20;
      ctype = GRN_CHAR_ALPHA;
      break;
    case 0x0d:
      *d = (c == 0xd7 || c == 0xdf) ? c : c + 0x20;
      ctype = (c == 0xd7) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    case 0x0e:
      *d = c;
      ctype = GRN_CHAR_ALPHA;
      break;
    case 0x0f:
      *d = c;
      ctype = (c == 0xf7) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    default:
      *d = c;
      ctype = GRN_CHAR_OTHERS;
      break;
    }
    d++;
    length++;
    if (cp) {
      *cp++ = ctype;
    }
    if (ch) {
      *ch++ = (int16_t)(s + 1 - s_);
      s_ = s + 1;
      while (++d_ < d) {
        *ch++ = 0;
      }
    }
  }
  if (cp) {
    *cp = GRN_CHAR_NULL;
  }
  *d = '\0';
  nstr->length = length;
  nstr->norm_blen = (unsigned int)(d - (unsigned char *)nstr->norm);
  return GRN_SUCCESS;
}

static inline grn_rc
normalize_koi8r(grn_ctx *ctx, grn_str *nstr)
{
  int16_t *ch;
  const unsigned char *s, *s_, *e;
  unsigned char *d, *d0, *d_;
  uint_least8_t *cp, *ctypes, ctype;
  unsigned int size = nstr->orig_blen;
  unsigned int length = 0;
  int removeblankp = nstr->flags & GRN_STR_REMOVEBLANK;
  if (!(nstr->norm = GRN_MALLOC(size + 1))) {
    return GRN_NO_MEMORY_AVAILABLE;
  }
  d0 = (unsigned char *)nstr->norm;
  if (nstr->flags & GRN_STR_WITH_CHECKS) {
    if (!(nstr->checks = GRN_MALLOC(size * sizeof(int16_t) + 1))) {
      GRN_FREE(nstr->norm);
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  ch = nstr->checks;
  if (nstr->flags & GRN_STR_WITH_CTYPES) {
    if (!(nstr->ctypes = GRN_MALLOC(size + 1))) {
      GRN_FREE(nstr->checks);
      GRN_FREE(nstr->norm);
      nstr->checks = NULL;
      nstr->norm = NULL;
      return GRN_NO_MEMORY_AVAILABLE;
    }
  }
  cp = ctypes = nstr->ctypes;
  e = (unsigned char *)nstr->orig + size;
  for (s = s_ = (unsigned char *)nstr->orig, d = d_ = d0; s < e; s++) {
    unsigned char c = *s;
    switch (c >> 4) {
    case 0:
    case 1:
      /* skip unprintable ascii */
      if (cp > ctypes) {
        *(cp - 1) |= GRN_STR_BLANK;
      }
      continue;
    case 2:
      if (c == 0x20) {
        if (removeblankp) {
          if (cp > ctypes) {
            *(cp - 1) |= GRN_STR_BLANK;
          }
          continue;
        } else {
          *d = ' ';
          ctype = GRN_STR_BLANK | GRN_CHAR_SYMBOL;
        }
      } else {
        *d = c;
        ctype = GRN_CHAR_SYMBOL;
      }
      break;
    case 3:
      *d = c;
      ctype = (c <= 0x39) ? GRN_CHAR_DIGIT : GRN_CHAR_SYMBOL;
      break;
    case 4:
      *d = ('A' <= c) ? c + 0x20 : c;
      ctype = (c == 0x40) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    case 5:
      *d = (c <= 'Z') ? c + 0x20 : c;
      ctype = (c <= 0x5a) ? GRN_CHAR_ALPHA : GRN_CHAR_SYMBOL;
      break;
    case 6:
      *d = c;
      ctype = (c == 0x60) ? GRN_CHAR_SYMBOL : GRN_CHAR_ALPHA;
      break;
    case 7:
      *d = c;
      ctype = (c <= 0x7a) ? GRN_CHAR_ALPHA
                          : (c == 0x7f ? GRN_CHAR_OTHERS : GRN_CHAR_SYMBOL);
      break;
    case 0x0a:
      *d = c;
      ctype = (c == 0xa3) ? GRN_CHAR_ALPHA : GRN_CHAR_OTHERS;
      break;
    case 0x0b:
      if (c == 0xb3) {
        *d = c - 0x10;
        ctype = GRN_CHAR_ALPHA;
      } else {
        *d = c;
        ctype = GRN_CHAR_OTHERS;
      }
      break;
    case 0x0c:
    case 0x0d:
      *d = c;
      ctype = GRN_CHAR_ALPHA;
      break;
    case 0x0e:
    case 0x0f:
      *d = c - 0x20;
      ctype = GRN_CHAR_ALPHA;
      break;
    default:
      *d = c;
      ctype = GRN_CHAR_OTHERS;
      break;
    }
    d++;
    length++;
    if (cp) {
      *cp++ = ctype;
    }
    if (ch) {
      *ch++ = (int16_t)(s + 1 - s_);
      s_ = s + 1;
      while (++d_ < d) {
        *ch++ = 0;
      }
    }
  }
  if (cp) {
    *cp = GRN_CHAR_NULL;
  }
  *d = '\0';
  nstr->length = length;
  nstr->norm_blen = (unsigned int)(d - (unsigned char *)nstr->norm);
  return GRN_SUCCESS;
}

static grn_str *
grn_fakenstr_open(grn_ctx *ctx,
                  const char *str,
                  size_t str_len,
                  grn_encoding encoding,
                  int flags)
{
  /* TODO: support GRN_STR_REMOVEBLANK flag and ctypes */
  grn_str *nstr;
  if (!(nstr = GRN_CALLOC(sizeof(grn_str)))) {
    GRN_LOG(ctx,
            GRN_LOG_ALERT,
            "memory allocation on grn_fakenstr_open failed !");
    return NULL;
  }
  if (!(nstr->norm = GRN_MALLOC(str_len + 1))) {
    GRN_LOG(ctx,
            GRN_LOG_ALERT,
            "memory allocation for keyword on grn_snip_add_cond failed !");
    GRN_FREE(nstr);
    return NULL;
  }
  nstr->orig = str;
  nstr->orig_blen = (unsigned int)str_len;
  grn_memcpy(nstr->norm, str, str_len);
  nstr->norm[str_len] = '\0';
  nstr->norm_blen = (unsigned int)str_len;
  nstr->ctypes = NULL;
  nstr->flags = flags;

  if (flags & GRN_STR_WITH_CHECKS) {
    int16_t f = 0;
    unsigned char c;
    size_t i;
    if (!(nstr->checks = (int16_t *)GRN_MALLOC(sizeof(int16_t) * str_len))) {
      GRN_FREE(nstr->norm);
      GRN_FREE(nstr);
      return NULL;
    }
    switch (encoding) {
    case GRN_ENC_EUC_JP:
      for (i = 0; i < str_len; i++) {
        if (!f) {
          c = (unsigned char)str[i];
          f = ((c >= 0xa1U && c <= 0xfeU) || c == 0x8eU ? 2
                                                        : (c == 0x8fU ? 3 : 1));
          nstr->checks[i] = f;
        } else {
          nstr->checks[i] = 0;
        }
        f--;
      }
      break;
    case GRN_ENC_SJIS:
      for (i = 0; i < str_len; i++) {
        if (!f) {
          c = (unsigned char)str[i];
          f = (c >= 0x81U && ((c <= 0x9fU) || (c >= 0xe0U && c <= 0xfcU)) ? 2
                                                                          : 1);
          nstr->checks[i] = f;
        } else {
          nstr->checks[i] = 0;
        }
        f--;
      }
      break;
    case GRN_ENC_UTF8:
      for (i = 0; i < str_len; i++) {
        if (!f) {
          c = (unsigned char)str[i];
          f = (c & 0x80U ? (c & 0x20U ? (c & 0x10U ? 4 : 3) : 2) : 1);
          nstr->checks[i] = f;
        } else {
          nstr->checks[i] = 0;
        }
        f--;
      }
      break;
    default:
      for (i = 0; i < str_len; i++) {
        nstr->checks[i] = 1;
      }
      break;
    }
  } else {
    nstr->checks = NULL;
  }
  return nstr;
}

grn_str *
grn_str_open_(grn_ctx *ctx,
              const char *str,
              unsigned int str_len,
              int flags,
              grn_encoding encoding)
{
  grn_rc rc;
  grn_str *nstr;
  if (!str || !str_len) {
    return NULL;
  }

  if (!(flags & GRN_STR_NORMALIZE)) {
    return grn_fakenstr_open(ctx, str, str_len, encoding, flags);
  }

  if (!(nstr = GRN_MALLOC(sizeof(grn_str)))) {
    GRN_LOG(ctx, GRN_LOG_ALERT, "memory allocation on grn_str_open failed !");
    return NULL;
  }
  nstr->orig = str;
  nstr->orig_blen = str_len;
  nstr->norm = NULL;
  nstr->norm_blen = 0;
  nstr->checks = NULL;
  nstr->ctypes = NULL;
  nstr->encoding = encoding;
  nstr->flags = flags;
  switch (encoding) {
  case GRN_ENC_EUC_JP:
    rc = normalize_euc(ctx, nstr);
    break;
  case GRN_ENC_UTF8:
#ifdef GRN_WITH_NFKC
    rc = normalize_utf8(ctx, nstr);
#else  /* GRN_WITH_NFKC */
    rc = normalize_none(ctx, nstr);
#endif /* GRN_WITH_NFKC */
    break;
  case GRN_ENC_SJIS:
    rc = normalize_sjis(ctx, nstr);
    break;
  case GRN_ENC_LATIN1:
    rc = normalize_latin1(ctx, nstr);
    break;
  case GRN_ENC_KOI8R:
    rc = normalize_koi8r(ctx, nstr);
    break;
  default:
    rc = normalize_none(ctx, nstr);
    break;
  }
  if (rc) {
    grn_str_close(ctx, nstr);
    return NULL;
  }
  return nstr;
}

grn_str *
grn_str_open(grn_ctx *ctx, const char *str, unsigned int str_len, int flags)
{
  return grn_str_open_(ctx, str, str_len, flags, ctx->encoding);
}

grn_rc
grn_str_close(grn_ctx *ctx, grn_str *nstr)
{
  if (nstr) {
    if (nstr->norm) {
      GRN_FREE(nstr->norm);
    }
    if (nstr->ctypes) {
      GRN_FREE(nstr->ctypes);
    }
    if (nstr->checks) {
      GRN_FREE(nstr->checks);
    }
    GRN_FREE(nstr);
    return GRN_SUCCESS;
  } else {
    return GRN_INVALID_ARGUMENT;
  }
}

static const char *grn_enc_string[] = {
  "default", "none", "euc_jp", "utf8", "sjis", "latin1", "koi8r"};

const char *
grn_encoding_to_string(grn_encoding enc)
{
  if (enc < (sizeof(grn_enc_string) / sizeof(char *))) {
    return grn_enc_string[enc];
  } else {
    return "unknown";
  }
}

grn_encoding
grn_encoding_parse(const char *str)
{
  grn_encoding e = GRN_ENC_UTF8;
  int i = sizeof(grn_enc_string) / sizeof(grn_enc_string[0]);
  while (i--) {
    if (!strcmp(str, grn_enc_string[i])) {
      e = (grn_encoding)i;
    }
  }
  return e;
}

size_t
grn_str_len(grn_ctx *ctx,
            const char *str,
            const char *end,
            grn_encoding encoding,
            const char **last)
{
  size_t len, tlen;
  const char *p = NULL;
  for (len = 0; str < end; len++) {
    p = str;
    if (!(tlen = grn_charlen_(ctx, str, end, encoding))) {
      break;
    }
    str += tlen;
  }
  if (last) {
    *last = str;
  }
  return len;
}

int
grn_isspace(const char *str, grn_encoding encoding)
{
  const unsigned char *s = (const unsigned char *)str;
  if (!s) {
    return 0;
  }
  switch (s[0]) {
  case ' ':
  case '\f':
  case '\n':
  case '\r':
  case '\t':
  case '\v':
    return 1;
  case 0x81:
    if (encoding == GRN_ENC_SJIS && s[1] == 0x40) {
      return 2;
    }
    break;
  case 0xA1:
    if (encoding == GRN_ENC_EUC_JP && s[1] == 0xA1) {
      return 2;
    }
    break;
  case 0xE3:
    if (encoding == GRN_ENC_UTF8 && s[1] == 0x80 && s[2] == 0x80) {
      return 3;
    }
    break;
  default:
    break;
  }
  return 0;
}

int8_t
grn_atoi8(const char *nptr, const char *end, const char **rest)
{
  const char *p = nptr;
  int8_t v = 0, t, n = 0, o = 0;
  if (p < end && *p == '-') {
    p++;
    n = 1;
    o = 1;
  }
  while (p < end && *p >= '0' && *p <= '9') {
    t = (int8_t)(v * 10 - (*p - '0'));
    if (t > v || (!n && t == INT8_MIN)) {
      v = 0;
      break;
    }
    v = t;
    o = 0;
    p++;
  }
  if (rest) {
    *rest = o ? nptr : p;
  }
  return n ? v : -v;
}

uint8_t
grn_atoui8(const char *nptr, const char *end, const char **rest)
{
  uint8_t v = 0, t;
  while (nptr < end && *nptr >= '0' && *nptr <= '9') {
    t = (uint8_t)(v * 10 + (*nptr - '0'));
    if (t < v) {
      v = 0;
      break;
    }
    v = t;
    nptr++;
  }
  if (rest) {
    *rest = nptr;
  }
  return v;
}

int16_t
grn_atoi16(const char *nptr, const char *end, const char **rest)
{
  const char *p = nptr;
  int16_t v = 0, t, n = 0, o = 0;
  if (p < end && *p == '-') {
    p++;
    n = 1;
    o = 1;
  }
  while (p < end && *p >= '0' && *p <= '9') {
    t = (int16_t)(v * 10 - (*p - '0'));
    if (t > v || (!n && t == INT16_MIN)) {
      v = 0;
      break;
    }
    v = t;
    o = 0;
    p++;
  }
  if (rest) {
    *rest = o ? nptr : p;
  }
  return n ? v : -v;
}

uint16_t
grn_atoui16(const char *nptr, const char *end, const char **rest)
{
  uint16_t v = 0, t;
  while (nptr < end && *nptr >= '0' && *nptr <= '9') {
    t = (uint16_t)(v * 10 + (*nptr - '0'));
    if (t < v) {
      v = 0;
      break;
    }
    v = t;
    nptr++;
  }
  if (rest) {
    *rest = nptr;
  }
  return v;
}

int
grn_atoi(const char *nptr, const char *end, const char **rest)
{
  const char *p = nptr;
  int v = 0, t, n = 0, o = 0;
  if (p < end && *p == '-') {
    p++;
    n = 1;
    o = 1;
  }
  while (p < end && *p >= '0' && *p <= '9') {
    t = v * 10 - (*p - '0');
    if (t > v || (!n && t == INT32_MIN)) {
      v = 0;
      break;
    }
    v = t;
    o = 0;
    p++;
  }
  if (rest) {
    *rest = o ? nptr : p;
  }
  return n ? v : -v;
}

unsigned int
grn_atoui(const char *nptr, const char *end, const char **rest)
{
  unsigned int v = 0, t;
  while (nptr < end && *nptr >= '0' && *nptr <= '9') {
    t = v * 10 + (unsigned int)(*nptr - '0');
    if (t < v) {
      v = 0;
      break;
    }
    v = t;
    nptr++;
  }
  if (rest) {
    *rest = nptr;
  }
  return v;
}

int64_t
grn_atoll(const char *nptr, const char *end, const char **rest)
{
  const char *p = nptr;
  int o = 0;
  int64_t v = 0;
  if (p < end && *p == '-') {
    p++;
    o = 1;
    while (p < end && *p >= '0' && *p <= '9') {
      int64_t t = v * 10 - (*p - '0');
      if (t > v) {
        v = 0;
        break;
      }
      v = t;
      o = 0;
      p++;
    }
  } else {
    while (p < end && *p >= '0' && *p <= '9') {
      int64_t t = v * 10 + (*p - '0');
      if (t < v) {
        v = 0;
        break;
      }
      v = t;
      p++;
    }
  }
  if (rest) {
    *rest = o ? nptr : p;
  }
  return v;
}

uint64_t
grn_atoull(const char *nptr, const char *end, const char **rest)
{
  uint64_t v = 0, t;
  while (nptr < end && *nptr >= '0' && *nptr <= '9') {
    t = v * 10 + (uint64_t)(*nptr - '0');
    if (t < v) {
      v = 0;
      break;
    }
    v = t;
    nptr++;
  }
  if (rest) {
    *rest = nptr;
  }
  return v;
}

unsigned int
grn_htoui(const char *nptr, const char *end, const char **rest)
{
  unsigned int v = 0, t;
  while (nptr < end) {
    switch (*nptr) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      t = v * 16 + (unsigned int)(*nptr++ - '0');
      break;
    case 'a':
    case 'b':
    case 'c':
    case 'd':
    case 'e':
    case 'f':
      t = v * 16 + (unsigned int)(*nptr++ - 'a') + 10;
      break;
    case 'A':
    case 'B':
    case 'C':
    case 'D':
    case 'E':
    case 'F':
      t = v * 16 + (unsigned int)(*nptr++ - 'A') + 10;
      break;
    default:
      v = 0;
      goto exit;
    }
    if (t < v) {
      v = 0;
      goto exit;
    }
    v = t;
  }
exit:
  if (rest) {
    *rest = nptr;
  }
  return v;
}

void
grn_itoh(unsigned int i, char *p, unsigned int len)
{
  static const char *hex = "0123456789ABCDEF";
  p += len - 1;
  while (len--) {
    *p-- = hex[i & 0xf];
    i >>= 4;
  }
}

grn_rc
grn_itoa(int i, char *p, char *end, char **rest)
{
  char *q;
  if (p >= end) {
    return GRN_INVALID_ARGUMENT;
  }
  q = p;
  if (i < 0) {
    *p++ = '-';
    q = p;
    if (i == INT_MIN) {
      if (p >= end) {
        return GRN_INVALID_ARGUMENT;
      }
      *p++ = (char)((-(i % 10)) + '0');
      i /= 10;
    }
    i = -i;
  }
  do {
    if (p >= end) {
      return GRN_INVALID_ARGUMENT;
    }
    *p++ = (char)(i % 10 + '0');
  } while ((i /= 10) > 0);
  if (rest) {
    *rest = p;
  }
  for (p--; q < p; q++, p--) {
    char t = *q;
    *q = *p;
    *p = t;
  }
  return GRN_SUCCESS;
}

grn_rc
grn_itoa_padded(int i, char *p, char *end, char ch)
{
  char *q;
  if (p >= end) {
    return GRN_INVALID_ARGUMENT;
  }
  if (i < 0) {
    *p++ = '-';
    if (i == INT_MIN) {
      if (p >= end) {
        return GRN_INVALID_ARGUMENT;
      }
      *p++ = (char)((-(i % 10)) + '0');
      i /= 10;
    }
    i = -i;
  }
  q = end - 1;
  do {
    if (q < p) {
      return GRN_INVALID_ARGUMENT;
    }
    *q-- = (char)(i % 10 + '0');
  } while ((i /= 10) > 0);
  while (q >= p) {
    *q-- = ch;
  }
  return GRN_SUCCESS;
}

grn_rc
grn_lltoa(int64_t i, char *p, char *end, char **rest)
{
  char *q;
  if (p >= end) {
    return GRN_INVALID_ARGUMENT;
  }
  q = p;
  if (i < 0) {
    *p++ = '-';
    q = p;
    if (i == INT64_MIN) {
      *p++ = (char)((-(i % 10)) + '0');
      i /= 10;
    }
    i = -i;
  }
  do {
    if (p >= end) {
      return GRN_INVALID_ARGUMENT;
    }
    *p++ = (char)(i % 10 + '0');
  } while ((i /= 10) > 0);
  if (rest) {
    *rest = p;
  }
  for (p--; q < p; q++, p--) {
    char t = *q;
    *q = *p;
    *p = t;
  }
  return GRN_SUCCESS;
}

grn_rc
grn_ulltoa(uint64_t i, char *p, char *end, char **rest)
{
  char *q;
  if (p >= end) {
    return GRN_INVALID_ARGUMENT;
  }
  q = p;
  do {
    if (p >= end) {
      return GRN_INVALID_ARGUMENT;
    }
    *p++ = (char)(i % 10 + '0');
  } while ((i /= 10) > 0);
  if (rest) {
    *rest = p;
  }
  for (p--; q < p; q++, p--) {
    char t = *q;
    *q = *p;
    *p = t;
  }
  return GRN_SUCCESS;
}

#define I2B(i)                                                                 \
  ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(i) &    \
                                                                      0x3f])

#define B2I(b)                                                                 \
  (((b) < '+' || 'z' < (b))                                                    \
     ? 0xff                                                                    \
     : "\x3e\xff\xff\xff\x3f\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\xff\xff"  \
       "\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b"  \
       "\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xff\xff\xff"  \
       "\xff\xff\xff\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27"  \
       "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"[(b) - '+'])

#define MASK 0x34d34d34

char *
grn_itob(grn_id id, char *p)
{
  id ^= MASK;
  *p++ = I2B(id >> 24);
  *p++ = I2B(id >> 18);
  *p++ = I2B(id >> 12);
  *p++ = I2B(id >> 6);
  *p++ = I2B(id);
  return p;
}

grn_id
grn_btoi(char *b)
{
  uint8_t i;
  grn_id id = 0;
  int len = 5;
  while (len--) {
    char c = *b++;
    if ((i = (uint8_t)B2I(c)) == 0xff) {
      return 0;
    }
    id = (id << 6) + i;
  }
  return id ^ MASK;
}

#define I2B32H(i) ("0123456789ABCDEFGHIJKLMNOPQRSTUV"[(i) & 0x1f])

char *
grn_lltob32h(int64_t i, char *p)
{
  uint64_t u = (uint64_t)i + 0x8000000000000000ULL;
  *p++ = I2B32H(u >> 60);
  *p++ = I2B32H(u >> 55);
  *p++ = I2B32H(u >> 50);
  *p++ = I2B32H(u >> 45);
  *p++ = I2B32H(u >> 40);
  *p++ = I2B32H(u >> 35);
  *p++ = I2B32H(u >> 30);
  *p++ = I2B32H(u >> 25);
  *p++ = I2B32H(u >> 20);
  *p++ = I2B32H(u >> 15);
  *p++ = I2B32H(u >> 10);
  *p++ = I2B32H(u >> 5);
  *p++ = I2B32H(u);
  return p;
}

char *
grn_ulltob32h(uint64_t i, char *p)
{
  char lb = (i >> 59) & 0x10;
  i += 0x8000000000000000ULL;
  *p++ = lb + I2B32H(i >> 60);
  *p++ = I2B32H(i >> 55);
  *p++ = I2B32H(i >> 50);
  *p++ = I2B32H(i >> 45);
  *p++ = I2B32H(i >> 40);
  *p++ = I2B32H(i >> 35);
  *p++ = I2B32H(i >> 30);
  *p++ = I2B32H(i >> 25);
  *p++ = I2B32H(i >> 20);
  *p++ = I2B32H(i >> 15);
  *p++ = I2B32H(i >> 10);
  *p++ = I2B32H(i >> 5);
  *p++ = I2B32H(i);
  return p;
}

grn_rc
grn_aton(
  grn_ctx *ctx, const char *p, const char *end, const char **rest, grn_obj *res)
{
  grn_rc rc = GRN_SUCCESS;

  if (*p == '+') {
    p++;
  }

  switch (*p) {
  case '-':
  case '0':
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    {
      int64_t int64;
      char rest_char;
      int64 = grn_atoll(p, end, rest);
      rest_char = **rest;
      if (end == *rest) {
        if ((int64_t)INT32_MIN <= int64 && int64 <= (int64_t)INT32_MAX) {
          grn_obj_reinit(ctx, res, GRN_DB_INT32, 0);
          GRN_INT32_SET(ctx, res, int64);
        } else if ((int64_t)INT32_MAX < int64 && int64 <= (int64_t)UINT32_MAX) {
          grn_obj_reinit(ctx, res, GRN_DB_UINT32, 0);
          GRN_UINT32_SET(ctx, res, int64);
        } else {
          grn_obj_reinit(ctx, res, GRN_DB_INT64, 0);
          GRN_INT64_SET(ctx, res, int64);
        }
      } else {
        if (*p != '-' && rest_char >= '0' && rest_char <= '9') {
          uint64_t uint64 = grn_atoull(p, end, rest);
          if (end == *rest) {
            grn_obj_reinit(ctx, res, GRN_DB_UINT64, 0);
            GRN_UINT64_SET(ctx, res, uint64);
          }
        }
        if (end != *rest) {
          if (rest_char == '.' || rest_char == 'e' || rest_char == 'E' ||
              (rest_char >= '0' && rest_char <= '9')) {
            grn_obj buffer;
            char *rest_float;
            double d;
            errno = 0;
            GRN_TEXT_INIT(&buffer, 0);
            GRN_TEXT_SET(ctx, &buffer, p, end - p);
            GRN_TEXT_PUTC(ctx, &buffer, '\0');
            d = strtod(GRN_TEXT_VALUE(&buffer), &rest_float);
            if (errno == 0 && rest_float + 1 == GRN_BULK_CURR(&buffer)) {
              grn_obj_reinit(ctx, res, GRN_DB_FLOAT, 0);
              GRN_FLOAT_SET(ctx, res, d);
              *rest = end;
            } else {
              rc = GRN_INVALID_ARGUMENT;
            }
            GRN_OBJ_FIN(ctx, &buffer);
          }
        }
      }
    }
    break;
  default:
    rc = GRN_INVALID_ARGUMENT;
    break;
  }

  return rc;
}

int
grn_str_tok(const char *str,
            size_t str_len,
            char delim,
            const char **tokbuf,
            int buf_size,
            const char **rest)
{
  const char **tok = tokbuf, **tok_end = tokbuf + buf_size;
  if (buf_size > 0) {
    const char *str_end = str + str_len;
    for (;; str++) {
      if (str == str_end) {
        *tok++ = str;
        break;
      }
      if (delim == *str) {
        // *str = '\0';
        *tok++ = str;
        if (tok == tok_end) {
          break;
        }
      }
    }
  }
  if (rest) {
    *rest = str;
  }
  return (int)(tok - tokbuf);
}

static inline int
op_getopt_flag(int *flags,
               const grn_str_getopt_opt *o,
               int argc,
               char *const argv[],
               int i,
               const char *optvalue)
{
  switch (o->op) {
  case GETOPT_OP_NONE:
    break;
  case GETOPT_OP_ON:
    *flags |= o->flag;
    break;
  case GETOPT_OP_OFF:
    *flags &= ~o->flag;
    break;
  case GETOPT_OP_UPDATE:
    *flags = o->flag;
    break;
  default:
    return i;
  }
  if (o->arg) {
    if (optvalue) {
      *o->arg = (char *)optvalue;
    } else if (++i < argc) {
      *o->arg = argv[i];
    } else {
      return -1;
    }
  }
  return i;
}

int
grn_str_getopt(int argc,
               char *const argv[],
               const grn_str_getopt_opt *opts,
               int *flags)
{
  int i;
  for (i = 1; i < argc; i++) {
    const char *v = argv[i];
    if (*v == '-') {
      const grn_str_getopt_opt *o;
      int found;
      if (*++v == '-') {
        const char *eq;
        size_t len;
        found = 0;
        v++;
        for (eq = v; *eq != '\0' && *eq != '='; eq++) {
        }
        len = (size_t)(eq - v);
        for (o = opts; o->opt != '\0' || o->longopt != NULL; o++) {
          if (o->longopt && strlen(o->longopt) == len &&
              !memcmp(v, o->longopt, len)) {
            i = op_getopt_flag(flags,
                               o,
                               argc,
                               argv,
                               i,
                               (*eq == '\0' ? NULL : eq + 1));
            if (i < 0) {
              fprintf(stderr,
                      "%s: option '--%s' needs argument.\n",
                      argv[0],
                      o->longopt);
              return -1;
            }
            found = 1;
            break;
          }
        }
        if (!found) {
          goto exit;
        }
      } else {
        const char *p;
        for (p = v; *p; p++) {
          found = 0;
          for (o = opts; o->opt != '\0' || o->longopt != NULL; o++) {
            if (o->opt && *p == o->opt) {
              i = op_getopt_flag(flags, o, argc, argv, i, NULL);
              if (i < 0) {
                fprintf(stderr,
                        "%s: option '-%c' needs argument.\n",
                        argv[0],
                        *p);
                return -1;
              }
              found = 1;
              break;
            }
          }
          if (!found) {
            goto exit;
          }
        }
      }
    } else {
      break;
    }
  }
  return i;
exit:
  fprintf(stderr, "%s: cannot recognize option '%s'.\n", argv[0], argv[i]);
  return -1;
}

#define UNIT_SIZE (1 << 12)
#define UNIT_MASK (UNIT_SIZE - 1)

static size_t grn_bulk_margin_size = 0;

grn_rc
grn_bulk_resize(grn_ctx *ctx, grn_obj *buf, size_t new_size)
{
  new_size += grn_bulk_margin_size + 1;
  if (!GRN_BULK_OUTP(buf) && new_size <= GRN_BULK_BUFSIZE) {
    return GRN_SUCCESS;
  }

  {
    size_t rounded_new_size;
    rounded_new_size = (new_size + (UNIT_MASK)) & (size_t)(~UNIT_MASK);
    if (rounded_new_size < new_size) {
      size_t current_size = GRN_BULK_VSIZE(buf);
      grn_rc rc = GRN_NOT_ENOUGH_SPACE;
      ERR(rc,
          "[bulk][resize] too large size: "
          "<%" GRN_FMT_SIZE "> -> <%" GRN_FMT_SIZE ">(<%" GRN_FMT_SIZE ">)",
          current_size,
          new_size,
          rounded_new_size);
      return rc;
    }
    new_size = rounded_new_size;
  }

  if (GRN_BULK_OUTP(buf)) {
    char *head = buf->u.b.head - (buf->u.b.head ? grn_bulk_margin_size : 0);
    if (!(head = GRN_REALLOC(head, new_size))) {
      return GRN_NO_MEMORY_AVAILABLE;
    }
    buf->u.b.curr = head + grn_bulk_margin_size + GRN_BULK_VSIZE(buf);
    buf->u.b.head = head + grn_bulk_margin_size;
    buf->u.b.tail = head + new_size;
  } else {
    char *head;
    if (!(head = GRN_MALLOC(new_size))) {
      return GRN_NO_MEMORY_AVAILABLE;
    }
    grn_memcpy(head, GRN_BULK_HEAD(buf), GRN_BULK_VSIZE(buf));
    buf->u.b.curr = head + grn_bulk_margin_size + GRN_BULK_VSIZE(buf);
    buf->u.b.head = head + grn_bulk_margin_size;
    buf->u.b.tail = head + new_size;
    buf->header.impl_flags |= GRN_OBJ_OUTPLACE;
  }

  return GRN_SUCCESS;
}

grn_rc
grn_bulk_reinit(grn_ctx *ctx, grn_obj *buf, size_t size)
{
  GRN_BULK_REWIND(buf);
  return grn_bulk_resize(ctx, buf, size);
}

static size_t
grn_bulk_compute_new_size(grn_ctx *ctx, grn_obj *buf, size_t new_size)
{
  if ((GRN_BULK_OUTP(buf) ||
       (new_size + grn_bulk_margin_size + 1) > GRN_BULK_BUFSIZE) &&
      (new_size < (UINT32_MAX / 2))) {
    new_size *= 2;
  }
  return new_size;
}

grn_rc
grn_bulk_write(grn_ctx *ctx, grn_obj *buf, const char *str, size_t len)
{
  grn_rc rc = GRN_SUCCESS;
  char *curr;
  if (GRN_BULK_REST(buf) < len) {
    size_t new_size;
    new_size = grn_bulk_compute_new_size(ctx, buf, GRN_BULK_VSIZE(buf) + len);
    if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
      return rc;
    }
  }
  if (len > 0) {
    curr = GRN_BULK_CURR(buf);
    grn_memcpy(curr, str, len);
    GRN_BULK_INCR_LEN(buf, len);
  }
  return rc;
}

grn_rc
grn_bulk_write_from(
  grn_ctx *ctx, grn_obj *bulk, const char *str, size_t from, size_t len)
{
  grn_rc rc = grn_bulk_truncate(ctx, bulk, from);
  if (!rc) {
    rc = grn_bulk_write(ctx, bulk, str, len);
  }
  return rc;
}

grn_rc
grn_bulk_reserve(grn_ctx *ctx, grn_obj *buf, size_t len)
{
  grn_rc rc = GRN_SUCCESS;
  if (GRN_BULK_REST(buf) < len) {
    size_t new_size;
    new_size = grn_bulk_compute_new_size(ctx, buf, GRN_BULK_VSIZE(buf) + len);
    if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
      return rc;
    }
  }
  return rc;
}

grn_rc
grn_bulk_space(grn_ctx *ctx, grn_obj *buf, size_t len)
{
  grn_rc rc = grn_bulk_reserve(ctx, buf, len);
  if (!rc) {
    GRN_BULK_INCR_LEN(buf, len);
  }
  return rc;
}

static grn_rc
grn_bulk_space_clear(grn_ctx *ctx, grn_obj *buf, size_t len)
{
  grn_rc rc = grn_bulk_reserve(ctx, buf, len);
  if (!rc) {
    memset(GRN_BULK_CURR(buf), 0, len);
    GRN_BULK_INCR_LEN(buf, len);
  }
  return rc;
}

grn_rc
grn_bulk_truncate(grn_ctx *ctx, grn_obj *bulk, size_t len)
{
  if (GRN_BULK_OUTP(bulk)) {
    if ((size_t)(bulk->u.b.tail - bulk->u.b.head) < len) {
      return grn_bulk_space_clear(ctx, bulk, len - GRN_BULK_VSIZE(bulk));
    } else {
      if (GRN_BULK_VSIZE(bulk) < len) {
        memset(GRN_BULK_CURR(bulk), 0, len - GRN_BULK_VSIZE(bulk));
      }
      bulk->u.b.curr = bulk->u.b.head + len;
    }
  } else {
    if (GRN_BULK_BUFSIZE < len) {
      return grn_bulk_space_clear(ctx, bulk, len - GRN_BULK_VSIZE(bulk));
    } else {
      if (GRN_BULK_VSIZE(bulk) < len) {
        memset(GRN_BULK_CURR(bulk), 0, len - GRN_BULK_VSIZE(bulk));
      }
      bulk->header.flags &= (grn_obj_flags)(~GRN_BULK_BUFSIZE_MAX);
      bulk->header.flags += (grn_obj_flags)len;
    }
  }
  return GRN_SUCCESS;
}

grn_rc
grn_text_itoa(grn_ctx *ctx, grn_obj *buf, int i)
{
  grn_rc rc = GRN_SUCCESS;
  for (;;) {
    char *curr = GRN_BULK_CURR(buf);
    char *tail = GRN_BULK_TAIL(buf);
    if (grn_itoa(i, curr, tail, &curr)) {
      size_t new_size;
      new_size =
        grn_bulk_compute_new_size(ctx, buf, GRN_BULK_WSIZE(buf) + UNIT_SIZE);
      if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
        return rc;
      }
    } else {
      GRN_BULK_SET_CURR(buf, curr);
      break;
    }
  }
  return rc;
}

grn_rc
grn_text_itoa_padded(grn_ctx *ctx, grn_obj *buf, int i, char ch, size_t len)
{
  grn_rc rc = GRN_SUCCESS;
  char *curr;
  if ((rc = grn_bulk_reserve(ctx, buf, len))) {
    return rc;
  }
  curr = GRN_BULK_CURR(buf);
  if (!grn_itoa_padded(i, curr, curr + len, ch)) {
    GRN_BULK_SET_CURR(buf, curr + len);
  }
  return rc;
}

grn_rc
grn_text_lltoa(grn_ctx *ctx, grn_obj *buf, long long int i)
{
  grn_rc rc = GRN_SUCCESS;
  for (;;) {
    char *curr = GRN_BULK_CURR(buf);
    char *tail = GRN_BULK_TAIL(buf);
    char *end;
    if (grn_lltoa(i, curr, tail, &end)) {
      size_t new_size;
      new_size =
        grn_bulk_compute_new_size(ctx, buf, GRN_BULK_WSIZE(buf) + UNIT_SIZE);
      if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
        return rc;
      }
    } else {
      GRN_BULK_SET_CURR(buf, end);
      break;
    }
  }
  return rc;
}

grn_rc
grn_text_ulltoa(grn_ctx *ctx, grn_obj *buf, unsigned long long int i)
{
  grn_rc rc = GRN_SUCCESS;
  for (;;) {
    char *curr = GRN_BULK_CURR(buf);
    char *tail = GRN_BULK_TAIL(buf);
    char *end;
    if (grn_ulltoa(i, curr, tail, &end)) {
      size_t new_size;
      new_size =
        grn_bulk_compute_new_size(ctx, buf, GRN_BULK_WSIZE(buf) + UNIT_SIZE);
      if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
        return rc;
      }
    } else {
      GRN_BULK_SET_CURR(buf, end);
      break;
    }
  }
  return rc;
}

static inline grn_rc
grn_text_ftoa_adjust(grn_ctx *ctx, grn_obj *buf, size_t before_size)
{
  size_t length = GRN_TEXT_LEN(buf) - before_size;
  char *start = GRN_BULK_CURR(buf) - length;
  if (start[length - 1] == '.') {
    GRN_TEXT_PUTC(ctx, buf, '0');
    return GRN_SUCCESS;
  } else {
    char *p, *q;
    start[length] = '\0';
    if ((p = strchr(start, 'e'))) {
      for (q = p; *(q - 2) != '.' && *(q - 1) == '0'; q--) {
        length--;
      }
      grn_memmove(q, p, (size_t)(start + length - q));
    } else {
      for (q = start + length; *(q - 2) != '.' && *(q - 1) == '0'; q--) {
        length--;
      }
    }
    return grn_bulk_truncate(ctx, buf, before_size + length);
  }
}

#ifdef GRN_HAVE_BFLOAT16
grn_rc
grn_text_bf16toa(grn_ctx *ctx, grn_obj *buf, grn_bfloat16 value)
{
  return grn_text_f32toa(ctx, buf, grn_bfloat16_to_float32(value));
}
#endif

grn_rc
grn_text_f32toa(grn_ctx *ctx, grn_obj *buf, float f)
{
  size_t before_size = GRN_TEXT_LEN(buf);
  grn_rc rc = grn_text_printf(ctx, buf, "%#.*g", FLT_DIG + 1, f);
  if (rc != GRN_SUCCESS) {
    return rc;
  }
  return grn_text_ftoa_adjust(ctx, buf, before_size);
}

grn_rc
grn_text_ftoa(grn_ctx *ctx, grn_obj *buf, double d)
{
  size_t before_size = GRN_TEXT_LEN(buf);
  grn_rc rc = grn_text_printf(ctx, buf, "%#.*g", DBL_DIG + 1, d);
  if (rc != GRN_SUCCESS) {
    return rc;
  }
  return grn_text_ftoa_adjust(ctx, buf, before_size);
}

grn_rc
grn_text_itoh(grn_ctx *ctx, grn_obj *buf, unsigned int i, size_t len)
{
  grn_rc rc = GRN_SUCCESS;
  if (GRN_BULK_REST(buf) < len) {
    size_t new_size;
    new_size = grn_bulk_compute_new_size(ctx, buf, GRN_BULK_VSIZE(buf) + len);
    if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
      return rc;
    }
  }
  grn_itoh(i, GRN_BULK_CURR(buf), (unsigned int)len);
  GRN_BULK_INCR_LEN(buf, len);
  return rc;
}

grn_rc
grn_text_itob(grn_ctx *ctx, grn_obj *buf, grn_id id)
{
  size_t len = 5;
  grn_rc rc = GRN_SUCCESS;
  if (GRN_BULK_REST(buf) < len) {
    size_t new_size;
    new_size = grn_bulk_compute_new_size(ctx, buf, GRN_BULK_VSIZE(buf) + len);
    if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
      return rc;
    }
  }
  grn_itob(id, GRN_BULK_CURR(buf));
  GRN_BULK_INCR_LEN(buf, len);
  return rc;
}

grn_rc
grn_text_lltob32h(grn_ctx *ctx, grn_obj *buf, long long int i)
{
  size_t len = 13;
  grn_rc rc = GRN_SUCCESS;
  if (GRN_BULK_REST(buf) < len) {
    size_t new_size;
    new_size = grn_bulk_compute_new_size(ctx, buf, GRN_BULK_VSIZE(buf) + len);
    if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
      return rc;
    }
  }
  grn_lltob32h(i, GRN_BULK_CURR(buf));
  GRN_BULK_INCR_LEN(buf, len);
  return rc;
}

grn_rc
grn_text_esc(grn_ctx *ctx, grn_obj *buf, const char *s, size_t len)
{
  const char *e;
  int l;
  grn_rc rc = GRN_SUCCESS;

  GRN_TEXT_PUTC(ctx, buf, '"');
  for (e = s + len; s < e; s += l) {
    l = grn_charlen(ctx, s, e);
    if (l == 0) {
      rc = grn_bulk_write(ctx, buf, "\\u", 2);
      if (rc != GRN_SUCCESS) {
        return rc;
      }
      rc = grn_text_itoh(ctx, buf, (uint8_t)*s, 4);
      if (rc != GRN_SUCCESS) {
        GRN_BULK_INCR_LEN(buf, -2);
        return rc;
      }
      l = 1;
      continue;
    }
    if (l == 1) {
      switch (*s) {
      case '"':
        grn_bulk_write(ctx, buf, "\\\"", 2);
        break;
      case '\\':
        grn_bulk_write(ctx, buf, "\\\\", 2);
        break;
      case '\b':
        grn_bulk_write(ctx, buf, "\\b", 2);
        break;
      case '\f':
        grn_bulk_write(ctx, buf, "\\f", 2);
        break;
      case '\n':
        grn_bulk_write(ctx, buf, "\\n", 2);
        break;
      case '\r':
        grn_bulk_write(ctx, buf, "\\r", 2);
        break;
      case '\t':
        grn_bulk_write(ctx, buf, "\\t", 2);
        break;
      case '\x00':
      case '\x01':
      case '\x02':
      case '\x03':
      case '\x04':
      case '\x05':
      case '\x06':
      case '\x07':
      case '\x0b':
      case '\x0e':
      case '\x0f':
      case '\x10':
      case '\x11':
      case '\x12':
      case '\x13':
      case '\x14':
      case '\x15':
      case '\x16':
      case '\x17':
      case '\x18':
      case '\x19':
      case '\x1a':
      case '\x1b':
      case '\x1c':
      case '\x1d':
      case '\x1e':
      case '\x1f':
      case '\x7f':
        if (!(rc = grn_bulk_write(ctx, buf, "\\u", 2))) {
          if ((rc = grn_text_itoh(ctx, buf, (unsigned int)(*s), 4))) {
            GRN_BULK_INCR_LEN(buf, -2);
            return rc;
          }
        } else {
          return rc;
        }
        break;
      default:
        GRN_TEXT_PUTC(ctx, buf, *s);
      }
    } else if (l == 3) {
      if (*s == '\xe2' && *(s + 1) == '\x80') {
        switch (*(s + 2)) {
        case '\xa8': /* \u2028 */
          grn_bulk_write(ctx, buf, "\\u2028", 6);
          break;
        case '\xa9': /* \u2029 */
          grn_bulk_write(ctx, buf, "\\u2029", 6);
          break;
        default:
          grn_bulk_write(ctx, buf, s, (size_t)l);
        }
      } else {
        grn_bulk_write(ctx, buf, s, (size_t)l);
      }
    } else {
      grn_bulk_write(ctx, buf, s, (size_t)l);
    }
  }
  GRN_TEXT_PUTC(ctx, buf, '"');
  return rc;
}

grn_rc
grn_text_code_point(grn_ctx *ctx, grn_obj *buffer, uint32_t code_point)
{
  GRN_API_ENTER;

  if (ctx->encoding != GRN_ENC_UTF8) {
    ERR(GRN_FUNCTION_NOT_IMPLEMENTED,
        "[text][code-point] UTF-8 is only supported: %s",
        grn_encoding_to_string(ctx->encoding));
  }

  if (code_point == 0x00 || code_point > 0x10FFFF ||
      /* surrogate */
      (0xD800 <= code_point && code_point <= 0xDFFF)) {
    code_point = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */
  }
  if (code_point <= 0x7F) {
    GRN_TEXT_PUTC(ctx, buffer, (char)code_point);
  } else if (code_point <= 0x07FF) {
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x0007C0) >> 6) | 0x60));
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x00003F)) | 0x80));
  } else if (code_point <= 0xFFFF) {
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x00F000) >> 12) | 0xE0));
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x000FC0) >> 6) | 0x80));
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x00003F)) | 0x80));
  } else if (code_point <= 0x10FFFF) {
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x1C0000) >> 18) | 0xE0));
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x03F000) >> 12) | 0xE0));
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x000FC0) >> 6) | 0x80));
    GRN_TEXT_PUTC(ctx, buffer, (char)(((code_point & 0x00003F)) | 0x80));
  } else {
    ERR(GRN_FUNCTION_NOT_IMPLEMENTED,
        "[text][code-point] out of range: %#x",
        code_point);
  }

  GRN_API_RETURN(ctx->rc);
}

grn_rc
grn_text_escape_xml(grn_ctx *ctx, grn_obj *buf, const char *s, size_t len)
{
  const char *e;
  int l;
  grn_rc rc = GRN_SUCCESS;

  for (e = s + len; s < e; s += l) {
    if (!(l = grn_charlen(ctx, s, e))) {
      break;
    }
    if (l == 1) {
      switch (*s) {
      case '"':
        grn_bulk_write(ctx, buf, "&quot;", 6);
        break;
      case '<':
        grn_bulk_write(ctx, buf, "&lt;", 4);
        break;
      case '>':
        grn_bulk_write(ctx, buf, "&gt;", 4);
        break;
      case '&':
        grn_bulk_write(ctx, buf, "&amp;", 5);
        break;
      default:
        GRN_TEXT_PUTC(ctx, buf, *s);
      }
    } else {
      grn_bulk_write(ctx, buf, s, (size_t)l);
    }
  }
  return rc;
}

#define TOK_ESC (0x80)

const char *
grn_text_unesc_tok(
  grn_ctx *ctx, grn_obj *buf, const char *s, const char *e, uint8_t *tok_type)
{
  const char *p;
  int len;
  uint8_t stat = GRN_TOK_VOID;
  for (p = s; p < e; p += len) {
    if (!(len = grn_charlen(ctx, p, e))) {
      p = e;
      stat &= (uint8_t)(~TOK_ESC);
      goto exit;
    }
    switch (stat) {
    case GRN_TOK_VOID:
      if (*p == ' ') {
        continue;
      }
      switch (*p) {
      case '"':
        stat = GRN_TOK_STRING;
        break;
      case '\'':
        stat = GRN_TOK_QUOTE;
        break;
      case ')':
      case '(':
        GRN_TEXT_PUT(ctx, buf, p, len);
        p += len;
        stat = GRN_TOK_SYMBOL;
        goto exit;
      case '\\':
        stat = GRN_TOK_SYMBOL | TOK_ESC;
        break;
      default:
        stat = GRN_TOK_SYMBOL;
        GRN_TEXT_PUT(ctx, buf, p, len);
        break;
      }
      break;
    case GRN_TOK_SYMBOL:
      if (*p == ' ') {
        goto exit;
      }
      switch (*p) {
      case '\'':
      case '"':
      case ')':
      case '(':
        goto exit;
      case '\\':
        stat |= TOK_ESC;
        break;
      default:
        GRN_TEXT_PUT(ctx, buf, p, len);
        break;
      }
      break;
    case GRN_TOK_STRING:
      switch (*p) {
      case '"':
        p += len;
        goto exit;
      case '\\':
        stat |= TOK_ESC;
        break;
      default:
        GRN_TEXT_PUT(ctx, buf, p, len);
        break;
      }
      break;
    case GRN_TOK_QUOTE:
      switch (*p) {
      case '\'':
        p += len;
        goto exit;
      case '\\':
        stat |= TOK_ESC;
        break;
      default:
        GRN_TEXT_PUT(ctx, buf, p, len);
        break;
      }
      break;
    case GRN_TOK_SYMBOL | TOK_ESC:
    case GRN_TOK_STRING | TOK_ESC:
    case GRN_TOK_QUOTE | TOK_ESC:
      switch (*p) {
      case 'b':
        GRN_TEXT_PUTC(ctx, buf, '\b');
        break;
      case 'f':
        GRN_TEXT_PUTC(ctx, buf, '\f');
        break;
      case 'n':
        GRN_TEXT_PUTC(ctx, buf, '\n');
        break;
      case 'r':
        GRN_TEXT_PUTC(ctx, buf, '\r');
        break;
      case 't':
        GRN_TEXT_PUTC(ctx, buf, '\t');
        break;
      default:
        GRN_TEXT_PUT(ctx, buf, p, len);
        break;
      }
      stat &= (uint8_t)(~TOK_ESC);
      break;
    }
  }
exit:
  *tok_type = stat;
  return p;
}

grn_rc
grn_text_benc(grn_ctx *ctx, grn_obj *buf, unsigned int v)
{
  grn_rc rc = GRN_SUCCESS;
  uint8_t *p;
  size_t required_size = 5;
  if (GRN_BULK_REST(buf) < required_size) {
    size_t new_size;
    new_size =
      grn_bulk_compute_new_size(ctx, buf, GRN_BULK_VSIZE(buf) + required_size);
    if ((rc = grn_bulk_resize(ctx, buf, new_size))) {
      return rc;
    }
  }
  p = (uint8_t *)GRN_BULK_CURR(buf);
  GRN_B_ENC(v, p);
  GRN_BULK_SET_CURR(buf, (char *)p);
  return rc;
}

/* 0x00 - 0x7f */
static const int_least8_t urlenc_tbl[] = {
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};

grn_rc
grn_text_urlenc(grn_ctx *ctx, grn_obj *buf, const char *s, size_t len)
{
  const char *e, c = '%';
  for (e = s + len; s < e; s++) {
    if ((signed char)*s < 0 || urlenc_tbl[(int)*s]) {
      if (!grn_bulk_write(ctx, buf, &c, 1)) {
        if (grn_text_itoh(ctx, buf, (unsigned int)(*s), 2)) {
          GRN_BULK_INCR_LEN(buf, -1);
        }
      }
    } else {
      GRN_TEXT_PUTC(ctx, buf, *s);
    }
  }
  return GRN_SUCCESS;
}

static const char *weekdays[7] = {
  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *months[12] = {"Jan",
                                 "Feb",
                                 "Mar",
                                 "Apr",
                                 "May",
                                 "Jun",
                                 "Jul",
                                 "Aug",
                                 "Sep",
                                 "Oct",
                                 "Nov",
                                 "Dec"};

grn_rc
grn_text_time2rfc1123(grn_ctx *ctx, grn_obj *bulk, int sec)
{
  time_t tsec;
  struct tm *t;
#ifdef HAVE__GMTIME64_S
  struct tm tm;
  tsec = (time_t)sec;
  t = (gmtime_s(&tm, &tsec) == 0) ? &tm : NULL;
#else /* HAVE__GMTIME64_S */
#  ifdef HAVE_GMTIME_R
  struct tm tm;
  tsec = (time_t)sec;
  t = gmtime_r(&tsec, &tm);
#  else  /* HAVE_GMTIME_R */
  tsec = (time_t)sec;
  t = gmtime(&tsec);
#  endif /* HAVE_GMTIME_R */
#endif   /* HAVE__GMTIME64_S */
  if (t) {
    GRN_TEXT_SET(ctx, bulk, weekdays[t->tm_wday], 3);
    GRN_TEXT_PUTS(ctx, bulk, ", ");
    grn_text_itoa_padded(ctx, bulk, t->tm_mday, '0', 2);
    GRN_TEXT_PUTS(ctx, bulk, " ");
    GRN_TEXT_PUT(ctx, bulk, months[t->tm_mon], 3);
    GRN_TEXT_PUTS(ctx, bulk, " ");
    grn_text_itoa(ctx, bulk, t->tm_year + 1900);
    GRN_TEXT_PUTS(ctx, bulk, " ");
    grn_text_itoa_padded(ctx, bulk, t->tm_hour, '0', 2);
    GRN_TEXT_PUTS(ctx, bulk, ":");
    grn_text_itoa_padded(ctx, bulk, t->tm_min, '0', 2);
    GRN_TEXT_PUTS(ctx, bulk, ":");
    grn_text_itoa_padded(ctx, bulk, t->tm_sec, '0', 2);
    GRN_TEXT_PUTS(ctx, bulk, " GMT");
  } else {
    GRN_TEXT_SETS(ctx, bulk, "Mon, 16 Mar 1980 20:40:00 GMT");
  }
  return GRN_SUCCESS;
}

grn_rc
grn_text_printf(grn_ctx *ctx, grn_obj *bulk, const char *format, ...)
{
  va_list args;

  va_start(args, format);
  grn_rc rc = grn_text_printfv(ctx, bulk, format, args);
  va_end(args);

  return rc;
}

grn_rc
grn_text_printfv(grn_ctx *ctx, grn_obj *bulk, const char *format, va_list args)
{
  int written_size = -1;
  const size_t rest_size = GRN_BULK_REST(bulk);

#ifdef WIN32
  bool is_written = false;
  if (rest_size > 0) {
    va_list copied_args;
    va_copy(copied_args, args);
    written_size =
      vsnprintf(GRN_BULK_CURR(bulk), rest_size, format, copied_args);
    va_end(copied_args);
    is_written = (written_size >= 0 && ((size_t)written_size + 1 <= rest_size));
  }

  if (!is_written) {
    va_list copied_args;
    va_copy(copied_args, args);
    const int to_be_written_size = vsnprintf(NULL, 0, format, copied_args);
    va_end(copied_args);

    if (to_be_written_size < 0) {
      return GRN_INVALID_ARGUMENT;
    }
    const size_t required_size =
      (size_t)to_be_written_size + 1; /* for terminate '\0'. */
    const grn_rc rc = grn_bulk_reserve(ctx, bulk, required_size);
    if (rc) {
      return rc;
    }
    va_copy(copied_args, args);
    written_size =
      vsnprintf(GRN_BULK_CURR(bulk), required_size, format, copied_args);
    va_end(copied_args);
  }
#else  /* WIN32 */
  va_list copied_args;
  va_copy(copied_args, args);
  written_size = vsnprintf(GRN_BULK_CURR(bulk), rest_size, format, copied_args);
  va_end(copied_args);
  const bool is_written =
    (written_size >= 0 && ((size_t)written_size + 1 <= rest_size));
  if (!is_written) {
    const size_t required_size =
      (size_t)written_size + 1; /* "+ 1" for terminate '\0'. */
    const grn_rc rc = grn_bulk_reserve(ctx, bulk, required_size);
    if (rc) {
      return rc;
    }
    va_copy(copied_args, args);
    written_size =
      vsnprintf(GRN_BULK_CURR(bulk), required_size, format, copied_args);
    va_end(copied_args);
  }
#endif /* WIN32 */

  if (written_size < 0) {
    return GRN_INVALID_ARGUMENT;
  }

  GRN_BULK_INCR_LEN(bulk, written_size);
  return GRN_SUCCESS;
}

grn_rc
grn_text_vprintf(grn_ctx *ctx, grn_obj *bulk, const char *format, va_list args)
{
  return grn_text_printfv(ctx, bulk, format, args);
}

grn_rc
grn_bulk_fin(grn_ctx *ctx, grn_obj *buf)
{
  if (!(buf->header.impl_flags & GRN_OBJ_REFER)) {
    if (GRN_BULK_OUTP(buf) && buf->u.b.head) {
      GRN_REALLOC(buf->u.b.head - grn_bulk_margin_size, 0);
    }
  }
  buf->header.flags = 0;
  buf->header.impl_flags &= (uint8_t)(~GRN_OBJ_DO_SHALLOW_COPY);
  memset(&(buf->u), 0, sizeof(buf->u));
  return GRN_SUCCESS;
}

grn_rc
grn_substring(grn_ctx *ctx,
              char **str,
              char **str_end,
              int start,
              int end,
              grn_encoding encoding)
{
  int i;
  int l;
  char *s = *str, *e = *str_end;
  for (i = 0; s < e; i++, s += l) {
    if (i == start) {
      *str = s;
    }
    if (!(l = grn_charlen(ctx, s, e))) {
      return GRN_INVALID_ARGUMENT;
    }
    if (i == end) {
      *str_end = s;
      break;
    }
  }
  return GRN_SUCCESS;
}

static void
grn_text_atoj(grn_ctx *ctx, grn_obj *bulk, grn_obj *obj, grn_id id)
{
  grn_obj buf;
  if (obj->header.type == GRN_ACCESSOR) {
    grn_accessor *a = (grn_accessor *)obj;
    GRN_TEXT_INIT(&buf, 0);
    for (;;) {
      GRN_BULK_REWIND(&buf);
      switch (a->action) {
      case GRN_ACCESSOR_GET_ID:
        GRN_UINT32_PUT(ctx, &buf, id);
        buf.header.domain = GRN_DB_UINT32;
        break;
      case GRN_ACCESSOR_GET_KEY:
        grn_table_get_key2(ctx, a->obj, id, &buf);
        buf.header.domain = DB_OBJ(a->obj)->header.domain;
        break;
      case GRN_ACCESSOR_GET_VALUE:
        grn_obj_get_value(ctx, a->obj, id, &buf);
        buf.header.domain = GRN_DB_INT32; /* fix me */
        break;
      case GRN_ACCESSOR_GET_SCORE:
        {
          double score = grn_table_get_score(ctx, a->obj, id);
          int32_t int32_score = (int32_t)score;
          GRN_INT32_PUT(ctx, &buf, int32_score);
        }
        buf.header.domain = GRN_DB_INT32;
        break;
      case GRN_ACCESSOR_GET_NSUBRECS:
        {
          uint32_t vs;
          grn_rset_recinfo *ri =
            (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          GRN_INT32_PUT(ctx, &buf, ri->n_subrecs);
        }
        buf.header.domain = GRN_DB_INT32;
        break;
      case GRN_ACCESSOR_GET_COLUMN_VALUE:
        if ((a->obj->header.flags & GRN_OBJ_COLUMN_TYPE_MASK) ==
            GRN_OBJ_COLUMN_VECTOR) {
          if (a->next) {
            grn_id *idp;
            grn_obj_get_value(ctx, a->obj, id, &buf);
            idp = (grn_id *)GRN_BULK_HEAD(&buf);
            GRN_TEXT_PUTC(ctx, bulk, '[');
            size_t vs;
            for (vs = GRN_BULK_VSIZE(&buf) / sizeof(grn_id); vs--; idp++) {
              grn_text_atoj(ctx, bulk, (grn_obj *)a->next, *idp);
              if (vs) {
                GRN_TEXT_PUTC(ctx, bulk, ',');
              }
            }
            GRN_TEXT_PUTC(ctx, bulk, ']');
          } else {
            grn_text_atoj(ctx, bulk, a->obj, id);
          }
          goto exit;
        } else {
          grn_obj_get_value(ctx, a->obj, id, &buf);
        }
        break;
      case GRN_ACCESSOR_GET_DB_OBJ:
        /* todo */
        break;
      case GRN_ACCESSOR_LOOKUP:
        /* todo */
        break;
      case GRN_ACCESSOR_FUNCALL:
        /* todo */
        break;
      }
      if (a->next) {
        a = a->next;
        id = *((grn_id *)GRN_BULK_HEAD(&buf));
      } else {
        break;
      }
    }
  } else {
    switch (obj->header.type) {
    case GRN_COLUMN_FIX_SIZE:
      GRN_VALUE_FIX_SIZE_INIT(&buf, 0, DB_OBJ(obj)->range);
      break;
    case GRN_COLUMN_VAR_SIZE:
      if ((obj->header.flags & GRN_OBJ_COLUMN_TYPE_MASK) ==
          GRN_OBJ_COLUMN_VECTOR) {
        grn_obj *range = grn_ctx_at(ctx, DB_OBJ(obj)->range);
        if (range->header.flags & GRN_OBJ_KEY_VAR_SIZE) {
          GRN_VALUE_VAR_SIZE_INIT(&buf, GRN_OBJ_VECTOR, DB_OBJ(obj)->range);
        } else {
          GRN_VALUE_FIX_SIZE_INIT(&buf, GRN_OBJ_VECTOR, DB_OBJ(obj)->range);
        }
      } else {
        GRN_VALUE_VAR_SIZE_INIT(&buf, 0, DB_OBJ(obj)->range);
      }
      break;
    case GRN_COLUMN_INDEX:
      GRN_UINT32_INIT(&buf, 0);
      break;
    default:
      GRN_TEXT_INIT(&buf, 0);
      break;
    }
    grn_obj_get_value(ctx, obj, id, &buf);
  }
  grn_text_otoj(ctx, bulk, &buf, NULL);
exit:
  grn_obj_close(ctx, &buf);
}

grn_rc
grn_text_otoj(grn_ctx *ctx, grn_obj *bulk, grn_obj *obj, grn_obj_format *format)
{
  grn_obj buf;
  GRN_TEXT_INIT(&buf, 0);
  switch (obj->header.type) {
  case GRN_BULK:
    switch (obj->header.domain) {
    case GRN_DB_VOID:
    case GRN_DB_SHORT_TEXT:
    case GRN_DB_TEXT:
    case GRN_DB_LONG_TEXT:
      grn_text_esc(ctx, bulk, GRN_BULK_HEAD(obj), GRN_BULK_VSIZE(obj));
      break;
    case GRN_DB_BOOL:
      if (*((unsigned char *)GRN_BULK_HEAD(obj))) {
        GRN_TEXT_PUTS(ctx, bulk, "true");
      } else {
        GRN_TEXT_PUTS(ctx, bulk, "false");
      }
      break;
    case GRN_DB_INT8:
      grn_text_itoa(ctx, bulk, GRN_BULK_VSIZE(obj) ? GRN_INT8_VALUE(obj) : 0);
      break;
    case GRN_DB_UINT8:
      grn_text_lltoa(ctx, bulk, GRN_BULK_VSIZE(obj) ? GRN_UINT8_VALUE(obj) : 0);
      break;
    case GRN_DB_INT16:
      grn_text_itoa(ctx, bulk, GRN_BULK_VSIZE(obj) ? GRN_INT16_VALUE(obj) : 0);
      break;
    case GRN_DB_UINT16:
      grn_text_lltoa(ctx,
                     bulk,
                     GRN_BULK_VSIZE(obj) ? GRN_UINT16_VALUE(obj) : 0);
      break;
    case GRN_DB_INT32:
      grn_text_itoa(ctx, bulk, GRN_BULK_VSIZE(obj) ? GRN_INT32_VALUE(obj) : 0);
      break;
    case GRN_DB_UINT32:
      grn_text_lltoa(ctx,
                     bulk,
                     GRN_BULK_VSIZE(obj) ? GRN_UINT32_VALUE(obj) : 0);
      break;
    case GRN_DB_INT64:
      grn_text_lltoa(ctx, bulk, GRN_BULK_VSIZE(obj) ? GRN_INT64_VALUE(obj) : 0);
      break;
    case GRN_DB_UINT64:
      grn_text_ulltoa(ctx,
                      bulk,
                      GRN_BULK_VSIZE(obj) ? GRN_UINT64_VALUE(obj) : 0);
      break;
#ifdef GRN_HAVE_BFLOAT16
    case GRN_DB_BFLOAT16:
      grn_text_bf16toa(ctx,
                       bulk,
                       GRN_BULK_VSIZE(obj) ? GRN_BFLOAT16_VALUE(obj) : 0);
      break;
#endif
    case GRN_DB_FLOAT32:
      grn_text_f32toa(ctx,
                      bulk,
                      GRN_BULK_VSIZE(obj) ? GRN_FLOAT32_VALUE(obj) : 0);
      break;
    case GRN_DB_FLOAT:
      grn_text_ftoa(ctx, bulk, GRN_BULK_VSIZE(obj) ? GRN_FLOAT_VALUE(obj) : 0);
      break;
    case GRN_DB_TIME:
      {
        double dv = (double)(*((int64_t *)GRN_BULK_HEAD(obj)));
        dv /= 1000000.0;
        grn_text_ftoa(ctx, bulk, dv);
      }
      break;
    case GRN_DB_TOKYO_GEO_POINT:
    case GRN_DB_WGS84_GEO_POINT:
      if (GRN_BULK_VSIZE(obj) == sizeof(grn_geo_point)) {
        grn_geo_point *gp = (grn_geo_point *)GRN_BULK_HEAD(obj);
        GRN_TEXT_PUTC(ctx, bulk, '"');
        grn_text_itoa(ctx, bulk, gp->latitude);
        GRN_TEXT_PUTC(ctx, bulk, 'x');
        grn_text_itoa(ctx, bulk, gp->longitude);
        GRN_TEXT_PUTC(ctx, bulk, '"');
      } else {
        GRN_TEXT_PUTS(ctx, bulk, "\"\"");
      }
      break;
    case GRN_DB_SHORT_BINARY:
    case GRN_DB_BINARY:
    case GRN_DB_LONG_BINARY:
      GRN_TEXT_PUTC(ctx, bulk, '"');
      grn_obj_cast(ctx, obj, bulk, false);
      GRN_TEXT_PUTC(ctx, bulk, '"');
      break;
    case GRN_DB_JSON:
      GRN_TEXT_PUTC(ctx, bulk, '"');
      {
        grn_obj json_text;
        GRN_TEXT_INIT(&json_text, 0);
        grn_json_to_string(ctx, obj, &json_text);
        const char *start = GRN_TEXT_VALUE(&json_text);
        const char *end = start + GRN_TEXT_LEN(&json_text);
        while (start < end) {
          int length = grn_charlen(ctx, start, end);
          if (length == 0) {
            break;
          }
          if (length == 1 && start[0] == '"') {
            GRN_TEXT_PUTC(ctx, bulk, '\\');
          }
          GRN_TEXT_PUT(ctx, bulk, start, length);
          start += length;
        }
        GRN_OBJ_FIN(ctx, &json_text);
      }
      GRN_TEXT_PUTC(ctx, bulk, '"');
      break;
    default:
      if (format) {
        size_t j;
        size_t ncolumns = GRN_PTR_VECTOR_SIZE(&format->columns);
        grn_id id = GRN_RECORD_VALUE(obj);
        if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
          GRN_TEXT_PUTS(ctx, bulk, "[");
          for (j = 0; j < ncolumns; j++) {
            grn_obj *column = GRN_PTR_VALUE_AT(&format->columns, j);
            grn_id range_id;
            if (j) {
              GRN_TEXT_PUTC(ctx, bulk, ',');
            }
            GRN_TEXT_PUTS(ctx, bulk, "[");
            GRN_BULK_REWIND(&buf);
            grn_column_name_(ctx, column, &buf);
            grn_text_otoj(ctx, bulk, &buf, NULL);
            GRN_TEXT_PUTC(ctx, bulk, ',');
            /* column range */
            range_id = grn_obj_get_range(ctx, column);
            if (range_id == GRN_ID_NIL) {
              GRN_TEXT_PUTS(ctx, bulk, "null");
            } else {
              int name_len;
              grn_obj *range_obj;
              char name_buf[GRN_TABLE_MAX_KEY_SIZE];

              range_obj = grn_ctx_at(ctx, range_id);
              name_len =
                grn_obj_name(ctx, range_obj, name_buf, GRN_TABLE_MAX_KEY_SIZE);
              GRN_BULK_REWIND(&buf);
              GRN_TEXT_PUT(ctx, &buf, name_buf, name_len);
              grn_text_otoj(ctx, bulk, &buf, NULL);
            }
            GRN_TEXT_PUTS(ctx, bulk, "]");
          }
          GRN_TEXT_PUTS(ctx, bulk, "],");
        }
        GRN_TEXT_PUTC(ctx, bulk, '[');
        for (j = 0; j < ncolumns; j++) {
          grn_obj *column = GRN_PTR_VALUE_AT(&format->columns, j);
          if (j) {
            GRN_TEXT_PUTC(ctx, bulk, ',');
          }
          grn_text_atoj(ctx, bulk, column, id);
        }
        GRN_TEXT_PUTC(ctx, bulk, ']');
      } else {
        if (GRN_BULK_VSIZE(obj) == 0) {
          GRN_TEXT_PUTS(ctx, bulk, "null");
        } else {
          grn_obj *table = grn_ctx_at(ctx, obj->header.domain);
          grn_id id = GRN_RECORD_VALUE(obj);
          if (table && table->header.type != GRN_TABLE_NO_KEY) {
            /* todo : temporal patch. grn_table_at() is kinda costful... */
            if (grn_table_at(ctx, table, id)) {
              grn_obj *accessor = grn_obj_column(ctx,
                                                 table,
                                                 GRN_COLUMN_NAME_KEY,
                                                 GRN_COLUMN_NAME_KEY_LEN);
              if (accessor) {
                grn_obj_get_value(ctx, accessor, id, &buf);
                grn_obj_unlink(ctx, accessor);
              }
            }
            grn_text_otoj(ctx, bulk, &buf, format);
          } else {
            grn_text_lltoa(ctx, bulk, id);
          }
        }
      }
    }
    break;
  case GRN_UVECTOR:
    if (format) {
      if (format->flags & GRN_OBJ_FORMAT_WITH_WEIGHT) {
        uint32_t i;
        uint32_t n;
        grn_obj *domain;

        n = grn_uvector_size(ctx, obj);
        domain = grn_ctx_at(ctx, obj->header.domain);
        GRN_TEXT_PUTS(ctx, bulk, "{");
        for (i = 0; i < n; i++) {
          grn_id id;
          float weight;

          if (i > 0) {
            GRN_TEXT_PUTC(ctx, bulk, ',');
          }
          id = grn_uvector_get_element_record(ctx, obj, i, &weight);
          if (domain) {
            if (domain->header.type == GRN_TABLE_NO_KEY) {
              GRN_TEXT_PUTC(ctx, bulk, '"');
              grn_text_ulltoa(ctx, bulk, id);
              GRN_TEXT_PUTC(ctx, bulk, '"');
            } else {
              GRN_BULK_REWIND(&buf);
              grn_table_get_key2(ctx, domain, id, &buf);
              grn_text_otoj(ctx, bulk, &buf, NULL);
            }
          } else {
            GRN_TEXT_PUTC(ctx, bulk, '"');
            grn_text_ulltoa(ctx, bulk, id);
            GRN_TEXT_PUTC(ctx, bulk, '"');
          }
          GRN_TEXT_PUTC(ctx, bulk, ':');
          if (format->flags & GRN_OBJ_FORMAT_WEIGHT_FLOAT32) {
            grn_text_f32toa(ctx, bulk, weight);
          } else {
            grn_text_ulltoa(ctx, bulk, (uint64_t)weight);
          }
        }
        GRN_TEXT_PUTS(ctx, bulk, "}");
      } else {
        /* TODO: Does we still need this code? If we don't need this, we should
           remove this. */
        int i;
        grn_id *v = (grn_id *)GRN_BULK_HEAD(obj),
               *ve = (grn_id *)GRN_BULK_CURR(obj);
        size_t j;
        size_t ncolumns = GRN_PTR_VECTOR_SIZE(&format->columns);
        GRN_TEXT_PUTS(ctx, bulk, "[[");
        grn_text_itoa(ctx, bulk, (int)(ve - v));
        GRN_TEXT_PUTC(ctx, bulk, ']');
        if (v < ve) {
          if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
            GRN_TEXT_PUTS(ctx, bulk, ",[");
            for (j = 0; j < ncolumns; j++) {
              grn_obj *column = GRN_PTR_VALUE_AT(&format->columns, j);
              grn_id range_id;
              if (j) {
                GRN_TEXT_PUTC(ctx, bulk, ',');
              }
              GRN_TEXT_PUTS(ctx, bulk, "[");
              GRN_BULK_REWIND(&buf);
              grn_column_name_(ctx, column, &buf);
              grn_text_otoj(ctx, bulk, &buf, NULL);
              GRN_TEXT_PUTC(ctx, bulk, ',');
              /* column range */
              range_id = grn_obj_get_range(ctx, column);
              if (range_id == GRN_ID_NIL) {
                GRN_TEXT_PUTS(ctx, bulk, "null");
              } else {
                int name_len;
                grn_obj *range_obj;
                char name_buf[GRN_TABLE_MAX_KEY_SIZE];

                range_obj = grn_ctx_at(ctx, range_id);
                name_len = grn_obj_name(ctx,
                                        range_obj,
                                        name_buf,
                                        GRN_TABLE_MAX_KEY_SIZE);
                GRN_BULK_REWIND(&buf);
                GRN_TEXT_PUT(ctx, &buf, name_buf, name_len);
                grn_text_otoj(ctx, bulk, &buf, NULL);
              }
              GRN_TEXT_PUTS(ctx, bulk, "]");
            }
            GRN_TEXT_PUTC(ctx, bulk, ']');
          }
          for (i = 0;; i++) {
            GRN_TEXT_PUTS(ctx, bulk, ",[");
            for (j = 0; j < ncolumns; j++) {
              grn_obj *column = GRN_PTR_VALUE_AT(&format->columns, j);
              if (j) {
                GRN_TEXT_PUTC(ctx, bulk, ',');
              }
              GRN_BULK_REWIND(&buf);
              grn_obj_get_value(ctx, column, *v, &buf);
              grn_text_otoj(ctx, bulk, &buf, NULL);
            }
            GRN_TEXT_PUTC(ctx, bulk, ']');
            v++;
            if (v < ve) {
              GRN_TEXT_PUTC(ctx, bulk, ',');
            } else {
              break;
            }
          }
        }
        GRN_TEXT_PUTC(ctx, bulk, ']');
      }
    } else {
      grn_obj *range = grn_ctx_at(ctx, obj->header.domain);
      if (range && range->header.type == GRN_TYPE) {
        grn_id value_size = ((struct _grn_type *)range)->obj.range;
        char *v = (char *)GRN_BULK_HEAD(obj), *ve = (char *)GRN_BULK_CURR(obj);
        GRN_TEXT_PUTC(ctx, bulk, '[');
        if (v < ve) {
          for (;;) {
            grn_obj value;
            GRN_OBJ_INIT(&value, GRN_BULK, 0, obj->header.domain);
            grn_bulk_write_from(ctx, &value, v, 0, value_size);
            grn_text_otoj(ctx, bulk, &value, NULL);

            v += value_size;
            if (v < ve) {
              GRN_TEXT_PUTC(ctx, bulk, ',');
            } else {
              break;
            }
          }
        }
        GRN_TEXT_PUTC(ctx, bulk, ']');
      } else {
        grn_id *v = (grn_id *)GRN_BULK_HEAD(obj),
               *ve = (grn_id *)GRN_BULK_CURR(obj);
        GRN_TEXT_PUTC(ctx, bulk, '[');
        if (v < ve) {
          for (;;) {
            if (range && range->header.type != GRN_TABLE_NO_KEY) {
              grn_obj key;
              GRN_OBJ_INIT(&key, GRN_BULK, 0, range->header.domain);
              grn_table_get_key2(ctx, range, *v, &key);
              grn_text_otoj(ctx, bulk, &key, NULL);
              GRN_OBJ_FIN(ctx, &key);
            } else {
              grn_text_lltoa(ctx, bulk, *v);
            }
            v++;
            if (v < ve) {
              GRN_TEXT_PUTC(ctx, bulk, ',');
            } else {
              break;
            }
          }
        }
        GRN_TEXT_PUTC(ctx, bulk, ']');
      }
    }
    break;
  case GRN_VECTOR:
    if (obj->header.domain == GRN_DB_VOID) {
      ERR(GRN_INVALID_ARGUMENT, "invalid obj->header.domain");
    } else {
      unsigned int i, n;
      grn_obj value;
      grn_obj weight;
      bool with_weight = false;
      bool is_weight_float32 = false;

      GRN_VOID_INIT(&value);
      if (format) {
        with_weight = (format->flags & GRN_OBJ_FORMAT_WITH_WEIGHT);
        is_weight_float32 = (format->flags & GRN_OBJ_FORMAT_WEIGHT_FLOAT32);
      }
      if (is_weight_float32) {
        GRN_FLOAT32_INIT(&weight, 0);
      } else {
        GRN_UINT32_INIT(&weight, 0);
      }
      n = grn_vector_size(ctx, obj);
      if (with_weight) {
        GRN_TEXT_PUTC(ctx, bulk, '{');
      } else {
        GRN_TEXT_PUTC(ctx, bulk, '[');
      }
      for (i = 0; i < n; i++) {
        const char *value_raw;
        float weight_raw;
        unsigned int length;
        grn_id domain;
        if (i) {
          GRN_TEXT_PUTC(ctx, bulk, ',');
        }

        length = grn_vector_get_element_float(ctx,
                                              obj,
                                              i,
                                              &value_raw,
                                              &weight_raw,
                                              &domain);
        if (domain != GRN_DB_VOID) {
          grn_obj_reinit(ctx, &value, domain, 0);
        } else {
          grn_obj_reinit(ctx, &value, obj->header.domain, 0);
        }
        grn_bulk_write(ctx, &value, value_raw, length);
        grn_text_otoj(ctx, bulk, &value, NULL);
        if (with_weight) {
          GRN_TEXT_PUTC(ctx, bulk, ':');
          if (is_weight_float32) {
            GRN_FLOAT32_SET(ctx, &weight, weight_raw);
          } else {
            GRN_UINT32_SET(ctx, &weight, weight_raw);
          }
          grn_text_otoj(ctx, bulk, &weight, NULL);
        }
      }
      if (with_weight) {
        GRN_TEXT_PUTC(ctx, bulk, '}');
      } else {
        GRN_TEXT_PUTC(ctx, bulk, ']');
      }
      GRN_OBJ_FIN(ctx, &value);
      GRN_OBJ_FIN(ctx, &weight);
    }
    break;
  case GRN_PVECTOR:
    if (format) {
      ERR(GRN_FUNCTION_NOT_IMPLEMENTED,
          "cannot print GRN_PVECTOR using grn_obj_format");
    } else {
      GRN_TEXT_PUTC(ctx, bulk, '[');
      size_t n = GRN_PTR_VECTOR_SIZE(obj);
      size_t i;
      for (i = 0; i < n; i++) {
        grn_obj *value;

        if (i) {
          GRN_TEXT_PUTC(ctx, bulk, ',');
        }
        value = GRN_PTR_VALUE_AT(obj, i);
        grn_text_otoj(ctx, bulk, value, NULL);
      }
      GRN_TEXT_PUTC(ctx, bulk, ']');
    }
    break;
  case GRN_TABLE_HASH_KEY:
  case GRN_TABLE_PAT_KEY:
  case GRN_TABLE_NO_KEY:
    if (format) {
      int i;
      size_t ncolumns = GRN_PTR_VECTOR_SIZE(&format->columns);
      grn_table_cursor *tc = grn_table_cursor_open(ctx,
                                                   obj,
                                                   NULL,
                                                   0,
                                                   NULL,
                                                   0,
                                                   format->offset,
                                                   format->limit,
                                                   GRN_CURSOR_ASCENDING);
      if (!tc) {
        ERRCLR(ctx);
      }
      GRN_TEXT_PUTS(ctx, bulk, "[[");
      grn_text_itoa(ctx, bulk, format->nhits);
      GRN_TEXT_PUTC(ctx, bulk, ']');
      if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
        GRN_TEXT_PUTS(ctx, bulk, ",[");
        size_t j;
        for (j = 0; j < ncolumns; j++) {
          grn_obj *column = GRN_PTR_VALUE_AT(&format->columns, j);
          grn_id range_id;
          if (j) {
            GRN_TEXT_PUTC(ctx, bulk, ',');
          }
          GRN_TEXT_PUTS(ctx, bulk, "[");
          GRN_BULK_REWIND(&buf);
          grn_column_name_(ctx, column, &buf);
          grn_text_otoj(ctx, bulk, &buf, NULL);
          GRN_TEXT_PUTC(ctx, bulk, ',');
          /* column range */
          range_id = grn_obj_get_range(ctx, column);
          if (range_id == GRN_ID_NIL) {
            GRN_TEXT_PUTS(ctx, bulk, "null");
          } else {
            int name_len;
            grn_obj *range_obj;
            char name_buf[GRN_TABLE_MAX_KEY_SIZE];

            range_obj = grn_ctx_at(ctx, range_id);
            name_len =
              grn_obj_name(ctx, range_obj, name_buf, GRN_TABLE_MAX_KEY_SIZE);
            GRN_BULK_REWIND(&buf);
            GRN_TEXT_PUT(ctx, &buf, name_buf, name_len);
            grn_text_otoj(ctx, bulk, &buf, NULL);
          }
          GRN_TEXT_PUTS(ctx, bulk, "]");
        }
        GRN_TEXT_PUTC(ctx, bulk, ']');
      }
      if (tc) {
        grn_id id;
        for (i = 0; (id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL; i++) {
          GRN_TEXT_PUTS(ctx, bulk, ",[");
          size_t j;
          for (j = 0; j < ncolumns; j++) {
            grn_obj *column = GRN_PTR_VALUE_AT(&format->columns, j);
            if (j) {
              GRN_TEXT_PUTC(ctx, bulk, ',');
            }
            grn_text_atoj(ctx, bulk, column, id);
          }
          GRN_TEXT_PUTC(ctx, bulk, ']');
        }
        grn_table_cursor_close(ctx, tc);
      }
      GRN_TEXT_PUTC(ctx, bulk, ']');
    } else {
      int i;
      grn_id id;
      grn_obj *column =
        grn_obj_column(ctx, obj, GRN_COLUMN_NAME_KEY, GRN_COLUMN_NAME_KEY_LEN);
      grn_table_cursor *tc = grn_table_cursor_open(ctx,
                                                   obj,
                                                   NULL,
                                                   0,
                                                   NULL,
                                                   0,
                                                   0,
                                                   -1,
                                                   GRN_CURSOR_ASCENDING);
      GRN_TEXT_PUTC(ctx, bulk, '[');
      if (tc) {
        for (i = 0; (id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL; i++) {
          if (i) {
            GRN_TEXT_PUTC(ctx, bulk, ',');
          }
          GRN_BULK_REWIND(&buf);
          grn_obj_get_value(ctx, column, id, &buf);
          grn_text_esc(ctx, bulk, GRN_BULK_HEAD(&buf), GRN_BULK_VSIZE(&buf));
        }
        grn_table_cursor_close(ctx, tc);
      }
      GRN_TEXT_PUTC(ctx, bulk, ']');
      grn_obj_unlink(ctx, column);
    }
    break;
  }
  grn_obj_close(ctx, &buf);
  return GRN_SUCCESS;
}

const char *
grn_text_urldec(
  grn_ctx *ctx, grn_obj *buf, const char *p, const char *e, char d)
{
  while (p < e) {
    if (*p == d) {
      p++;
      break;
    } else if (*p == '%' && p + 3 <= e) {
      const char *r;
      unsigned int c = grn_htoui(p + 1, p + 3, &r);
      if (p + 3 == r) {
        GRN_TEXT_PUTC(ctx, buf, (char)c);
        p += 3;
      } else {
        GRN_LOG(ctx, GRN_LOG_NOTICE, "invalid %% sequence (%c%c)", p[1], p[2]);
        GRN_TEXT_PUTC(ctx, buf, '%');
        p += 1;
      }
    } else {
      GRN_TEXT_PUTC(ctx, buf, *p);
      p++;
    }
  }
  return p;
}

const char *
grn_text_cgidec(grn_ctx *ctx,
                grn_obj *buf,
                const char *p,
                const char *e,
                const char *delimiters)
{
  while (p < e) {
    bool found_delimiter = false;
    const char *delimiter;
    for (delimiter = delimiters; *delimiter; delimiter++) {
      if (*p == *delimiter) {
        found_delimiter = true;
        break;
      }
    }
    if (found_delimiter) {
      p++;
      break;
    }

    if (*p == '+') {
      GRN_TEXT_PUTC(ctx, buf, ' ');
      p++;
    } else if (*p == '%' && p + 3 <= e) {
      const char *r;
      unsigned int c = grn_htoui(p + 1, p + 3, &r);
      if (p + 3 == r) {
        GRN_TEXT_PUTC(ctx, buf, (char)c);
        p += 3;
      } else {
        GRN_LOG(ctx, GRN_LOG_NOTICE, "invalid %% sequence (%c%c)", p[1], p[2]);
        GRN_TEXT_PUTC(ctx, buf, '%');
        p += 1;
      }
    } else {
      GRN_TEXT_PUTC(ctx, buf, *p);
      p++;
    }
  }
  return p;
}

void
grn_str_url_path_normalize(
  grn_ctx *ctx, const char *path, size_t path_len, char *buf, size_t buf_len)
{
  char *b = buf, *be = buf + buf_len - 1;
  const char *p = path, *pe = path + path_len, *pc;

  if (buf_len < 2) {
    return;
  }

  while (p < pe) {
    for (pc = p; pc < pe && *pc != '/'; pc++) {
    }
    if (*p == '.') {
      if (pc == p + 2 && *(p + 1) == '.') {
        /* '..' */
        if (b - buf >= 2) {
          for (b -= 2; *b != '/' && b >= buf; b--) {
          }
        }
        if (*b == '/') {
          b++;
          ERR(GRN_INVALID_ARGUMENT, "parent path doesn't exist.");
        }
        p = pc + 1;
        continue;
      } else if (pc == p + 1) {
        /* '.' */
        p = pc + 1;
        continue;
      }
    }
    if (be - b >= pc - p) {
      grn_memcpy(b, p, (size_t)(pc - p));
      b += pc - p;
      p = pc;
      if (p < pe && *pc == '/' && be > b) {
        *b++ = '/';
        p++;
      }
    }
  }
  *b = '\0';
}

bool
grn_bulk_is_zero(grn_ctx *ctx, grn_obj *obj)
{
  const char *v = GRN_BULK_HEAD(obj);
  size_t s = GRN_BULK_VSIZE(obj);
  for (; s; s--, v++) {
    if (*v) {
      return false;
    }
  }
  return true;
}

char *
grn_bulk_detach(grn_ctx *ctx, grn_obj *bulk)
{
  if (GRN_BULK_VSIZE(bulk) == 0) {
    return NULL;
  }

  char *data;
  if (GRN_BULK_OUTP(bulk)) {
    data = GRN_BULK_HEAD(bulk);
    bulk->header.impl_flags &= (uint8_t)(~GRN_OBJ_OUTPLACE);
  } else {
    data = GRN_MALLOC(GRN_BULK_VSIZE(bulk));
    if (!data) {
      return NULL;
    }
    memcpy(data, GRN_BULK_HEAD(bulk), GRN_BULK_VSIZE(bulk));
  }
  GRN_BULK_REWIND(bulk);
  return data;
}
