Skip to content

Instantly share code, notes, and snippets.

@starwing
Last active May 24, 2016 13:07
Show Gist options
  • Save starwing/069ec985a48b34626a8739edabfa114d to your computer and use it in GitHub Desktop.
Save starwing/069ec985a48b34626a8739edabfa114d to your computer and use it in GitHub Desktop.
LuaCsv - a CSV reading/writting module for Lua language.
#ifdef _MSC_VER
# define _CRT_SECURE_NO_WARNINGS
#endif /* _MSC_VER */
#define LUA_LIB
#include <lua.h>
#include <lauxlib.h>
#if LUA_VERSION_NUM < 503
static int lua53_rawgeti(lua_State *L, int idx, lua_Integer n)
{ lua_rawgeti(L,idx,n); return lua_type(L,-1); }
#else
# define lua53_rawgeti lua_rawgeti
#endif
#if LUA_VERSION_NUM < 502
# define lua_rawlen lua_objlen
# define luaL_newlibtable(L,l) \
lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
# define luaL_newlib(L,l) \
(luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
static int lua_absindex(lua_State *L, int idx)
{ return idx >= 0 || idx <= LUA_REGISTRYINDEX ? idx : idx+lua_gettop(L); }
static void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) {
luaL_checkstack(L, nup, "too many upvalues");
for (; l->name != NULL; l++) {
int i;
for (i = 0; i < nup; i++)
lua_pushvalue(L, -nup);
lua_pushcclosure(L, l->func, nup);
lua_setfield(L, -(nup + 2), l->name);
}
lua_pop(L, nup);
}
static const char *luaL_tolstring(lua_State *L, int idx, size_t *plen) {
if (!luaL_callmeta(L, idx, "__tostring")) { /* no metafield? */
switch (lua_type(L, idx)) {
case LUA_TNUMBER:
case LUA_TSTRING:
lua_pushvalue(L, idx);
break;
case LUA_TBOOLEAN:
lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false"));
break;
case LUA_TNIL:
lua_pushliteral(L, "nil");
break;
default:
lua_pushfstring(L, "%s: %p", luaL_typename(L, idx),
lua_topointer(L, idx));
break;
}
}
return lua_tolstring(L, -1, plen);
}
#endif
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define csv_isnewline(ch) ((ch) == EOF || (ch) == '\n' || (ch) == '\r')
typedef const char *CSV_Reader(void *ud, size_t *plen);
typedef struct CSV_State {
/* configs */
int delimiter;
int quote;
/* states */
int current;
size_t size;
const char *p;
CSV_Reader *reader;
void *ud;
} CSV_State;
static void csv_init(CSV_State *S, int delimiter, int quote) {
memset(S, 0, sizeof(*S));
S->delimiter = delimiter ? delimiter : ',';
S->quote = quote ? quote : '\"';
}
static int csv_next(CSV_State *S) {
if (S->current == EOF) return EOF;
if (S->size == 0 || S->p == NULL) {
if (S->reader == NULL)
return S->current = EOF;
S->p = S->reader(S->ud, &S->size);
if (S->p == NULL || S->size == 0)
return S->current = EOF;
}
--S->size;
return S->current = *S->p++;
}
static void csv_setinput(CSV_State *S, CSV_Reader *reader, void *ud) {
S->size = 0;
S->p = NULL;
S->reader = reader;
S->ud = ud;
S->current = csv_next(S);
}
static int csv_readline(CSV_State *S, lua_State *L, int t) {
int count = 0, old;
luaL_Buffer B;
if (t == 0) { lua_createtable(L, 4, 1); t = -1; }
t = lua_absindex(L, t);
while (S->current != EOF) {
int quote = 0;
luaL_buffinit(L, &B);
while (S->current != EOF) {
if (S->current == S->quote && csv_next(S) != S->quote)
quote = !quote;
if (!quote && (csv_isnewline(S->current)
|| S->current == S->delimiter)) break;
luaL_addchar(&B, S->current);
csv_next(S);
}
luaL_pushresult(&B);
lua_rawseti(L, t, ++count);
if (csv_isnewline(S->current)) break;
if (S->current == S->delimiter) csv_next(S);
}
old = S->current;
csv_next(S); /* skip '\n' or '\r' */
if (csv_isnewline(S->current) && S->current != old)
csv_next(S); /* skip '\n\r' or '\r\n' */
return S->current != EOF;
}
static void csv_writeline(CSV_State *S, luaL_Buffer *B, int t) {
lua_State *L = B->L;
int quote, i, len = lua_rawlen(L, t);
t = lua_absindex(L, t);
for (i = 1; i <= len; ++i) {
size_t pos, len;
const char *s;
lua_rawgeti(L, t, i);
s = luaL_tolstring(L, -1, &len);
lua_pop(L, 1);
for (quote = 0, pos = 0; pos < len; ++pos)
if (s[pos] == S->quote || s[pos] == S->delimiter)
{ quote = 1; break; }
if (i != 1) luaL_addchar(B, S->delimiter);
if (!quote) { luaL_addvalue(B); continue; }
luaL_addchar(B, S->quote);
for (pos = 0; pos < len; ++pos) {
if (s[pos] == S->quote)
luaL_addchar(B, S->quote);
luaL_addchar(B, s[pos]);
}
luaL_addchar(B, S->quote);
}
}
/* entry point */
#define RESERVEDSLOT 3
typedef struct LoadS {
const char *s;
size_t size;
} LoadS;
static const char *getS(void *ud, size_t *plen) {
LoadS *ls = (LoadS*)ud;
if (ls->size == 0) return NULL;
*plen = ls->size;
ls->size = 0;
return ls->s;
}
static const char *generic_reader(void *ud, size_t *size) {
lua_State *L = (lua_State*)ud;
luaL_checkstack(L, 2, "too many nested functions");
lua_pushvalue(L, 1); /* get function */
lua_call(L, 0, 1); /* call it */
if (lua_isnil(L, -1)) {
lua_pop(L, 1); /* pop result */
*size = 0;
return NULL;
}
else if (!lua_isstring(L, -1))
luaL_error(L, "reader function must return a string");
lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */
return lua_tolstring(L, RESERVEDSLOT, size);
}
static int load_aux(CSV_State *S, lua_State *L) {
int count = 0;
lua_createtable(L, 4, 1);
while (csv_readline(S, L, 0))
lua_rawseti(L, -2, ++count);
lua_rawseti(L, -2, ++count);
return 1;
}
static int Lload(lua_State *L) {
CSV_State S;
LoadS ls;
const char *opt = luaL_optstring(L, 2, ",\"");
ls.s = lua_tolstring(L, 1, &ls.size);
csv_init(&S, opt[0], opt[0] ? opt[1] : 0);
if (ls.s != NULL)
csv_setinput(&S, getS, &ls);
else { /* loading from a reader function */
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_settop(L, RESERVEDSLOT); /* create reserved slot */
csv_setinput(&S, generic_reader, L);
}
return load_aux(&S, L);
}
typedef struct LoadF {
int n;
FILE *f;
char buff[BUFSIZ];
} LoadF;
static const char *getF(void *ud, size_t *size) {
LoadF *lf = (LoadF *)ud;
if (lf->n > 0) { *size = lf->n; lf->n = 0; }
else {
if (feof(lf->f)) return NULL;
*size = fread(lf->buff, 1, sizeof(lf->buff), lf->f);
}
return lf->buff;
}
static int errfile(lua_State *L, const char *fname, const char *what) {
const char *serr = strerror(errno);
lua_pushnil(L);
lua_pushfstring(L, "cannot %s %s: %s", what, fname, serr);
return 2;
}
static void skipBOM(LoadF *lf) {
const char *p = "\xEF\xBB\xBF";
int c;
lf->n = 0;
do {
c = getc(lf->f);
lf->buff[lf->n++] = c;
if (c == EOF || c != *(const unsigned char *)p++)
return;
} while (*p != '\0');
lf->n = 0;
}
static int Lloadfile(lua_State *L) {
CSV_State S;
LoadF lf;
int ret, readstatus;
const char *fname = luaL_optstring(L, 1, NULL);
const char *opt = luaL_optstring(L, 2, ",\"");
lf.f = fname ? fopen(fname, "r") : stdin;
if (lf.f == NULL) return errfile(L, fname, "open");
skipBOM(&lf);
csv_init(&S, opt[0], opt[0] ? opt[1] : 0);
csv_setinput(&S, getF, &lf);
ret = load_aux(&S, L);
readstatus = ferror(lf.f);
if (fname) fclose(lf.f); /* close file (even in case of errors) */
if (readstatus) {
errfile(L, fname ? fname : "=stdin", "read");
lua_remove(L, -2);
return ret + 1;
}
return ret;
}
static lua_Integer posrelat(lua_Integer pos, size_t len) {
if (pos >= 0) return pos;
else if (0u - (size_t)pos > len) return 0;
else return (lua_Integer)len + pos + 1;
}
static int Lloadline(lua_State *L) {
CSV_State S;
size_t len;
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer i = posrelat(luaL_optinteger(L, 2, 1), len);
const char *opt = luaL_optstring(L, 3, ",\"");
if (i < 1) i = 1;
if (i > len) return 0;
csv_init(&S, opt[0], opt[0] ? opt[1] : 0);
S.current = s[i-1];
S.p = s + i;
S.size = (size_t)(len - i);
csv_readline(&S, L, 0);
lua_pushinteger(L, len - (S.size > 0 ? S.size : -1));
return 2;
}
static int Ldump(lua_State *L) {
CSV_State S;
luaL_Buffer B;
int i, len;
const char *opt = luaL_optstring(L, 2, ",\"");
csv_init(&S, opt[0], opt[0] ? opt[1] : 0);
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 2);
luaL_buffinit(L, &B);
for (i = 1, len = lua_rawlen(L, 1); i <= len; ++i) {
if (lua53_rawgeti(L, 1, i) == LUA_TTABLE) {
lua_replace(L, 2);
csv_writeline(&S, &B, 2);
luaL_addchar(&B, '\n');
}
}
luaL_pushresult(&B);
return 1;
}
static int Ldumpline(lua_State *L) {
luaL_Buffer B;
CSV_State S;
const char *opt = luaL_optstring(L, 2, ",\"");
csv_init(&S, opt[0], opt[0] ? opt[1] : 0);
luaL_checktype(L, 1, LUA_TTABLE);
luaL_buffinit(L, &B);
csv_writeline(&S, &B, 1);
luaL_pushresult(&B);
return 1;
}
LUALIB_API int luaopen_csv(lua_State *L) {
luaL_Reg libs[] = {
#define ENTRY(name) { #name, L##name }
ENTRY(load),
ENTRY(loadline),
ENTRY(loadfile),
ENTRY(dump),
ENTRY(dumpline),
#undef ENTRY
{ NULL, NULL }
};
luaL_newlib(L, libs);
return 1;
}
/* cc: flags+='-s -O3 -mdll -DLUA_BUILD_AS_DLL'
* cc: libs+='-llua53' output='csv.dll' */
local csv = require "csv"
function test1()
local test = [[
"a","b"
"a,b","c,d"
"a","b,c"
"a,b","c"
"a",b,"c"
"a,b",c,"d,e"
"a",b,"c,d"
"a,b",c,"d"
"a","b",
"a,b","c,d",
"a","b,c",
"a,b","c",
"a",b,"c",
"a,b",c,"d,e",
"a",b,"c,d",
"a,b",c,"d",
,"a","b"
,"a,b","c,d"
,"a","b,c"
,"a,b","c"
,"a",b,"c"
,"a,b",c,"d,e"
,"a",b,"c,d"
,"a,b",c,"d"
"a,b
,c,d",e,f,g
]]
local result = {
"{a}-{b}",
"{a,b}-{c,d}",
"{a}-{b,c}",
"{a,b}-{c}",
"{a}-{b}-{c}",
"{a,b}-{c}-{d,e}",
"{a}-{b}-{c,d}",
"{a,b}-{c}-{d}",
"{a}-{b}-{}",
"{a,b}-{c,d}-{}",
"{a}-{b,c}-{}",
"{a,b}-{c}-{}",
"{a}-{b}-{c}-{}",
"{a,b}-{c}-{d,e}-{}",
"{a}-{b}-{c,d}-{}",
"{a,b}-{c}-{d}-{}",
"{}-{a}-{b}",
"{}-{a,b}-{c,d}",
"{}-{a}-{b,c}",
"{}-{a,b}-{c}",
"{}-{a}-{b}-{c}",
"{}-{a,b}-{c}-{d,e}",
"{}-{a}-{b}-{c,d}",
"{}-{a,b}-{c}-{d}",
"{a,b\n,c,d}-{e}-{f}-{g}",
}
local out = csv.load(test)
for i, v in ipairs(out) do
local r = "{"..table.concat(v, "}-{").."}"
assert(r == result[i])
end
local once
local out = csv.load(function()
if not once then
once = true
return test end
end)
for i, v in ipairs(out) do
local r = "{"..table.concat(v, "}-{").."}"
assert(r == result[i])
end
local i, pos = 1, 1
while true do
local v, nl = csv.loadline(test, pos)
if not v then break end
local r = "{"..table.concat(v, "}-{").."}"
assert(r == result[i])
i = i + 1
pos = nl
end
local fh = assert(io.open("out.csv", "w"))
fh:write("\xEF\xBB\xBF", test)
fh:close()
local out = csv.loadfile "out.csv"
for i, v in ipairs(out) do
local r = "{"..table.concat(v, "}-{").."}"
assert(r == result[i])
end
end
function test2()
local test = [[
"Name ""x"" is that ",Product,Date,Age
steve,"bonzo ""dog"" catcher",10/10/09,3
bonzo,dog,23/04/08,10
john,CowCatcher,20/10/09,4
]]
local result = {
"{Name \"x\" is that }-{Product}-{Date}-{Age}",
"{steve}-{bonzo \"dog\" catcher}-{10/10/09}-{3}",
"{bonzo}-{dog}-{23/04/08}-{10}",
"{john}-{CowCatcher}-{20/10/09}-{4}",
}
local out = csv.load(test)
for i, v in ipairs(out) do
local r = "{"..table.concat(v, "}-{").."}"
assert(r == result[i])
end
for i, v in ipairs(out) do
assert(csv.dumpline(v) ==
csv.dumpline((csv.loadline(csv.dumpline(v)))))
end
local outs = csv.dump(out)
assert(outs == test)
end
test1()
test2()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment