/*************************************************************************************************
 * Scripting language extension of Tokyo Tyrant
 *                                                      Copyright (C) 2006-2010 Mikio Hirabayashi
 * This file is part of Tokyo Tyrant.
 * Tokyo Tyrant 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 any later version.  Tokyo Tyrant 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 Tokyo
 * Tyrant; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "scrext.h"



/*************************************************************************************************
 * by default
 *************************************************************************************************/


#if defined(TTNOEXT)


typedef struct _SCREXT {                 // type of structure of the script extension
  struct _SCREXT **screxts;              // script extension objects
  int thnum;                             // number of native threads
  int thid;                              // thread ID
  char *path;                            // path of the initializing script
  TCADB *adb;                            // abstract database object
  TCULOG *ulog;                          // update log object
  uint32_t sid;                          // server ID
  TCMDB *stash;                          // global stash object
  TCMDB *lock;                           // global lock object
  void (*logger)(int, const char *, void *);  // logging function
  void *logopq;                          // opaque pointer for the logging function
  bool term;                             // terminate flag
} SCREXT;


/* Initialize the global scripting language extension. */
void *scrextnew(void **screxts, int thnum, int thid, const char *path,
                TCADB *adb, TCULOG *ulog, uint32_t sid, TCMDB *stash, TCMDB *lock,
                void (*logger)(int, const char *, void *), void *logopq){
  SCREXT *scr = tcmalloc(sizeof(*scr));
  scr->screxts = (SCREXT **)screxts;
  scr->thnum = thnum;
  scr->thid = thid;
  scr->path = tcstrdup(path);
  scr->adb = adb;
  scr->ulog = ulog;
  scr->sid = sid;
  scr->stash = stash;
  scr->lock = lock;
  scr->logger = logger;
  scr->logopq = logopq;
  scr->term = false;
  return scr;
}


/* Destroy the global scripting language extension. */
bool scrextdel(void *scr){
  SCREXT *myscr = scr;
  tcfree(myscr->path);
  tcfree(myscr);
  return true;
}


/* Call a method of the scripting language extension. */
char *scrextcallmethod(void *scr, const char *name,
                       const void *kbuf, int ksiz, const void *vbuf, int vsiz, int *sp){
  SCREXT *myscr = scr;
  if(!strcmp(name, "put")){
    if(!tculogadbput(myscr->ulog, myscr->sid, 0, myscr->adb, kbuf, ksiz, vbuf, vsiz))
      return NULL;
    char *msg = tcstrdup("ok");
    *sp = strlen(msg);
    return msg;
  } else if(!strcmp(name, "putkeep")){
    if(!tculogadbputkeep(myscr->ulog, myscr->sid, 0, myscr->adb, kbuf, ksiz, vbuf, vsiz))
      return NULL;
    char *msg = tcstrdup("ok");
    *sp = strlen(msg);
    return msg;
  } else if(!strcmp(name, "putcat")){
    if(!tculogadbputcat(myscr->ulog, myscr->sid, 0, myscr->adb, kbuf, ksiz, vbuf, vsiz))
      return NULL;
    char *msg = tcstrdup("ok");
    *sp = strlen(msg);
    return msg;
  } else if(!strcmp(name, "out")){
    if(!tculogadbout(myscr->ulog, myscr->sid, 0, myscr->adb, kbuf, ksiz)) return NULL;
    char *msg = tcstrdup("ok");
    *sp = strlen(msg);
    return msg;
  } else if(!strcmp(name, "get")){
    return tcadbget(myscr->adb, kbuf, ksiz, sp);
  } else if(!strcmp(name, "log")){
    char *msg = tcmemdup(kbuf, ksiz);
    myscr->logger(TTLOGINFO, msg, myscr->logopq);
    tcfree(msg);
    msg = tcstrdup("ok");
    *sp = strlen(msg);
    return msg;
  }
  int psiz = strlen(myscr->path);
  int nsiz = strlen(name);
  char *msg = tcmalloc(psiz + nsiz + ksiz + vsiz + 4);
  char *wp = msg;
  memcpy(wp, myscr->path, psiz);
  wp += psiz;
  *(wp++) = ':';
  memcpy(wp, name, nsiz);
  wp += nsiz;
  *(wp++) = ':';
  memcpy(wp, kbuf, ksiz);
  wp += ksiz;
  *(wp++) = ':';
  memcpy(wp, vbuf, vsiz);
  wp += vsiz;
  *sp = wp - msg;
  return msg;
}


/* Send the terminate signal to the scripting language extension */
bool scrextkill(void *scr){
  SCREXT *myscr = scr;
  myscr->term = true;
  return true;
}


#endif



/*************************************************************************************************
 * for Lua
 *************************************************************************************************/


#if defined(TTLUAEXT)


#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#define SERVVAR      "_serv_"            // global variable name for server resources
#define ITERVAR      "_iter_"            // global variable name for iterator
#define MRMAPVAR     "_mrmap_"           // global variable name for mapreduce mapper
#define MRREDVAR     "_mrred_"           // global variable name for mapreduce reducer
#define MRPOOLVAR    "_mrpool_"          // global variable name for mapreduce pool

typedef struct {                         // type of structure of the script extension
  lua_State *lua;                        // Lua environment
  int thnum;                             // number of native threads
  int thid;                              // thread ID
} SCREXT;

typedef struct {                         // type of structure of the server data
  SCREXT **screxts;                      // script extension objects
  int thnum;                             // number of native threads
  int thid;                              // thread ID
  TCADB *adb;                            // abstract database object
  TCULOG *ulog;                          // update log object
  uint32_t sid;                          // server ID
  TCMDB *stash;                          // global stash object
  TCMDB *lock;                           // global lock object
  pthread_mutex_t *lcks;                 // mutex for user locks
  int lcknum;                            // number of user locks
  void (*logger)(int, const char *, void *);  // logging function
  void *logopq;                          // opaque pointer for the logging function
  bool term;                             // terminate flag
} SERV;


/* private function prototypes */
static void reporterror(lua_State *lua);
static bool iterrec(const void *kbuf, int ksiz, const void *vbuf, int vsiz, lua_State *lua);
static int serv_eval(lua_State *lua);
static int serv_log(lua_State *lua);
static int serv_put(lua_State *lua);
static int serv_putkeep(lua_State *lua);
static int serv_putcat(lua_State *lua);
static int serv_out(lua_State *lua);
static int serv_get(lua_State *lua);
static int serv_vsiz(lua_State *lua);
static int serv_iterinit(lua_State *lua);
static int serv_iternext(lua_State *lua);
static int serv_fwmkeys(lua_State *lua);
static int serv_addint(lua_State *lua);
static int serv_adddouble(lua_State *lua);
static int serv_vanish(lua_State *lua);
static int serv_rnum(lua_State *lua);
static int serv_size(lua_State *lua);
static int serv_misc(lua_State *lua);
static int serv_foreach(lua_State *lua);
static int serv_mapreduce(lua_State *lua);
static int serv_mapreducemapemit(lua_State *lua);
static int serv_stashput(lua_State *lua);
static int serv_stashputkeep(lua_State *lua);
static int serv_stashputcat(lua_State *lua);
static int serv_stashout(lua_State *lua);
static int serv_stashget(lua_State *lua);
static int serv_stashvanish(lua_State *lua);
static int serv_stashforeach(lua_State *lua);
static int serv_lock(lua_State *lua);
static int serv_unlock(lua_State *lua);
static int serv_pack(lua_State *lua);
static int serv_unpack(lua_State *lua);
static int serv_split(lua_State *lua);
static int serv_codec(lua_State *lua);
static int serv_hash(lua_State *lua);
static int serv_bit(lua_State *lua);
static int serv_strstr(lua_State *lua);
static int serv_regex(lua_State *lua);
static int serv_ucs(lua_State *lua);
static int serv_dist(lua_State *lua);
static int serv_isect(lua_State *lua);
static int serv_union(lua_State *lua);
static int serv_time(lua_State *lua);
static int serv_sleep(lua_State *lua);
static int serv_stat(lua_State *lua);
static int serv_glob(lua_State *lua);
static int serv_remove(lua_State *lua);
static int serv_mkdir(lua_State *lua);


