#include "stream.h" #include #include #include #include #include #include #include /// Creates and pushes new Stream into the Lua stack. /// \param L Lua state to push to /// \param fd File descriptor used by the Stream /// \return TODO int stream_push_new(lua_State * L, const int fd) { struct stream * s = lua_newuserdata(L, sizeof(struct stream)); memset(s, 0, sizeof(struct stream)); s->fd = fd; if (1 == luaL_newmetatable(L, "stream")) { lua_pushliteral(L, "__gc"); lua_pushcfunction(L, stream_gc); lua_rawset(L, -3); lua_pushliteral(L, "__index"); lua_createtable(L, 0, 1); lua_pushliteral(L, "read"); lua_pushcfunction(L, stream_read); lua_rawset(L, -3); lua_rawset(L, -3); } lua_setmetatable(L, -2); return LUA_OK; } /// Metamethod to handle garbage collection of Stream userdata. /// \param L Lua state in which Stream resides /// \return Number of the results pushed to the stack; always zero in this case int stream_gc(lua_State * L) { int n = lua_gettop(L); if (1 != n) { lua_pushliteral(L, "Invalid number of arguments received"); return lua_error(L); } struct stream * s = lua_touserdata(L, -1); if (NULL == s) { lua_pushliteral(L, "Missing stream argument"); return lua_error(L); } if (NULL != s->in.data) { free(s->in.data); } lua_pop(L, 1); return 0; } /// Starts reading operation from a stream. /// \param L Lua state in which Stream resides /// \return Number of the results pushed to the stack int stream_read(lua_State * L) { int n = lua_gettop(L); if (2 > n) { lua_pushliteral(L, "At least two arguments expected"); return lua_error(L); } struct stream * s = lua_touserdata(L, 1); if (NULL == s) { // TODO: Improve error handling and raising in steam_* lua_CFunctions lua_pushliteral(L, "Missing stream argument"); return lua_error(L); } if (NULL == s->in.data) { void * buffer = malloc(1024); if (NULL == buffer) { lua_pushliteral(L, "Could not allocate buffer for stream"); return lua_error(L); } s->in.data = buffer; s->in.allocated = 1024; s->in.length = 0; s->in.offset = 0; } return stream_readk(L, LUA_OK, (lua_KContext) s); // Intentionally do not remove arguments from the stack. } static int until(struct buffer * b, const char * pattern, const int pattern_length) { int offset = b->offset; while (offset + pattern_length <= b->length) { if (0 == strncmp(&b->data[offset], pattern, pattern_length)) { return offset; } else { offset++; } } return -1; } /// Continuation function and core implementation of the reading operation from a stream. /// \param L Lua state running reading operation /// \param status TODO /// \param ctx Address of the Stream that is being read /// \param Number of the results pushed to the stack int stream_readk(lua_State * L, int status, lua_KContext ctx) { struct stream * s = (struct stream *) ctx; int remaining_bytes = s->in.length - s->in.offset; int offset = -1; size_t len; const char * pattern = lua_tolstring(L, 2, &len); const int pattern_length = (int) len; // TODO: NULL check if (pattern_length <= remaining_bytes) { offset = until(&s->in, pattern, pattern_length); } if (-1 == offset || 0 == remaining_bytes) { if (0 < s->in.length && 0 <= s->in.allocated - s->in.offset - 1) { // TODO: Allow buffer to grow some before raising an error. lua_pushliteral(L, "Read buffer growing not implemented"); return lua_error(L); } // TODO: Allow user to control amount of bytes being read. int length = read(s->fd, s->in.data + s->in.offset, s->in.allocated - s->in.offset - 1); if (-1 == length) { if (EWOULDBLOCK == errno || EAGAIN == errno) { return lua_yieldk(L, 0, ctx, stream_readk); } else { lua_pop(L, 1); lua_pushstring(L, strerror(errno)); return lua_error(L); } } s->in.data[length] = 0; s->in.length = s->in.offset + length; offset = until(&s->in, pattern, pattern_length); if (-1 == offset) { // TODO: Reiterate at this point, grow buffer until some error is reached or pattern is found. lua_pushliteral(L, "Could not find pattern"); return lua_error(L); } } lua_pushlstring(L, &s->in.data[s->in.offset], offset - s->in.offset); s->in.offset = offset + 1; for (int n = -lua_gettop(L); n < -1; ++n) { lua_remove(L, n); } return 1; }