/* lrexlib.c - POSIX regular expression library */
/* can use Spencer extensions for matching NULs if available */

/* Reimplement strfind and gsub; test with Spencer's lib */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include "lua.h"
#include "lauxlib.h"
#include "../lmem.h"

static int regex(lua_State *L) {
  int tag = lua_tonumber(L, -1);
  size_t l;
  const char *pattern;
  int res;
  regex_t *rp = luaM_new(L, regex_t);

  lua_pop(L, 1); /* remove upvalue */
  pattern = luaL_check_lstr(L, 1, &l);
#ifdef REG_BASIC
  rp->re_endp = lua_strlen(L, 1);
  res = regcomp(rp, pattern, REG_EXTENDED | REG_PEND);
#else
  res = regcomp(rp, pattern, REG_EXTENDED);
#endif
  if (res) {
    size_t sz = regerror(res, rp, NULL, 0);
    char *errbuf;
    
    errbuf = luaM_newvector(L, sz, char);
    regerror(res, rp, errbuf, sz);
    lua_error(L, errbuf);
  }
  
  lua_pushuserdata(L, rp);
  lua_settag(L, tag);
  
  return 1;
}

static int match(lua_State *L) {
  int res, tag = lua_tonumber(L, -1);
  size_t l, nmatch, i;
  const char *text;
#ifdef REG_BASIC
  size_t len;
#endif
  regex_t *rp;
  regmatch_t *match;

  lua_pop(L, 1);  /* pop upvalue */
  text = luaL_check_lstr(L, 1, &l);
#ifdef REG_BASIC
  len = lua_strlen(L, 1);
#endif
  luaL_checkany(L, 2);
  rp = (regex_t *)lua_touserdata(L, 2);
  if (lua_tag(L, 2) != tag)
    luaL_argerror(L, 2, "regular expression expected");
  nmatch = rp->re_nsub;
  luaL_checkstack(L, nmatch + 2, "too many captures");
  match = luaM_newvector(L, nmatch + 1, regmatch_t);
#ifdef REG_BASIC
  match[0].rm_so = 0;
  match[0].rm_eo = len;
  res = regexec(rp, text, nmatch + 1, match, REG_STARTEND);
#else
  res = regexec(rp, text, nmatch + 1, match, 0);
#endif
  if (!res) {
    lua_pushnumber(L, match[0].rm_so + 1);
    lua_pushnumber(L, match[0].rm_eo);
    lua_newtable(L);
    for (i = 1; i <= nmatch; i++) {
      if (match[i].rm_so >= 0) {
        lua_pushlstring(L, text + match[i].rm_so,
          match[i].rm_eo - match[i].rm_so);
        lua_rawseti(L, -2, i);
      }
    }
    lua_pushstring(L, "n");
    lua_pushnumber(L, nmatch);
    lua_rawset(L, -3);
  } else {
    lua_pushnil(L);
    lua_pushnil(L);
    lua_pushnil(L);
  }

  return 3;
}

static int rex_collect (lua_State *L) {
  regex_t *rp = (regex_t *)lua_touserdata(L, -1);

  regfree(rp);
  free(rp);
  return 0;
}

static const struct luaL_reg rexlib[] = {
{"regex",   regex},
{"match",   match},
};

LUALIB_API void lua_rexlibopen(lua_State *L) {
  unsigned int i;
  int tag = lua_newtag(L);

  lua_pushnumber(L, tag);
  for (i=0; i<sizeof(rexlib)/sizeof(rexlib[0]); i++) {
    /* put tag as upvalue for these functions */
    lua_pushvalue(L, -1);
    lua_pushcclosure(L, rexlib[i].func, 1);
    lua_setglobal(L, rexlib[i].name);
  }
  /* free compiled regexps when collected */
  lua_pushcclosure(L, rex_collect, 1);  /* pops tag from stack */
  lua_settagmethod(L, tag, "gc");
}