/* Initialize the global scripting language extension. */
void *scrextnew(void **screxts, int thnum, int thid, const char *path,
                TCADB *adb, TCULOG *ulog, uint32_t sid, TCMDB *stash, TCMDB *lock,
                void (*logger)(int, const char *, void *), void *logopq){
  char *ibuf;
  int isiz;
  if(*path == '@'){
    ibuf = tcstrdup(path + 1);
    isiz = strlen(ibuf);
  } else if(*path != '\0'){
    ibuf = tcreadfile(path, 0, &isiz);
  } else {
    ibuf = tcmemdup("", 0);
    isiz = 0;
  }
  if(!ibuf) return NULL;
  lua_State *lua = luaL_newstate();
  if(!lua){
    tcfree(ibuf);
    return NULL;
  }
  luaL_openlibs(lua);
  lua_settop(lua, 0);
  SERV *serv = lua_newuserdata(lua, sizeof(*serv));
  serv->screxts = (SCREXT **)screxts;
  serv->thnum = thnum;
  serv->thid = thid;
  serv->adb = adb;
  serv->ulog = ulog;
  serv->sid = sid;
  serv->stash = stash;
  serv->lock = lock;
  serv->logger = logger;
  serv->logopq = logopq;
  serv->term = false;
  lua_setglobal(lua, SERVVAR);
  lua_register(lua, "_eval", serv_eval);
  lua_register(lua, "_log", serv_log);
  lua_register(lua, "_put", serv_put);
  lua_register(lua, "_putkeep", serv_putkeep);
  lua_register(lua, "_putcat", serv_putcat);
  lua_register(lua, "_out", serv_out);
  lua_register(lua, "_get", serv_get);
  lua_register(lua, "_vsiz", serv_vsiz);
  lua_register(lua, "_iterinit", serv_iterinit);
  lua_register(lua, "_iternext", serv_iternext);
  lua_register(lua, "_fwmkeys", serv_fwmkeys);
  lua_register(lua, "_addint", serv_addint);
  lua_register(lua, "_adddouble", serv_adddouble);
  lua_register(lua, "_vanish", serv_vanish);
  lua_register(lua, "_rnum", serv_rnum);
  lua_register(lua, "_size", serv_size);
  lua_register(lua, "_misc", serv_misc);
  lua_register(lua, "_foreach", serv_foreach);
  lua_register(lua, "_mapreduce", serv_mapreduce);
  lua_register(lua, "_stashput", serv_stashput);
  lua_register(lua, "_stashputkeep", serv_stashputkeep);
  lua_register(lua, "_stashputcat", serv_stashputcat);
  lua_register(lua, "_stashout", serv_stashout);
  lua_register(lua, "_stashget", serv_stashget);
  lua_register(lua, "_stashvanish", serv_stashvanish);
  lua_register(lua, "_stashforeach", serv_stashforeach);
  lua_register(lua, "_lock", serv_lock);
  lua_register(lua, "_unlock", serv_unlock);
  lua_register(lua, "_pack", serv_pack);
  lua_register(lua, "_unpack", serv_unpack);
  lua_register(lua, "_split", serv_split);
  lua_register(lua, "_codec", serv_codec);
  lua_register(lua, "_hash", serv_hash);
  lua_register(lua, "_bit", serv_bit);
  lua_register(lua, "_strstr", serv_strstr);
  lua_register(lua, "_regex", serv_regex);
  lua_register(lua, "_ucs", serv_ucs);
  lua_register(lua, "_dist", serv_dist);
  lua_register(lua, "_isect", serv_isect);
  lua_register(lua, "_union", serv_union);
  lua_register(lua, "_time", serv_time);
  lua_register(lua, "_sleep", serv_sleep);
  lua_register(lua, "_stat", serv_stat);
  lua_register(lua, "_glob", serv_glob);
  lua_register(lua, "_remove", serv_remove);
  lua_register(lua, "_mkdir", serv_mkdir);
  lua_pushstring(lua, ttversion);
  lua_setglobal(lua, "_version");
  lua_pushinteger(lua, getpid());
  lua_setglobal(lua, "_pid");
  lua_pushinteger(lua, sid);
  lua_setglobal(lua, "_sid");
  lua_pushinteger(lua, thnum);
  lua_setglobal(lua, "_thnum");
  lua_pushinteger(lua, thid + 1);
  lua_setglobal(lua, "_thid");
  lua_settop(lua, 0);
  if(luaL_loadstring(lua, ibuf) != 0 || lua_pcall(lua, 0, 0, 0) != 0) reporterror(lua);
  tcfree(ibuf);
  if(thid == 0){
    lua_getglobal(lua, "_begin");
    if(lua_isfunction(lua, -1) && lua_pcall(lua, 0, 0, 0) != 0) reporterror(lua);
  }
  lua_settop(lua, 0);
  SCREXT *scr = tcmalloc(sizeof(*scr));
  scr->lua = lua;
  scr->thnum = thnum;
  scr->thid = thid;
  return scr;
}


/* Destroy the global scripting language extension. */
bool scrextdel(void *scr){
  SCREXT *myscr = scr;
  lua_State *lua = myscr->lua;
  if(myscr->thid == 0){
    lua_getglobal(lua, "_end");
    if(lua_isfunction(lua, -1) && lua_pcall(lua, 0, 0, 0) != 0) reporterror(lua);
  }
  lua_close(lua);
  tcfree(scr);
  return true;
}


/* Call a method of the scripting language extension. */
char *scrextcallmethod(void *scr, const char *name,
                       const void *kbuf, int ksiz, const void *vbuf, int vsiz, int *sp){
  SCREXT *myscr = scr;
  lua_State *lua = myscr->lua;
  if(*name == '_') return NULL;
  lua_getglobal(lua, name);
  if(lua_gettop(lua) != 1 || !lua_isfunction(lua, 1)){
    lua_settop(lua, 0);
    return NULL;
  }
  lua_pushlstring(lua, kbuf, ksiz);
  lua_pushlstring(lua, vbuf, vsiz);
  if(lua_pcall(lua, 2, 1, 0) != 0){
    reporterror(lua);
    lua_settop(lua, 0);
    return NULL;
  }
  if(lua_gettop(lua) < 1) return NULL;
  const char *rbuf = NULL;
  size_t rsiz;
  switch(lua_type(lua, 1)){
    case LUA_TNUMBER:
    case LUA_TSTRING:
      rbuf = lua_tolstring(lua, 1, &rsiz);
      break;
    case LUA_TBOOLEAN:
      if(lua_toboolean(lua, 1)){
        rbuf = "true";
        rsiz = strlen(rbuf);
      }
      break;
    case LUA_TTABLE:
      if(lua_objlen(lua, 1) > 0){
        lua_rawgeti(lua, 1, 1);
        switch(lua_type(lua, -1)){
          case LUA_TNUMBER:
          case LUA_TSTRING:
            rbuf = lua_tolstring(lua, -1, &rsiz);
            break;
          case LUA_TBOOLEAN:
            if(lua_toboolean(lua, -1)){
              rbuf = "true";
              rsiz = strlen(rbuf);
            }
            break;
        }
        lua_pop(lua, 1);
      }
      break;
  }
  if(!rbuf){
    lua_settop(lua, 0);
    return NULL;
  }
  char *rv = tcmemdup(rbuf, rsiz);
  *sp = rsiz;
  lua_settop(lua, 0);
  return rv;
}


/* Send the terminate signal to the scripting language extension */
bool scrextkill(void *scr){
  SCREXT *myscr = scr;
  lua_State *lua = myscr->lua;
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  serv->term = true;
  return true;
}


/* report an error of Lua program */
static void reporterror(lua_State *lua){
  int argc = lua_gettop(lua);
  char *msg = tcsprintf("Lua error: %s", argc > 0 ? lua_tostring(lua, argc) : "unknown");
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  serv->logger(TTLOGERROR, msg, serv->logopq);
  tcfree(msg);
}


/* call function for each record */
static bool iterrec(const void *kbuf, int ksiz, const void *vbuf, int vsiz, lua_State *lua){
  int top = lua_gettop(lua);
  lua_getglobal(lua, ITERVAR);
  lua_pushlstring(lua, kbuf, ksiz);
  lua_pushlstring(lua, vbuf, vsiz);
  bool err = false;
  if(lua_pcall(lua, 2, 1, 0) == 0){
    if(lua_gettop(lua) <= top || !lua_toboolean(lua, -1)) err = true;
  } else {
    reporterror(lua);
    err = true;
  }
  lua_settop(lua, top);
  return !err;
}


/* call function to map records for mapreduce */
static bool maprec(void *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                   lua_State *lua){
  lua_pushlightuserdata(lua, map);
  lua_setglobal(lua, MRPOOLVAR);
  int top = lua_gettop(lua);
  lua_getglobal(lua, MRMAPVAR);
  lua_pushlstring(lua, kbuf, ksiz);
  lua_pushlstring(lua, vbuf, vsiz);
  lua_pushcfunction(lua, serv_mapreducemapemit);
  bool err = false;
  if(lua_pcall(lua, 3, 1, 0) == 0){
    if(lua_gettop(lua) < 1 && !lua_toboolean(lua, 1)) err = true;
  } else {
    reporterror(lua);
    err = true;
  }
  lua_settop(lua, top);
  return !err;
}


/* for _eval function */
static int serv_eval(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_eval: invalid arguments");
    lua_error(lua);
  }
  const char *expr = lua_tostring(lua, 1);
  if(!expr){
    lua_pushstring(lua, "_eval: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  SCREXT **screxts = serv->screxts;
  int thnum = serv->thnum;
  bool err = false;
  for(int i = 0; i < thnum; i++){
    if(!screxts[i]){
      lua_pushstring(lua, "_eval: not ready");
      lua_error(lua);
    }
  }
  for(int i = 0; i < thnum; i++){
    lua_State *elua = screxts[i]->lua;
    if(luaL_loadstring(elua, expr) != 0 || lua_pcall(elua, 0, 0, 0) != 0) reporterror(elua);
  }
  lua_settop(lua, 0);
  lua_pushboolean(lua, !err);
  return 1;
}


/* for _log function */
static int serv_log(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 1){
    lua_pushstring(lua, "_log: invalid arguments");
    lua_error(lua);
  }
  const char *msg = lua_tostring(lua, 1);
  if(!msg){
    lua_pushstring(lua, "_log: invalid arguments");
    lua_error(lua);
  }
  int level = TTLOGINFO;
  if(argc > 1) level = lua_tointeger(lua, 2);
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  serv->logger(level, msg, serv->logopq);
  lua_settop(lua, 0);
  return 0;
}


/* for _put function */
static int serv_put(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_put: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  size_t vsiz;
  const char *vbuf = lua_tolstring(lua, 2, &vsiz);
  if(!kbuf || !vbuf){
    lua_pushstring(lua, "_put: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tculogadbput(serv->ulog, serv->sid, 0, serv->adb, kbuf, ksiz, vbuf, vsiz);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _putkeep function */
static int serv_putkeep(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_putkeep: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  size_t vsiz;
  const char *vbuf = lua_tolstring(lua, 2, &vsiz);
  if(!kbuf || !vbuf){
    lua_pushstring(lua, "_putkeep: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tculogadbputkeep(serv->ulog, serv->sid, 0, serv->adb, kbuf, ksiz, vbuf, vsiz);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _putcat function */
static int serv_putcat(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_putcat: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  size_t vsiz;
  const char *vbuf = lua_tolstring(lua, 2, &vsiz);
  if(!kbuf || !vbuf){
    lua_pushstring(lua, "_putcat: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tculogadbputcat(serv->ulog, serv->sid, 0, serv->adb, kbuf, ksiz, vbuf, vsiz);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _putout function */
static int serv_out(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_out: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  if(!kbuf){
    lua_pushstring(lua, "_out: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tculogadbout(serv->ulog, serv->sid, 0, serv->adb, kbuf, ksiz);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _get function */
static int serv_get(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_get: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  if(!kbuf){
    lua_pushstring(lua, "_get: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  int vsiz;
  char *vbuf = tcadbget(serv->adb, kbuf, ksiz, &vsiz);
  lua_settop(lua, 0);
  if(vbuf){
    lua_pushlstring(lua, vbuf, vsiz);
    tcfree(vbuf);
  } else {
    lua_pushnil(lua);
  }
  return 1;
}


/* for _vsiz function */
static int serv_vsiz(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_vsiz: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  if(!kbuf){
    lua_pushstring(lua, "_vsiz: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  int vsiz = tcadbvsiz(serv->adb, kbuf, ksiz);
  lua_settop(lua, 0);
  lua_pushnumber(lua, vsiz);
  return 1;
}


/* for _iterinit function */
static int serv_iterinit(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 0){
    lua_pushstring(lua, "_iterinit: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tcadbiterinit(serv->adb);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _iternext function */
static int serv_iternext(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 0){
    lua_pushstring(lua, "_iternext: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  int vsiz;
  char *vbuf = tcadbiternext(serv->adb, &vsiz);
  lua_settop(lua, 0);
  if(vbuf){
    lua_pushlstring(lua, vbuf, vsiz);
    tcfree(vbuf);
  } else {
    lua_pushnil(lua);
  }
  return 1;
}


/* for _fwmkeys function */
static int serv_fwmkeys(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 1){
    lua_pushstring(lua, "_fwmkeys: invalid arguments");
    lua_error(lua);
  }
  size_t psiz;
  const char *pbuf = lua_tolstring(lua, 1, &psiz);
  if(!pbuf){
    lua_pushstring(lua, "_fwmkeys: invalid arguments");
    lua_error(lua);
  }
  int max = argc > 1 && lua_isnumber(lua, 2) ? lua_tonumber(lua, 2) : -1;
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  TCLIST *keys = tcadbfwmkeys(serv->adb, pbuf, psiz, max);
  lua_settop(lua, 0);
  int knum = tclistnum(keys);
  lua_createtable(lua, knum, 0);
  for(int i = 0; i < knum; i++){
    int ksiz;
    const char *kbuf = tclistval(keys, i, &ksiz);
    lua_pushlstring(lua, kbuf, ksiz);
    lua_rawseti(lua, 1, i + 1);
  }
  tclistdel(keys);
  return 1;
}


/* for _addint function */
static int serv_addint(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_addint: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  int num = lua_tonumber(lua, 2);
  if(!kbuf || !lua_isnumber(lua, 2)){
    lua_pushstring(lua, "_addint: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  int rv = tculogadbaddint(serv->ulog, serv->sid, 0, serv->adb, kbuf, ksiz, num);
  lua_settop(lua, 0);
  if(rv == INT_MIN){
    lua_pushnil(lua);
  } else {
    lua_pushnumber(lua, rv);
  }
  return 1;
}


/* for _adddouble function */
static int serv_adddouble(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_adddouble: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  double num = lua_tonumber(lua, 2);
  if(!kbuf || !lua_isnumber(lua, 2)){
    lua_pushstring(lua, "_adddouble: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  double rv = tculogadbadddouble(serv->ulog, serv->sid, 0, serv->adb, kbuf, ksiz, num);
  lua_settop(lua, 0);
  if(isnan(rv)){
    lua_pushnil(lua);
  } else {
    lua_pushnumber(lua, rv);
  }
  return 1;
}


/* for _vanish function */
static int serv_vanish(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 0){
    lua_pushstring(lua, "_vanish: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tculogadbvanish(serv->ulog, serv->sid, 0, serv->adb);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _rnum function */
static int serv_rnum(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 0){
    lua_pushstring(lua, "_rnum: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  uint64_t rnum = tcadbrnum(serv->adb);
  lua_settop(lua, 0);
  lua_pushnumber(lua, rnum);
  return 1;
}


/* for _size function */
static int serv_size(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 0){
    lua_pushstring(lua, "_size: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  uint64_t size = tcadbsize(serv->adb);
  lua_settop(lua, 0);
  lua_pushnumber(lua, size);
  return 1;
}


/* for _misc function */
static int serv_misc(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 1){
    lua_pushstring(lua, "_misc: invalid arguments");
    lua_error(lua);
  }
  const char *name = lua_tostring(lua, 1);
  if(!name){
    lua_pushstring(lua, "_misc: invalid arguments");
    lua_error(lua);
  }
  bool ulog = true;
  if(*name == '$'){
    name++;
    ulog = false;
  }
  TCLIST *args = tclistnew();
  for(int i = 2; i <= argc; i++){
    const char *aptr;
    size_t asiz;
    int len;
    switch(lua_type(lua, i)){
      case LUA_TNUMBER:
      case LUA_TSTRING:
        aptr = lua_tolstring(lua, i, &asiz);
        tclistpush(args, aptr, asiz);
        break;
      case LUA_TTABLE:
        len = lua_objlen(lua, i);
        for(int j = 1; j <= len; j++){
          lua_rawgeti(lua, i, j);
          switch(lua_type(lua, -1)){
            case LUA_TNUMBER:
            case LUA_TSTRING:
              aptr = lua_tolstring(lua, -1, &asiz);
              tclistpush(args, aptr, asiz);
              break;
          }
          lua_pop(lua, 1);
        }
        break;
    }
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  TCLIST *res = ulog ? tculogadbmisc(serv->ulog, serv->sid, 0, serv->adb, name, args) :
    tcadbmisc(serv->adb, name, args);
  lua_settop(lua, 0);
  if(res){
    int rnum = tclistnum(res);
    lua_createtable(lua, rnum, 0);
    for(int i = 0; i < rnum; i++){
      int rsiz;
      const char *rbuf = tclistval(res, i, &rsiz);
      lua_pushlstring(lua, rbuf, rsiz);
      lua_rawseti(lua, 1, i + 1);
    }
    tclistdel(res);
  } else {
    lua_pushnil(lua);
  }
  tclistdel(args);
  return 1;
}


/* for _foreach function */
static int serv_foreach(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_foreach: invalid arguments");
    lua_error(lua);
  }
  if(!lua_isfunction(lua, 1)){
    lua_pushstring(lua, "_foreach: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  lua_pushvalue(lua, 1);
  lua_setglobal(lua, ITERVAR);
  bool err = false;
  if(!tcadbforeach(serv->adb, (TCITER)iterrec, lua)) err = true;
  lua_pushnil(lua);
  lua_setglobal(lua, ITERVAR);
  lua_settop(lua, 0);
  lua_pushboolean(lua, !err);
  return 1;
}


/* for _mapreduce function */
static int serv_mapreduce(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 2){
    lua_pushstring(lua, "_mapreduce: invalid arguments");
    lua_error(lua);
  }
  if(!lua_isfunction(lua, 1) || !lua_isfunction(lua, 2)){
    lua_pushstring(lua, "_mapreduce: invalid arguments");
    lua_error(lua);
  }
  lua_pushvalue(lua, 1);
  lua_setglobal(lua, MRMAPVAR);
  lua_pushvalue(lua, 2);
  lua_setglobal(lua, MRREDVAR);
  TCLIST *keys = NULL;
  if(argc > 2){
    const char *kbuf;
    size_t ksiz;
    int len;
    switch(lua_type(lua, 3)){
      case LUA_TNUMBER:
      case LUA_TSTRING:
        keys = tclistnew2(1);
        kbuf = lua_tolstring(lua, 3, &ksiz);
        tclistpush(keys, kbuf, ksiz);
        break;
      case LUA_TTABLE:
        len = lua_objlen(lua, 3);
        keys = tclistnew2(len);
        for(int i = 1; i <= len; i++){
          lua_rawgeti(lua, 3, i);
          switch(lua_type(lua, -1)){
            case LUA_TNUMBER:
            case LUA_TSTRING:
              kbuf = lua_tolstring(lua, -1, &ksiz);
              tclistpush(keys, kbuf, ksiz);
              break;
          }
          lua_pop(lua, 1);
        }
        break;
    }
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool err = false;
  TCBDB *bdb = tcbdbnew();
  lua_getglobal(lua, "_tmpdir_");
  const char *tmpdir = lua_tostring(lua, -1);
  if(!tmpdir) tmpdir = "/tmp";
  char *path = tcsprintf("%s%c%s-%d-%u",
                         tmpdir, MYPATHCHR, "mapbdb", getpid(), (unsigned int)(tctime() * 1000));
  unlink(path);
  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC)) err = true;
  unlink(path);
  tcfree(path);
  if(!tcadbmapbdb(serv->adb, keys, bdb, (ADBMAPPROC)maprec, lua, -1)) err = true;
  if(!err){
    BDBCUR *cur = tcbdbcurnew(bdb);
    tcbdbcurfirst(cur);
    const char *lbuf = NULL;
    int lsiz = 0;
    int lnum = 0;
    const char *kbuf;
    int ksiz;
    while(!err && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
      int vsiz;
      const char *vbuf = tcbdbcurval3(cur, &vsiz);
      if(lbuf && lsiz == ksiz && !memcmp(lbuf, kbuf, lsiz)){
        lua_pushlstring(lua, vbuf, vsiz);
        lua_rawseti(lua, -2, ++lnum);
      } else {
        if(lbuf){
          if(lua_pcall(lua, 2, 1, 0) != 0){
            reporterror(lua);
            err = true;
          } else if(lua_gettop(lua) < 1 || !lua_toboolean(lua, 1)){
            err = true;
          }
        }
        lua_settop(lua, 0);
        lua_getglobal(lua, MRREDVAR);
        lua_pushlstring(lua, kbuf, ksiz);
        lua_newtable(lua);
        lnum = 1;
        lua_pushlstring(lua, vbuf, vsiz);
        lua_rawseti(lua, -2, lnum);
      }
      lbuf = kbuf;
      lsiz = ksiz;
      tcbdbcurnext(cur);
    }
    if(lbuf){
      if(lua_pcall(lua, 2, 1, 0) != 0){
        reporterror(lua);
        err = true;
      } else if(lua_gettop(lua) < 1 || !lua_toboolean(lua, 1)){
        err = true;
      }
      lua_settop(lua, 0);
    }
    tcbdbcurdel(cur);
  }
  if(!tcbdbclose(bdb)) err = true;
  tcbdbdel(bdb);
  if(keys) tclistdel(keys);
  lua_pushnil(lua);
  lua_setglobal(lua, MRREDVAR);
  lua_pushnil(lua);
  lua_setglobal(lua, MRMAPVAR);
  lua_settop(lua, 0);
  lua_pushboolean(lua, !err);
  return 1;
}


/* for _mapreduce function */
static int serv_mapreducemapemit(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_mapreducemapemit: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  size_t vsiz;
  const char *vbuf = lua_tolstring(lua, 2, &vsiz);
  if(!kbuf || !vbuf){
    lua_pushstring(lua, "_mapreducemapemit: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, MRPOOLVAR);
  void *map = lua_touserdata(lua, -1);
  bool rv = tcadbmapbdbemit(map, kbuf, ksiz, vbuf, vsiz);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _stashput function */
static int serv_stashput(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_stashput: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  size_t vsiz;
  const char *vbuf = lua_tolstring(lua, 2, &vsiz);
  if(!kbuf || !vbuf){
    lua_pushstring(lua, "_stashput: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  tcmdbput(serv->stash, kbuf, ksiz, vbuf, vsiz);
  lua_pushboolean(lua, true);
  return 1;
}


/* for _stashputkeep function */
static int serv_stashputkeep(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_stashputkeep: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  size_t vsiz;
  const char *vbuf = lua_tolstring(lua, 2, &vsiz);
  if(!kbuf || !vbuf){
    lua_pushstring(lua, "_stashputkeep: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  tcmdbputkeep(serv->stash, kbuf, ksiz, vbuf, vsiz);
  lua_pushboolean(lua, true);
  return 1;
}


/* for _stashputcat function */
static int serv_stashputcat(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_stashputcat: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  size_t vsiz;
  const char *vbuf = lua_tolstring(lua, 2, &vsiz);
  if(!kbuf || !vbuf){
    lua_pushstring(lua, "_stashputcat: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  tcmdbputcat(serv->stash, kbuf, ksiz, vbuf, vsiz);
  lua_pushboolean(lua, true);
  return 1;
}


/* for _stashout function */
static int serv_stashout(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_stashout: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  if(!kbuf){
    lua_pushstring(lua, "_stashout: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tcmdbout(serv->stash, kbuf, ksiz);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _stashget function */
static int serv_stashget(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_stashget: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  if(!kbuf){
    lua_pushstring(lua, "_stashget: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  int vsiz;
  char *vbuf = tcmdbget(serv->stash, kbuf, ksiz, &vsiz);
  if(vbuf){
    lua_pushlstring(lua, vbuf, vsiz);
    tcfree(vbuf);
  } else {
    lua_pushnil(lua);
  }
  return 1;
}


/* for _stashvanish function */
static int serv_stashvanish(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 0){
    lua_pushstring(lua, "_stashvanish: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  tcmdbvanish(serv->stash);
  return 0;
}


/* for _stashforeach function */
static int serv_stashforeach(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_stashforeach: invalid arguments");
    lua_error(lua);
  }
  if(!lua_isfunction(lua, 1)){
    lua_pushstring(lua, "_stashforeach: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  lua_pushvalue(lua, 1);
  lua_setglobal(lua, ITERVAR);
  tcmdbforeach(serv->stash, (TCITER)iterrec, lua);
  lua_pushnil(lua);
  lua_setglobal(lua, ITERVAR);
  return 0;
}


/* for _lock function */
static int serv_lock(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_lock: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  if(!kbuf){
    lua_pushstring(lua, "_lock: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = true;
  while(!tcmdbputkeep(serv->lock, kbuf, ksiz, "", 0)){
    tcsleep(0.1);
    if(serv->term){
      rv = false;
      break;
    }
  }
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _unlock function */
static int serv_unlock(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_unlock: invalid arguments");
    lua_error(lua);
  }
  size_t ksiz;
  const char *kbuf = lua_tolstring(lua, 1, &ksiz);
  if(!kbuf){
    lua_pushstring(lua, "_unlock: invalid arguments");
    lua_error(lua);
  }
  lua_getglobal(lua, SERVVAR);
  SERV *serv = lua_touserdata(lua, -1);
  bool rv = tcmdbout(serv->lock, kbuf, ksiz);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _pack function */
static int serv_pack(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 1){
    lua_pushstring(lua, "_pack: invalid arguments");
    lua_error(lua);
  }
  const char *format = lua_tostring(lua, 1);
  if(!format){
    lua_pushstring(lua, "_pack: invalid arguments");
    lua_error(lua);
  }
  lua_newtable(lua);
  int aidx = argc + 1;
  int eidx = 1;
  for(int i = 2; i <= argc; i++){
    int len;
    switch(lua_type(lua, i)){
      case LUA_TNUMBER:
      case LUA_TSTRING:
        lua_pushvalue(lua, i);
        lua_rawseti(lua, aidx, eidx++);
        break;
      case LUA_TTABLE:
        len = lua_objlen(lua, i);
        for(int j = 1; j <= len; j++){
          lua_rawgeti(lua, i, j);
          lua_rawseti(lua, aidx, eidx++);
        }
        break;
      default:
        lua_pushnumber(lua, 0);
        lua_rawseti(lua, aidx, eidx++);
        break;
    }
  }
  lua_replace(lua, 2);
  lua_settop(lua, 2);
  TCXSTR *xstr = tcxstrnew();
  int emax = eidx - 1;
  eidx = 1;
  while(*format != '\0'){
    int c = *format;
    int loop = 1;
    if(format[1] == '*'){
      loop = INT_MAX;
      format++;
    } else if(format[1] >= '0' && format[1] <= '9'){
      format++;
      loop = 0;
      while(*format >= '0' && *format <= '9'){
        loop = loop * 10 + *format - '0';
        format++;
      }
      format--;
    }
    loop = tclmin(loop, emax);
    int end = tclmin(eidx + loop - 1, emax);
    while(eidx <= end){
      lua_rawgeti(lua, 2, eidx);
      double num = lua_tonumber(lua, 3);
      lua_pop(lua, 1);
      uint8_t cnum;
      uint16_t snum;
      uint32_t inum;
      uint64_t lnum;
      double dnum;
      float fnum;
      uint64_t wnum;
      char wbuf[TTNUMBUFSIZ], *wp;
      switch(c){
        case 'c':
        case 'C':
          cnum = num;
          tcxstrcat(xstr, &cnum, sizeof(cnum));
          break;
        case 's':
        case 'S':
          snum = num;
          tcxstrcat(xstr, &snum, sizeof(snum));
          break;
        case 'i':
        case 'I':
          inum = num;
          tcxstrcat(xstr, &inum, sizeof(inum));
          break;
        case 'l':
        case 'L':
          lnum = num;
          tcxstrcat(xstr, &lnum, sizeof(lnum));
          break;
        case 'f':
        case 'F':
          fnum = num;
          tcxstrcat(xstr, &fnum, sizeof(fnum));
          break;
        case 'd':
        case 'D':
          dnum = num;
          tcxstrcat(xstr, &dnum, sizeof(dnum));
          break;
        case 'n':
          snum = num;
          snum = TTHTONS(snum);
          tcxstrcat(xstr, &snum, sizeof(snum));
          break;
        case 'N':
          inum = num;
          inum = TTHTONL(inum);
          tcxstrcat(xstr, &inum, sizeof(inum));
          break;
        case 'M':
          lnum = num;
          lnum = TTHTONLL(lnum);
          tcxstrcat(xstr, &lnum, sizeof(lnum));
          break;
        case 'w':
        case 'W':
          wnum = num;
          wp = wbuf;
          if(wnum < (1ULL << 7)){
            *(wp++) = wnum;
          } else if(wnum < (1ULL << 14)){
            *(wp++) = (wnum >> 7) | 0x80;
            *(wp++) = wnum & 0x7f;
          } else if(wnum < (1ULL << 21)){
            *(wp++) = (wnum >> 14) | 0x80;
            *(wp++) = ((wnum >> 7) & 0x7f) | 0x80;
            *(wp++) = wnum & 0x7f;
          } else if(wnum < (1ULL << 28)){
            *(wp++) = (wnum >> 21) | 0x80;
            *(wp++) = ((wnum >> 14) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 7) & 0x7f) | 0x80;
            *(wp++) = wnum & 0x7f;
          } else if(wnum < (1ULL << 35)){
            *(wp++) = (wnum >> 28) | 0x80;
            *(wp++) = ((wnum >> 21) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 14) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 7) & 0x7f) | 0x80;
            *(wp++) = wnum & 0x7f;
          } else if(wnum < (1ULL << 42)){
            *(wp++) = (wnum >> 35) | 0x80;
            *(wp++) = ((wnum >> 28) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 21) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 14) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 7) & 0x7f) | 0x80;
            *(wp++) = wnum & 0x7f;
          } else if(wnum < (1ULL << 49)){
            *(wp++) = (wnum >> 42) | 0x80;
            *(wp++) = ((wnum >> 35) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 28) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 21) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 14) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 7) & 0x7f) | 0x80;
            *(wp++) = wnum & 0x7f;
          } else if(wnum < (1ULL << 56)){
            *(wp++) = (wnum >> 49) | 0x80;
            *(wp++) = ((wnum >> 42) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 35) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 28) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 21) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 14) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 7) & 0x7f) | 0x80;
            *(wp++) = wnum & 0x7f;
          } else {
            *(wp++) = (wnum >> 63) | 0x80;
            *(wp++) = ((wnum >> 49) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 42) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 35) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 28) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 21) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 14) & 0x7f) | 0x80;
            *(wp++) = ((wnum >> 7) & 0x7f) | 0x80;
            *(wp++) = wnum & 0x7f;
          }
          tcxstrcat(xstr, wbuf, wp - wbuf);
          break;
      }
      eidx++;
    }
    format++;
    if(eidx > emax) break;
  }
  lua_settop(lua, 0);
  lua_pushlstring(lua, tcxstrptr(xstr), tcxstrsize(xstr));
  tcxstrdel(xstr);
  return 1;
}


/* for _unpack function */
static int serv_unpack(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_unpack: invalid arguments");
    lua_error(lua);
  }
  const char *format = lua_tostring(lua, 1);
  size_t size;
  const char *buf = lua_tolstring(lua, 2, &size);
  if(!format){
    lua_pushstring(lua, "_unpack: invalid arguments");
    lua_error(lua);
  }
  if(!buf){
    buf = "";
    size = 0;
  }
  lua_newtable(lua);
  const char *rp = buf;
  int eidx = 1;
  while(*format != '\0'){
    int c = *format;
    int loop = 1;
    if(format[1] == '*'){
      loop = INT_MAX;
      format++;
    } else if(format[1] >= '0' && format[1] <= '9'){
      format++;
      loop = 0;
      while(*format >= '0' && *format <= '9'){
        loop = loop * 10 + *format - '0';
        format++;
      }
      format--;
    }
    loop = tclmin(loop, size);
    for(int i = 0; i < loop && size > 0; i++){
      uint8_t cnum;
      uint16_t snum;
      uint32_t inum;
      uint64_t lnum;
      float fnum;
      double dnum;
      uint64_t wnum;
      switch(c){
        case 'c':
          if(size >= sizeof(cnum)){
            memcpy(&cnum, rp, sizeof(cnum));
            lua_pushnumber(lua, (int8_t)cnum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(cnum);
            size -= sizeof(cnum);
          } else {
            size = 0;
          }
          break;
        case 'C':
          if(size >= sizeof(cnum)){
            memcpy(&cnum, rp, sizeof(cnum));
            lua_pushnumber(lua, (uint8_t)cnum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(cnum);
            size -= sizeof(cnum);
          } else {
            size = 0;
          }
          break;
        case 's':
          if(size >= sizeof(snum)){
            memcpy(&snum, rp, sizeof(snum));
            lua_pushnumber(lua, (int16_t)snum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(snum);
            size -= sizeof(snum);
          } else {
            size = 0;
          }
          break;
        case 'S':
          if(size >= sizeof(snum)){
            memcpy(&snum, rp, sizeof(snum));
            lua_pushnumber(lua, (uint16_t)snum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(snum);
            size -= sizeof(snum);
          } else {
            size = 0;
          }
          break;
        case 'i':
          if(size >= sizeof(inum)){
            memcpy(&inum, rp, sizeof(inum));
            lua_pushnumber(lua, (int32_t)inum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(inum);
            size -= sizeof(inum);
          } else {
            size = 0;
          }
          break;
        case 'I':
          if(size >= sizeof(inum)){
            memcpy(&inum, rp, sizeof(inum));
            lua_pushnumber(lua, (uint32_t)inum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(inum);
            size -= sizeof(inum);
          } else {
            size = 0;
          }
          break;
        case 'l':
          if(size >= sizeof(lnum)){
            memcpy(&lnum, rp, sizeof(lnum));
            lua_pushnumber(lua, (int64_t)lnum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(lnum);
            size -= sizeof(lnum);
          } else {
            size = 0;
          }
          break;
        case 'L':
          if(size >= sizeof(lnum)){
            memcpy(&lnum, rp, sizeof(lnum));
            lua_pushnumber(lua, (uint64_t)lnum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(lnum);
            size -= sizeof(lnum);
          } else {
            size = 0;
          }
          break;
        case 'f':
        case 'F':
          if(size >= sizeof(fnum)){
            memcpy(&fnum, rp, sizeof(fnum));
            lua_pushnumber(lua, (float)fnum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(fnum);
            size -= sizeof(fnum);
          } else {
            size = 0;
          }
          break;
        case 'd':
        case 'D':
          if(size >= sizeof(dnum)){
            memcpy(&dnum, rp, sizeof(dnum));
            lua_pushnumber(lua, (double)dnum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(dnum);
            size -= sizeof(dnum);
          } else {
            size = 0;
          }
          break;
        case 'n':
          if(size >= sizeof(snum)){
            memcpy(&snum, rp, sizeof(snum));
            snum = TTNTOHS(snum);
            lua_pushnumber(lua, (uint16_t)snum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(snum);
            size -= sizeof(snum);
          } else {
            size = 0;
          }
          break;
        case 'N':
          if(size >= sizeof(inum)){
            memcpy(&inum, rp, sizeof(inum));
            inum = TTNTOHL(inum);
            lua_pushnumber(lua, (uint32_t)inum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(inum);
            size -= sizeof(inum);
          } else {
            size = 0;
          }
          break;
        case 'M':
          if(size >= sizeof(lnum)){
            memcpy(&lnum, rp, sizeof(lnum));
            lnum = TTNTOHLL(lnum);
            lua_pushnumber(lua, (uint64_t)lnum);
            lua_rawseti(lua, 3, eidx++);
            rp += sizeof(lnum);
            size -= sizeof(lnum);
          } else {
            size = 0;
          }
          break;
        case 'w':
        case 'W':
          wnum = 0;
          do {
            inum = *(unsigned char *)rp;
            wnum = wnum * 0x80 + (inum & 0x7f);
            rp++;
            size--;
          } while(inum >= 0x80 && size > 0);
          lua_pushnumber(lua, wnum);
          lua_rawseti(lua, 3, eidx++);
          break;
      }
    }
    format++;
    if(size < 1) break;
  }
  lua_replace(lua, 1);
  lua_settop(lua, 1);
  return 1;
}


/* for _split function */
static int serv_split(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 1){
    lua_pushstring(lua, "_split: invalid arguments");
    lua_error(lua);
  }
  size_t isiz;
  const char *ibuf = lua_tolstring(lua, 1, &isiz);
  if(!ibuf){
    lua_pushstring(lua, "_split: invalid arguments");
    lua_error(lua);
  }
  const char *delims = argc > 1 ? lua_tostring(lua, 2) : NULL;
  lua_newtable(lua);
  int lnum = 1;
  if(delims){
    const char *str = ibuf;
    while(true){
      const char *sp = str;
      while(*str != '\0' && !strchr(delims, *str)){
        str++;
      }
      lua_pushlstring(lua, sp, str - sp);
      lua_rawseti(lua, -2, lnum++);
      if(*str == '\0') break;
      str++;
    }
  } else {
    const char *ptr = ibuf;
    int size = isiz;
    while(size >= 0){
      const char *rp = ptr;
      const char *ep = ptr + size;
      while(rp < ep){
        if(*rp == '\0') break;
        rp++;
      }
      lua_pushlstring(lua, ptr, rp - ptr);
      lua_rawseti(lua, -2, lnum++);
      rp++;
      size -= rp - ptr;
      ptr = rp;
    }
  }
  lua_replace(lua, 1);
  lua_settop(lua, 1);
  return 1;
}


/* for _codec function */
static int serv_codec(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_codec: invalid arguments");
    lua_error(lua);
  }
  const char *mode = lua_tostring(lua, 1);
  size_t isiz;
  const char *ibuf = lua_tolstring(lua, 2, &isiz);
  if(!mode || !ibuf){
    lua_pushstring(lua, "_codec: invalid arguments");
    lua_error(lua);
  }
  char *obuf = NULL;
  int osiz = 0;
  if(*mode == '~'){
    mode++;
    if(!tcstricmp(mode, "url")){
      obuf = tcurldecode(ibuf, &osiz);
    } else if(!tcstricmp(mode, "base")){
      obuf = tcbasedecode(ibuf, &osiz);
    } else if(!tcstricmp(mode, "quote")){
      obuf = tcquotedecode(ibuf, &osiz);
    } else if(!tcstricmp(mode, "hex")){
      obuf = tchexdecode(ibuf, &osiz);
    } else if(!tcstricmp(mode, "pack")){
      obuf = tcpackdecode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "tcbs")){
      obuf = tcbsdecode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "deflate")){
      obuf = tcinflate(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "gzip")){
      obuf = tcgzipdecode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "bzip")){
      obuf = tcbzipdecode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "xml")){
      obuf = tcxmlunescape(ibuf);
      osiz = obuf ? strlen(obuf) : 0;
    }
  } else {
    if(!tcstricmp(mode, "url")){
      obuf = tcurlencode(ibuf, isiz);
      osiz = obuf ? strlen(obuf) : 0;
    } else if(!tcstricmp(mode, "base")){
      obuf = tcbaseencode(ibuf, isiz);
      osiz = obuf ? strlen(obuf) : 0;
    } else if(!tcstricmp(mode, "quote")){
      obuf = tcquoteencode(ibuf, isiz);
      osiz = obuf ? strlen(obuf) : 0;
    } else if(!tcstricmp(mode, "hex")){
      obuf = tchexencode(ibuf, isiz);
      osiz = obuf ? strlen(obuf) : 0;
    } else if(!tcstricmp(mode, "pack")){
      obuf = tcpackencode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "tcbs")){
      obuf = tcbsencode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "deflate")){
      obuf = tcdeflate(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "gzip")){
      obuf = tcgzipencode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "bzip")){
      obuf = tcbzipencode(ibuf, isiz, &osiz);
    } else if(!tcstricmp(mode, "xml")){
      obuf = tcxmlescape(ibuf);
      osiz = obuf ? strlen(obuf) : 0;
    }
  }
  lua_settop(lua, 0);
  if(obuf){
    lua_pushlstring(lua, obuf, osiz);
    tcfree(obuf);
  } else {
    lua_pushnil(lua);
  }
  return 1;
}


/* for _hash function */
static int serv_hash(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 2){
    lua_pushstring(lua, "_hash: invalid arguments");
    lua_error(lua);
  }
  const char *mode = lua_tostring(lua, 1);
  size_t isiz;
  const char *ibuf = lua_tolstring(lua, 2, &isiz);
  if(!mode || !ibuf){
    lua_pushstring(lua, "_hash: invalid arguments");
    lua_error(lua);
  }
  if(!tcstricmp(mode, "md5")){
    char obuf[48];
    tcmd5hash(ibuf, isiz, obuf);
    lua_settop(lua, 0);
    lua_pushstring(lua, obuf);
  } else if(!tcstricmp(mode, "md5raw")){
    char obuf[48];
    tcmd5hash(ibuf, isiz, obuf);
    int esiz;
    char *ebuf = tchexdecode(obuf, &esiz);
    lua_settop(lua, 0);
    lua_pushlstring(lua, ebuf, esiz);
    tcfree(ebuf);
  } else if(!tcstricmp(mode, "crc32")){
    uint32_t crc = tcgetcrc(ibuf, isiz);
    lua_settop(lua, 0);
    lua_pushnumber(lua, crc);
  } else {
    lua_settop(lua, 0);
    lua_pushnil(lua);
  }
  return 1;
}


/* for _bit function */
static int serv_bit(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 2){
    lua_pushstring(lua, "_bit: invalid arguments");
    lua_error(lua);
  }
  const char *mode = lua_tostring(lua, 1);
  uint32_t num = lua_tonumber(lua, 2);
  uint32_t aux = argc > 2 ? lua_tonumber(lua, 3) : 0;
  if(!mode){
    lua_pushstring(lua, "_bit: invalid arguments");
    lua_error(lua);
  } else if(!tcstricmp(mode, "and")){
    num &= aux;
  } else if(!tcstricmp(mode, "or")){
    num |= aux;
  } else if(!tcstricmp(mode, "xor")){
    num ^= aux;
  } else if(!tcstricmp(mode, "not")){
    num = ~num;
  } else if(!tcstricmp(mode, "left")){
    num <<= aux;
  } else if(!tcstricmp(mode, "right")){
    num >>= aux;
  } else {
    lua_pushstring(lua, "_bit: invalid arguments");
    lua_error(lua);
  }
  lua_settop(lua, 0);
  lua_pushnumber(lua, num);
  return 1;
}


/* for _strstr function */
static int serv_strstr(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 2){
    lua_pushstring(lua, "_strstr: invalid arguments");
    lua_error(lua);
  }
  const char *str = lua_tostring(lua, 1);
  const char *pat = lua_tostring(lua, 2);
  if(!str || !pat){
    lua_pushstring(lua, "_strstr: invalid arguments");
    lua_error(lua);
  }
  const char *alt = argc > 2 ? lua_tostring(lua, 3) : NULL;
  if(alt){
    TCXSTR *xstr = tcxstrnew();
    int plen = strlen(pat);
    int alen = strlen(alt);
    if(plen > 0){
      char *pv;
      while((pv = strstr(str, pat)) != NULL){
        tcxstrcat(xstr, str, pv - str);
        tcxstrcat(xstr, alt, alen);
        str = pv + plen;
      }
    }
    tcxstrcat2(xstr, str);
    lua_settop(lua, 0);
    lua_pushstring(lua, tcxstrptr(xstr));
    tcxstrdel(xstr);
  } else {
    char *pv = strstr(str, pat);
    if(pv){
      int idx = pv - str + 1;
      lua_settop(lua, 0);
      lua_pushinteger(lua, idx);
    } else {
      lua_settop(lua, 0);
      lua_pushinteger(lua, 0);
    }
  }
  return 1;
}


/* for _regex function */
static int serv_regex(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 2){
    lua_pushstring(lua, "_regex: invalid arguments");
    lua_error(lua);
  }
  const char *str = lua_tostring(lua, 1);
  const char *regex = lua_tostring(lua, 2);
  if(!str || !regex){
    lua_pushstring(lua, "_regex: invalid arguments");
    lua_error(lua);
  }
  const char *alt = argc > 2 ? lua_tostring(lua, 3) : NULL;
  if(alt){
    char *res = tcregexreplace(str, regex, alt);
    lua_settop(lua, 0);
    lua_pushstring(lua, res);
    tcfree(res);
  } else {
    if(tcregexmatch(str, regex)){
      lua_settop(lua, 0);
      lua_pushboolean(lua, true);
    } else {
      lua_settop(lua, 0);
      lua_pushboolean(lua, false);
    }
  }
  return 1;
}


/* for _ucs function */
static int serv_ucs(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "ucs: invalid arguments");
    lua_error(lua);
  }
  if(lua_type(lua, 1) == LUA_TTABLE){
    int anum = lua_objlen(lua, 1);
    uint16_t *ary = tcmalloc(sizeof(*ary) * anum + 1);
    for(int i = 1; i <= anum; i++){
      lua_rawgeti(lua, 1, i);
      ary[i-1] = lua_tointeger(lua, 2);
      lua_pop(lua, 1);
    }
    char *str = tcmalloc(anum * 3 + 1);
    tcstrucstoutf(ary, anum, str);
    lua_settop(lua, 0);
    lua_pushstring(lua, str);
    tcfree(str);
    tcfree(ary);
  } else {
    size_t len;
    const char *str = lua_tolstring(lua, 1, &len);
    if(!str){
      lua_pushstring(lua, "ucs: invalid arguments");
      lua_error(lua);
    }
    uint16_t *ary = tcmalloc(sizeof(*ary) * len + 1);
    int anum;
    tcstrutftoucs(str, ary, &anum);
    lua_settop(lua, 0);
    lua_createtable(lua, anum, 0);
    for(int i = 0; i < anum; i++){
      lua_pushinteger(lua, ary[i]);
      lua_rawseti(lua, 1, i + 1);
    }
    tcfree(ary);
  }
  return 1;
}


/* for _dist function */
static int serv_dist(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc < 2){
    lua_pushstring(lua, "dist: invalid arguments");
    lua_error(lua);
  }
  const char *astr = lua_tostring(lua, 1);
  const char *bstr = lua_tostring(lua, 2);
  bool utf = argc > 2 ? lua_toboolean(lua, 3) : false;
  if(!astr || !bstr){
    lua_pushstring(lua, "dist: invalid arguments");
    lua_error(lua);
  }
  int rv = utf ? tcstrdistutf(astr, bstr) : tcstrdist(astr, bstr);
  lua_settop(lua, 0);
  lua_pushnumber(lua, rv);
  return 1;
}


/* for _isect function */
static int serv_isect(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc == 1 && lua_type(lua, 1) == LUA_TTABLE){
    int len = lua_objlen(lua, 1);
    for(int i = 1; i <= len; i++){
      lua_rawgeti(lua, 1, i);
      if(lua_type(lua, -1) == LUA_TTABLE){
        argc++;
      } else {
        lua_pop(lua, 1);
        break;
      }
    }
    if(argc > 1){
      lua_remove(lua, 1);
      argc--;
    }
  }
  int tnum = 0;
  int rnum = 0;
  for(int i = 1; i <= argc; i++){
    if(lua_type(lua, i) != LUA_TTABLE) continue;
    int len = lua_objlen(lua, i);
    if(len < 1){
      lua_settop(lua, 0);
      lua_newtable(lua);
      return 1;
    }
    tnum++;
    rnum += len;
  }
  if(tnum == 2){
    TCMAP *former = NULL;
    TCMAP *latter = NULL;
    for(int i = 1; i <= argc; i++){
      if(lua_type(lua, i) != LUA_TTABLE) continue;
      int len = lua_objlen(lua, i);
      if(former){
        latter = tcmapnew2(tclmin(len, tcmaprnum(former)));
        for(int j = 1; j <= len; j++){
          lua_rawgeti(lua, i, j);
          size_t size;
          const char *ptr = lua_tolstring(lua, -1, &size);
          if(ptr){
            int vsiz;
            if(tcmapget(former, ptr, size, &vsiz)) tcmapput(latter, ptr, size, "", 0);
          }
          lua_pop(lua, 1);
        }
        break;
      } else {
        former = tcmapnew2(len);
        for(int j = 1; j <= len; j++){
          lua_rawgeti(lua, i, j);
          size_t size;
          const char *ptr = lua_tolstring(lua, -1, &size);
          if(ptr) tcmapput(former, ptr, size, "", 0);
          lua_pop(lua, 1);
        }
      }
    }
    lua_settop(lua, 0);
    if(latter){
      lua_createtable(lua, (int)tcmaprnum(latter), 0);
      tcmapiterinit(latter);
      int ridx = 1;
      const char *kbuf;
      int ksiz;
      while((kbuf = tcmapiternext(latter, &ksiz)) != NULL){
        lua_pushlstring(lua, kbuf, ksiz);
        lua_rawseti(lua, 1, ridx++);
      }
      tcmapdel(latter);
    } else {
      lua_newtable(lua);
    }
    if(former) tcmapdel(former);
  } else {
    TCMAP *freq = tcmapnew2(rnum);
    for(int i = 1; i <= argc; i++){
      if(lua_type(lua, i) != LUA_TTABLE) continue;
      int len = lua_objlen(lua, i);
      TCMAP *uniq = tcmapnew2(len);
      for(int j = 1; j <= len; j++){
        lua_rawgeti(lua, i, j);
        size_t size;
        const char *ptr = lua_tolstring(lua, -1, &size);
        if(ptr){
          int vsiz;
          if(!tcmapget(uniq, ptr, size, &vsiz)){
            tcmapaddint(freq, ptr, size, 1);
            tcmapput(uniq, ptr, size, "", 0);
          }
        }
        lua_pop(lua, 1);
      }
      tcmapdel(uniq);
    }
    lua_settop(lua, 0);
    lua_createtable(lua, (int)tcmaprnum(freq), 0);
    tcmapiterinit(freq);
    int ridx = 1;
    const char *kbuf;
    int ksiz;
    while((kbuf = tcmapiternext(freq, &ksiz)) != NULL){
      int vsiz;
      const char *vbuf = tcmapiterval(kbuf, &vsiz);
      int num = *(int *)vbuf;
      if(num != tnum) continue;
      lua_pushlstring(lua, kbuf, ksiz);
      lua_rawseti(lua, 1, ridx++);
    }
    tcmapdel(freq);
  }
  return 1;
}


/* for _union function */
static int serv_union(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc == 1 && lua_type(lua, 1) == LUA_TTABLE){
    int len = lua_objlen(lua, 1);
    for(int i = 1; i <= len; i++){
      lua_rawgeti(lua, 1, i);
      if(lua_type(lua, -1) == LUA_TTABLE){
        argc++;
      } else {
        lua_pop(lua, 1);
        break;
      }
    }
    if(argc > 1){
      lua_remove(lua, 1);
      argc--;
    }
  }
  int rnum = 0;
  for(int i = 1; i <= argc; i++){
    if(lua_type(lua, i) != LUA_TTABLE) continue;
    rnum += lua_objlen(lua, i);
  }
  TCMAP *result = tcmapnew2(rnum);
  for(int i = 1; i <= argc; i++){
    if(lua_type(lua, i) != LUA_TTABLE) continue;
    int len = lua_objlen(lua, i);
    for(int j = 1; j <= len; j++){
      lua_rawgeti(lua, i, j);
      size_t size;
      const char *ptr = lua_tolstring(lua, -1, &size);
      if(ptr) tcmapput(result, ptr, size, "", 0);
      lua_pop(lua, 1);
    }
  }
  lua_settop(lua, 0);
  lua_createtable(lua, (int)tcmaprnum(result), 0);
  tcmapiterinit(result);
  int ridx = 1;
  const char *kbuf;
  int ksiz;
  while((kbuf = tcmapiternext(result, &ksiz)) != NULL){
    lua_pushlstring(lua, kbuf, ksiz);
    lua_rawseti(lua, 1, ridx++);
  }
  tcmapdel(result);
  return 1;
}


/* for _time function */
static int serv_time(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 0){
    lua_pushstring(lua, "_time: invalid arguments");
    lua_error(lua);
  }
  lua_settop(lua, 0);
  lua_pushnumber(lua, tctime());
  return 1;
}


/* for _sleep function */
static int serv_sleep(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_sleep: invalid arguments");
    lua_error(lua);
  }
  double sec = lua_tonumber(lua, 1);
  if(!lua_isnumber(lua, 1)){
    lua_pushstring(lua, "_sleep: invalid arguments");
    lua_error(lua);
  }
  lua_settop(lua, 0);
  lua_pushboolean(lua, tcsleep(sec));
  return 1;
}


/* for stat function */
static int serv_stat(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_stat: invalid arguments");
    lua_error(lua);
  }
  const char *path = lua_tostring(lua, 1);
  if(!path){
    lua_pushstring(lua, "_stat: invalid arguments");
    lua_error(lua);
  }
  struct stat sbuf;
  if(lstat(path, &sbuf) == 0){
    lua_settop(lua, 0);
    lua_newtable(lua);
    lua_pushnumber(lua, sbuf.st_dev);
    lua_setfield(lua, -2, "dev");
    lua_pushnumber(lua, sbuf.st_ino);
    lua_setfield(lua, -2, "ino");
    lua_pushnumber(lua, sbuf.st_mode);
    lua_setfield(lua, -2, "mode");
    lua_pushnumber(lua, sbuf.st_nlink);
    lua_setfield(lua, -2, "nlink");
    lua_pushnumber(lua, sbuf.st_uid);
    lua_setfield(lua, -2, "uid");
    lua_pushnumber(lua, sbuf.st_gid);
    lua_setfield(lua, -2, "gid");
    lua_pushnumber(lua, sbuf.st_rdev);
    lua_setfield(lua, -2, "rdev");
    lua_pushnumber(lua, sbuf.st_size);
    lua_setfield(lua, -2, "size");
    lua_pushnumber(lua, sbuf.st_blksize);
    lua_setfield(lua, -2, "blksize");
    lua_pushnumber(lua, sbuf.st_blocks);
    lua_setfield(lua, -2, "blocks");
    lua_pushnumber(lua, sbuf.st_atime);
    lua_setfield(lua, -2, "atime");
    lua_pushnumber(lua, sbuf.st_mtime);
    lua_setfield(lua, -2, "mtime");
    lua_pushnumber(lua, sbuf.st_ctime);
    lua_setfield(lua, -2, "ctime");
    lua_pushboolean(lua, S_ISREG(sbuf.st_mode));
    lua_setfield(lua, -2, "_regular");
    lua_pushboolean(lua, S_ISDIR(sbuf.st_mode));
    lua_setfield(lua, -2, "_directory");
    bool readable = false;
    bool writable = false;
    bool executable = false;
    if(sbuf.st_uid == geteuid()){
      if(sbuf.st_mode & S_IRUSR) readable = true;
      if(sbuf.st_mode & S_IWUSR) writable = true;
      if(sbuf.st_mode & S_IXUSR) executable = true;
    }
    if(sbuf.st_gid == getegid()){
      if(sbuf.st_mode & S_IRGRP) readable = true;
      if(sbuf.st_mode & S_IWGRP) writable = true;
      if(sbuf.st_mode & S_IXGRP) executable = true;
    }
    if(sbuf.st_mode & S_IROTH) readable = true;
    if(sbuf.st_mode & S_IWOTH) writable = true;
    if(sbuf.st_mode & S_IXOTH) executable = true;
    lua_pushboolean(lua, readable);
    lua_setfield(lua, -2, "_readable");
    lua_pushboolean(lua, writable);
    lua_setfield(lua, -2, "_writable");
    lua_pushboolean(lua, executable);
    lua_setfield(lua, -2, "_executable");
    char *rpath = tcrealpath(path);
    if(rpath){
      lua_pushstring(lua, rpath);
      lua_setfield(lua, -2, "_realpath");
      tcfree(rpath);
    }
  } else {
    lua_settop(lua, 0);
    lua_pushnil(lua);
  }
  return 1;
}


/* for _glob function */
static int serv_glob(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_glob: invalid arguments");
    lua_error(lua);
  }
  const char *pattern = lua_tostring(lua, 1);
  if(!pattern){
    lua_pushstring(lua, "_glob: invalid arguments");
    lua_error(lua);
  }
  TCLIST *paths = tcglobpat(pattern);
  int pnum = tclistnum(paths);
  lua_settop(lua, 0);
  lua_createtable(lua, pnum, 0);
  for(int i = 0; i < pnum; i++){
    int size;
    const char *buf = tclistval(paths, i, &size);
    lua_pushlstring(lua, buf, size);
    lua_rawseti(lua, -2, i + 1);
  }
  tclistdel(paths);
  return 1;
}


/* for _remove function */
static int serv_remove(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_remove: invalid arguments");
    lua_error(lua);
  }
  const char *path = lua_tostring(lua, 1);
  if(!path){
    lua_pushstring(lua, "_remove: invalid arguments");
    lua_error(lua);
  }
  bool rv = tcremovelink(path);
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


/* for _mkdir function */
static int serv_mkdir(lua_State *lua){
  int argc = lua_gettop(lua);
  if(argc != 1){
    lua_pushstring(lua, "_mkdir: invalid arguments");
    lua_error(lua);
  }
  const char *path = lua_tostring(lua, 1);
  if(!path){
    lua_pushstring(lua, "_mkdir: invalid arguments");
    lua_error(lua);
  }
  bool rv = mkdir(path, 00755) == 0;
  lua_settop(lua, 0);
  lua_pushboolean(lua, rv);
  return 1;
}


#endif



// END OF FILE
