Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
lua-yajl/lua_yajl.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1027 lines (863 sloc)
30.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <yajl/yajl_parse.h> | |
| #include <yajl/yajl_gen.h> | |
| #include <lua.h> | |
| #include <lauxlib.h> | |
| #include <math.h> | |
| #include <stdlib.h> | |
| #include <errno.h> | |
| #include <string.h> | |
| #define js_check_generator(L, narg) \ | |
| (yajl_gen*)luaL_checkudata((L), (narg), "yajl.generator.meta") | |
| static void* js_null; | |
| static int js_generator(lua_State *L); | |
| static int js_generator_value(lua_State *L); | |
| static void js_parser_assert(lua_State* L, | |
| yajl_status status, | |
| yajl_handle* handle, | |
| const unsigned char* json_text, | |
| unsigned int json_text_len, | |
| int expect_complete, | |
| const char* file, | |
| int line); | |
| static int got_map_key(lua_State* L); | |
| static int got_map_value(lua_State* L); | |
| ////////////////////////////////////////////////////////////////////// | |
| static double todouble(lua_State* L, const char* val, unsigned int len) { | |
| // Convert into number using a temporary: | |
| char* tmp = (char*)lua_newuserdata(L, len+1); | |
| memcpy(tmp, val, len); | |
| tmp[len] = '\0'; | |
| double num = strtod(tmp, NULL); | |
| /* | |
| if ((num == HUGE_VAL || num == -HUGE_VAL) && | |
| errno == ERANGE) | |
| { | |
| TODO: Add appropriate handling of large numbers by delegating. | |
| } | |
| TODO: How can we tell if there was information loss? aka the | |
| number of significant digits in the string exceeds the | |
| significant digits in the double. | |
| */ | |
| lua_pop(L, 1); | |
| return num; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_to_string(lua_State *L) { | |
| lua_pushcfunction(L, js_generator); | |
| // convert_me, {extra}, ?, js_gen | |
| if ( lua_istable(L, 2) ) { | |
| // Be sure printer is not defined: | |
| lua_pushliteral(L, "printer"); | |
| lua_pushnil(L); | |
| lua_rawset(L, 2); | |
| lua_pushvalue(L, 2); | |
| // convert_me, {extra}, ?, js_gen, {extra} | |
| } else { | |
| lua_newtable(L); | |
| // convert_me, {extra}, ?, js_gen, {} | |
| } | |
| lua_call(L, 1, 1); | |
| // convert_me, {extra}, ?, gen_ud | |
| lua_pushcfunction(L, js_generator_value); | |
| // convert_me, {extra}, ?, gen_ud, js_gen_val | |
| lua_pushvalue(L, -2); | |
| // convert_me, {extra}, ?, gen_ud, js_gen_val, gen_ud | |
| lua_pushvalue(L, 1); | |
| // convert_me, {extra}, ?, gen_ud, js_gen_val, gen_ud, convert_me | |
| lua_call(L, 2, 0); | |
| // convert_me, {extra}, ?, gen_ud | |
| yajl_gen* gen = js_check_generator(L, -1); | |
| const unsigned char *buf; | |
| unsigned int len; | |
| yajl_gen_get_buf(*gen, &buf, &len); | |
| // Copy into results: | |
| lua_pushlstring(L, (char*)buf, len); | |
| yajl_gen_clear(*gen); | |
| return 1; | |
| } | |
| static int to_value_null(void* ctx) { | |
| lua_State* L = (lua_State*)ctx; | |
| lua_getfield(L, LUA_REGISTRYINDEX, "yajl.null"); | |
| (lua_tocfunction(L, -2))(L); | |
| return 1; | |
| } | |
| static int to_value_boolean(void* ctx, int val) { | |
| lua_State* L = (lua_State*)ctx; | |
| lua_pushboolean(L, val); | |
| (lua_tocfunction(L, -2))(L); | |
| return 1; | |
| } | |
| static int to_value_number(void* ctx, const char* val, unsigned int len) { | |
| lua_State* L = (lua_State*)ctx; | |
| lua_pushnumber(L, todouble(L, val, len)); | |
| (lua_tocfunction(L, -2))(L); | |
| return 1; | |
| } | |
| static int to_value_string(void* ctx, const unsigned char *val, unsigned int len) { | |
| lua_State* L = (lua_State*)ctx; | |
| lua_pushlstring(L, (const char*)val, len); | |
| (lua_tocfunction(L, -2))(L); | |
| return 1; | |
| } | |
| static int got_map_value(lua_State* L) { | |
| /* ..., Table, Key, Func, Value */ | |
| lua_insert(L, -2); | |
| lua_pop(L, 1); | |
| lua_rawset(L, -3); | |
| lua_pushnil(L); // Store future key here. | |
| lua_pushcfunction(L, got_map_key); | |
| return 0; // Ignored. | |
| } | |
| static int got_map_key(lua_State* L) { | |
| lua_replace(L, -3); | |
| lua_pop(L, 1); | |
| lua_pushcfunction(L, got_map_value); | |
| return 0; // Ignored. | |
| } | |
| static int got_array_value(lua_State* L) { | |
| /* ..., Table, Integer, Func, Value */ | |
| lua_rawseti(L, -4, lua_tointeger(L, -3)); | |
| lua_pushinteger(L, lua_tointeger(L, -2)+1); | |
| lua_replace(L, -3); | |
| } | |
| static int to_value_start_map(void* ctx) { | |
| lua_State* L = (lua_State*)ctx; | |
| /* The layout of the stack for "objects" is: | |
| - Table we are appending to. | |
| - Storage for the "last key found". | |
| - Function to call in to_value_* functions. | |
| - Value pushed on the stack by to_value_* functions. | |
| */ | |
| if ( ! lua_checkstack(L, 4) ) { | |
| // TODO: So far, YAJL seems to be fine with the longjmp, but | |
| // perhaps we should do errors a different way? | |
| return luaL_error(L, "lua stack overflow"); | |
| } | |
| lua_newtable(L); | |
| lua_pushnil(L); // Store future key here. | |
| lua_pushcfunction(L, got_map_key); | |
| return 1; | |
| } | |
| static int to_value_start_array(void* ctx) { | |
| lua_State* L = (lua_State*)ctx; | |
| /* The layout of the stack for "arrays" is: | |
| - Table we are appending to. | |
| - The index to use for the next insertion. | |
| - Function to call in to_value_* functions. | |
| - Value pushed on the stack by to_value_* functions. | |
| */ | |
| if ( ! lua_checkstack(L, 4) ) { | |
| // TODO: So far, YAJL seems to be fine with the longjmp, but | |
| // perhaps we should do errors a different way? | |
| return luaL_error(L, "lua stack overflow"); | |
| } | |
| lua_newtable(L); | |
| lua_pushinteger(L, 1); | |
| lua_pushcfunction(L, got_array_value); | |
| return 1; | |
| } | |
| static int to_value_end(void* ctx) { | |
| lua_State* L = (lua_State*)ctx; | |
| // Simply pop the stack and call the cfunction: | |
| lua_pop(L, 2); | |
| (lua_tocfunction(L, -2))(L); | |
| return 1; | |
| } | |
| static int noop(lua_State* L) { | |
| return 0; | |
| } | |
| static yajl_callbacks js_to_value_callbacks = { | |
| to_value_null, | |
| to_value_boolean, | |
| NULL, | |
| NULL, | |
| to_value_number, | |
| to_value_string, | |
| to_value_start_map, | |
| to_value_string, | |
| to_value_end, | |
| to_value_start_array, | |
| to_value_end, | |
| }; | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_to_value(lua_State *L) { | |
| yajl_parser_config cfg = { 1, 1 }; | |
| yajl_handle handle; | |
| size_t len; | |
| const unsigned char* buff = (const unsigned char*) luaL_checklstring(L, 1, &len); | |
| int expect_complete = 1; | |
| if ( NULL == buff ) return 0; | |
| if ( lua_istable(L, 2) ) { | |
| lua_getfield(L, 2, "allow_comments"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| cfg.allowComments = lua_toboolean(L, -1); | |
| } | |
| lua_pop(L, 1); | |
| lua_getfield(L, 2, "check_utf8"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| cfg.checkUTF8 = lua_toboolean(L, -1); | |
| } | |
| lua_pop(L, 1); | |
| } | |
| handle = yajl_alloc(&js_to_value_callbacks, &cfg, NULL, (void*)L); | |
| lua_pushcfunction(L, noop); | |
| js_parser_assert(L, | |
| yajl_parse(handle, buff, len), | |
| &handle, | |
| buff, | |
| len, | |
| expect_complete, | |
| __FILE__, | |
| __LINE__); | |
| yajl_free(handle); | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_null(void *ctx) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "value"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_getfield(L, LUA_REGISTRYINDEX, "yajl.null"); | |
| lua_pushliteral(L, "null"); | |
| lua_call(L, 3, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_boolean(void *ctx, int val) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "value"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_pushboolean(L, val); | |
| lua_pushliteral(L, "boolean"); | |
| lua_call(L, 3, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_number(void *ctx, const char* buffer, unsigned int buffer_len) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "value"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_pushnumber(L, todouble(L, buffer, buffer_len)); | |
| lua_pushliteral(L, "number"); | |
| lua_call(L, 3, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_string(void *ctx, const unsigned char *val, unsigned int len) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "value"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_pushlstring(L, (const char*)val, len); | |
| lua_pushliteral(L, "string"); | |
| lua_call(L, 3, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_start_map(void *ctx) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "open_object"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_call(L, 1, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_map_key(void *ctx, const unsigned char *val, unsigned int len) { | |
| lua_State *L=(lua_State*)ctx; | |
| // TODO: Do we want to fall-back to calling "value"? | |
| lua_getfield(L, lua_upvalueindex(2), "object_key"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_pushlstring(L, (const char*)val, len); | |
| lua_call(L, 2, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_end_map(void *ctx) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "close"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_pushliteral(L, "object"); | |
| lua_call(L, 2, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_start_array(void *ctx) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "open_array"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_call(L, 1, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_end_array(void *ctx) { | |
| lua_State *L=(lua_State*)ctx; | |
| lua_getfield(L, lua_upvalueindex(2), "close"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| lua_pushvalue(L, lua_upvalueindex(2)); | |
| lua_pushliteral(L, "array"); | |
| lua_call(L, 2, 0); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| return 1; | |
| } | |
| static yajl_callbacks js_parser_callbacks = { | |
| js_parser_null, | |
| js_parser_boolean, | |
| NULL, | |
| NULL, | |
| js_parser_number, | |
| js_parser_string, | |
| js_parser_start_map, | |
| js_parser_map_key, | |
| js_parser_end_map, | |
| js_parser_start_array, | |
| js_parser_end_array | |
| }; | |
| ////////////////////////////////////////////////////////////////////// | |
| static void js_parser_assert(lua_State* L, | |
| yajl_status status, | |
| yajl_handle* handle, | |
| const unsigned char* json_text, | |
| unsigned int json_text_len, | |
| int expect_complete, | |
| const char* file, | |
| int line) | |
| { | |
| int verbose = 1; | |
| unsigned char* msg; | |
| switch ( status ) { | |
| case yajl_status_ok: | |
| return; | |
| case yajl_status_client_canceled: | |
| lua_pushfstring(L, "Unreachable: yajl_status_client_canceled should never be returned since all callbacks return true at %s line %d", | |
| file, line); | |
| break; | |
| case yajl_status_insufficient_data: | |
| if ( ! expect_complete ) return; | |
| lua_pushfstring(L, "IncompleteInput: js_parser_parse called with nil input, but the json input was not complete at %s line %d", | |
| file, line); | |
| break; | |
| case yajl_status_error: | |
| msg = yajl_get_error(*handle, verbose, json_text, json_text_len); | |
| lua_pushfstring(L, "InvalidJSONInput: %s at %s line %d", msg, file, line); | |
| yajl_free_error(*handle, msg); | |
| break; | |
| } | |
| lua_error(L); | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_parse(lua_State *L) { | |
| yajl_handle* handle = (yajl_handle*) | |
| lua_touserdata(L, lua_upvalueindex(1)); | |
| if ( lua_isnil(L, 1) ) { | |
| int expect_complete = 1; | |
| js_parser_assert(L, | |
| yajl_parse_complete(*handle), | |
| handle, | |
| NULL, | |
| 0, | |
| expect_complete, | |
| __FILE__, | |
| __LINE__); | |
| } else { | |
| int expect_complete = 0; | |
| size_t len; | |
| const unsigned char* buff = (const unsigned char*) luaL_checklstring(L, 1, &len); | |
| if ( NULL == buff ) return 0; | |
| js_parser_assert(L, | |
| yajl_parse(*handle, buff, len), | |
| handle, | |
| buff, | |
| len, | |
| expect_complete, | |
| __FILE__, | |
| __LINE__); | |
| } | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser_delete(lua_State *L) { | |
| luaL_checktype(L, 1, LUA_TUSERDATA); | |
| yajl_free(*(yajl_handle*)lua_touserdata(L, 1)); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_parser(lua_State *L) { | |
| yajl_parser_config cfg = { 1, 1 }; | |
| luaL_checktype(L, 1, LUA_TTABLE); | |
| lua_getfield(L, 1, "allow_comments"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| cfg.allowComments = lua_toboolean(L, -1); | |
| } | |
| lua_pop(L, 1); | |
| lua_getfield(L, 1, "check_utf8"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| cfg.checkUTF8 = lua_toboolean(L, -1); | |
| } | |
| lua_pop(L, 1); | |
| yajl_handle* handle = (yajl_handle*)lua_newuserdata(L, sizeof(yajl_handle)); | |
| *handle = yajl_alloc(&js_parser_callbacks, &cfg, NULL, (void*)L); | |
| luaL_getmetatable(L, "yajl.parser.meta"); | |
| lua_setmetatable(L, -2); | |
| lua_getfield(L, 1, "events"); | |
| // Create callback function that calls yajl_parse[_complete]() | |
| // | |
| // upvalue(1) = yajl_handle* | |
| // upvalue(2) = events table | |
| lua_pushcclosure(L, &js_parser_parse, 2); | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_delete(lua_State *L) { | |
| yajl_gen* handle = js_check_generator(L, 1); | |
| yajl_gen_free(*handle); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static void js_generator_assert(lua_State *L, | |
| yajl_gen_status status, | |
| const char* file, | |
| int line) | |
| { | |
| switch ( status ) { | |
| case yajl_gen_status_ok: | |
| case yajl_gen_generation_complete: | |
| return; | |
| case yajl_gen_keys_must_be_strings: | |
| lua_pushfstring(L, "InvalidState: expected either a call to close() or string() since we are in the middle of an object declaration at %s line %d", file, line); | |
| break; | |
| case yajl_max_depth_exceeded: | |
| lua_pushfstring(L, "StackOverflow: YAJL's max generation depth was exceeded at %s line %d", file, line); | |
| break; | |
| case yajl_gen_in_error_state: | |
| lua_pushfstring(L, "AlreadyInError: generator method was called when the generator is already in an error state at %s line %d", file, line); | |
| break; | |
| default: | |
| lua_pushfstring(L, "Unreachable: yajl_gen_status (%d) not recognized at %s line %d", status, file, line); | |
| break; | |
| } | |
| lua_error(L); | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_integer(lua_State *L) { | |
| js_generator_assert(L, | |
| yajl_gen_integer(*js_check_generator(L, 1), | |
| luaL_checkinteger(L, 2)), | |
| __FILE__, __LINE__); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_double(lua_State *L) { | |
| js_generator_assert(L, | |
| yajl_gen_double(*js_check_generator(L, 1), | |
| luaL_checknumber(L, 2)), | |
| __FILE__, __LINE__); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_number(lua_State *L) { | |
| // It would be better to make it so an arbitrary string can be | |
| // used here, however we would then need to validate that the | |
| // generated string is a JSON number which is a bit beyond scope | |
| // at this point. Perhaps in the future we will loosen this | |
| // restriction, it is always easier to loosen restrictions than it | |
| // is to make new restrictions that break other people's code. | |
| double num = luaL_checknumber(L, 2); | |
| size_t len; | |
| const char* str; | |
| // These are special cases, not sure how better to represent them | |
| // :-(. | |
| if ( num == HUGE_VAL ) { | |
| str = "1e+666"; | |
| len = 6; | |
| } else if ( num == -HUGE_VAL ) { | |
| str = "-1e+666"; | |
| len = 7; | |
| } else if ( isnan(num) ) { | |
| str = "-0"; | |
| len = 2; | |
| } else { | |
| str = luaL_checklstring(L, 2, &len); | |
| } | |
| js_generator_assert(L, | |
| yajl_gen_number(*js_check_generator(L, 1), | |
| str, len), | |
| __FILE__, __LINE__); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_string(lua_State *L) { | |
| size_t len; | |
| const unsigned char* str = (const unsigned char*)luaL_checklstring(L, 2, &len); | |
| js_generator_assert(L, | |
| yajl_gen_string(*js_check_generator(L, 1), | |
| str, len), | |
| __FILE__, __LINE__); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_null(lua_State *L) { | |
| js_generator_assert(L, | |
| yajl_gen_null(*js_check_generator(L, 1)), | |
| __FILE__, __LINE__); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_boolean(lua_State *L) { | |
| luaL_checktype(L, 2, LUA_TBOOLEAN); | |
| js_generator_assert(L, | |
| yajl_gen_bool(*js_check_generator(L, 1), | |
| lua_toboolean(L, 2)), | |
| __FILE__, __LINE__); | |
| return 0; | |
| } | |
| #define JS_OPEN_OBJECT 1 | |
| #define JS_OPEN_ARRAY 2 | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_open_object(lua_State *L) { | |
| js_generator_assert(L, | |
| yajl_gen_map_open(*js_check_generator(L, 1)), | |
| __FILE__, __LINE__); | |
| // Why doesn't yajl_gen keep track of this!? | |
| lua_getfenv(L, 1); | |
| lua_getfield(L, -1, "stack"); | |
| lua_pushinteger(L, JS_OPEN_OBJECT); | |
| lua_rawseti(L, -2, lua_objlen(L, -2) + 1); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_open_array(lua_State *L) { | |
| js_generator_assert(L, | |
| yajl_gen_array_open(*js_check_generator(L, 1)), | |
| __FILE__, __LINE__); | |
| // Why doesn't yajl_gen keep track of this!? | |
| lua_getfenv(L, 1); | |
| lua_getfield(L, -1, "stack"); | |
| lua_pushinteger(L, JS_OPEN_ARRAY); | |
| lua_rawseti(L, -2, lua_objlen(L, -2) + 1); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_close(lua_State *L) { | |
| // Why doesn't yajl_gen keep track of this!? | |
| lua_getfenv(L, 1); | |
| lua_getfield(L, -1, "stack"); | |
| lua_rawgeti(L, -1, lua_objlen(L, -1)); | |
| if ( lua_isnil(L, -1) ) { | |
| lua_pushfstring(L, "StackUnderflow: Attempt to call close() when no array or object has been opened at %s line %d", __FILE__, __LINE__); | |
| lua_error(L); | |
| } | |
| lua_Integer type = lua_tointeger(L, -1); | |
| switch ( type ) { | |
| case JS_OPEN_OBJECT: | |
| js_generator_assert(L, | |
| yajl_gen_map_close(*js_check_generator(L, 1)), | |
| __FILE__, __LINE__); | |
| break; | |
| case JS_OPEN_ARRAY: | |
| js_generator_assert(L, | |
| yajl_gen_array_close(*js_check_generator(L, 1)), | |
| __FILE__, __LINE__); | |
| break; | |
| default: | |
| lua_pushfstring(L, "Unreachable: internal 'stack' contained invalid integer (%d) at %s line %d", type, __FILE__, __LINE__); | |
| lua_error(L); | |
| } | |
| // delete the top of the "stack": | |
| lua_pop(L, 1); | |
| lua_pushnil(L); | |
| lua_rawseti(L, -2, lua_objlen(L, -2)); | |
| return 0; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator_value(lua_State *L) { | |
| int type = lua_type(L, 2); | |
| switch ( type ) { | |
| case LUA_TNIL: | |
| return js_generator_null(L); | |
| case LUA_TNUMBER: | |
| return js_generator_number(L); | |
| case LUA_TBOOLEAN: | |
| return js_generator_boolean(L); | |
| case LUA_TSTRING: | |
| return js_generator_string(L); | |
| case LUA_TUSERDATA: | |
| if ( lua_topointer(L, 2) == js_null ) { | |
| return js_generator_null(L); | |
| } | |
| case LUA_TLIGHTUSERDATA: | |
| case LUA_TTABLE: | |
| case LUA_TFUNCTION: | |
| case LUA_TTHREAD: | |
| if ( luaL_getmetafield(L, 2, "__gen_json") ) { | |
| if ( lua_isfunction(L, -1) ) { | |
| lua_settop(L, 3); // gen, obj, func | |
| lua_insert(L, 1); // func, gen, obj | |
| lua_insert(L, 2); // func, obj, gen | |
| lua_call(L, 2, 0); | |
| return 0; | |
| } | |
| lua_pop(L, 1); | |
| } | |
| // Simply ignore it, perhaps we should warn? | |
| if ( type != LUA_TTABLE ) return 0; | |
| int max = 0; | |
| int is_array = 1; | |
| // First iterate over the table to see if it is an array: | |
| lua_pushnil(L); | |
| while ( lua_next(L, 2) != 0 ) { | |
| if ( lua_type(L, -2) == LUA_TNUMBER ) { | |
| double num = lua_tonumber(L, -2); | |
| if ( num == floor(num) ) { | |
| if ( num > max ) max = num; | |
| } else { | |
| lua_pop(L, 2); | |
| is_array = 0; | |
| break; | |
| } | |
| } else { | |
| lua_pop(L, 2); | |
| is_array = 0; | |
| break; | |
| } | |
| lua_pop(L, 1); | |
| } | |
| if ( is_array ) { | |
| int i; | |
| js_generator_open_array(L); | |
| for ( i=1; i <= max; i++ ) { | |
| lua_pushinteger(L, i); | |
| lua_gettable(L, 2); | |
| // RECURSIVE CALL: | |
| // gen, obj, ?, val, func, gen, val | |
| lua_pushcfunction(L, js_generator_value); | |
| lua_pushvalue(L, 1); | |
| lua_pushvalue(L, -3); | |
| lua_call(L, 2, 0); | |
| lua_pop(L, 1); | |
| } | |
| } else { | |
| js_generator_open_object(L); | |
| lua_pushnil(L); | |
| while ( lua_next(L, 2) != 0 ) { | |
| size_t len; | |
| const char* str; | |
| // gen, obj, ?, key, val, func, gen, key | |
| lua_pushcfunction(L, js_generator_string); | |
| lua_pushvalue(L, 1); | |
| if ( lua_isstring(L, -4) ) { | |
| lua_pushvalue(L, -4); | |
| } else { | |
| // Must coerce into a string: | |
| lua_getglobal(L, "tostring"); | |
| lua_pushvalue(L, -5); | |
| lua_call(L, 1, 1); | |
| } | |
| lua_call(L, 2, 0); | |
| // RECURSIVE CALL: | |
| // gen, obj, ?, key, val, func, gen, val | |
| lua_pushcfunction(L, js_generator_value); | |
| lua_pushvalue(L, 1); | |
| lua_pushvalue(L, -3); | |
| lua_call(L, 2, 0); | |
| lua_pop(L, 1); | |
| } | |
| } | |
| js_generator_close(L); | |
| return 0; | |
| case LUA_TNONE: | |
| lua_pushfstring(L, "MissingArgument: second parameter to js_generator_value() must be defined at %s line %d", type, __FILE__, __LINE__); | |
| default: | |
| lua_pushfstring(L, "Unreachable: js_generator_value passed lua type (%d) not recognized at %s line %d", type, __FILE__, __LINE__); | |
| } | |
| // Shouldn't get here: | |
| lua_error(L); | |
| return 0; | |
| } | |
| typedef struct { | |
| lua_State* L; | |
| int printer_ref; | |
| } js_printer_ctx; | |
| ////////////////////////////////////////////////////////////////////// | |
| static void js_printer(void* void_ctx, const char* str, unsigned int len) { | |
| js_printer_ctx* ctx = (js_printer_ctx*)void_ctx; | |
| lua_State* L = ctx->L; | |
| // refs | |
| lua_getfield(L, LUA_REGISTRYINDEX, "yajl.refs"); | |
| // refs, printer | |
| lua_rawgeti(L, -1, ctx->printer_ref); | |
| if ( lua_isfunction(L, -1) ) { | |
| lua_pushlstring(L, str, len); | |
| // Not sure if yajl can handle longjmp's if this errors... | |
| lua_call(L, 1, 0); | |
| lua_pop(L, 1); | |
| } else { | |
| lua_pop(L, 2); | |
| } | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static int js_generator(lua_State *L) { | |
| yajl_gen_config cfg = { 0, NULL }; | |
| yajl_print_t print = NULL; | |
| void * ctx = NULL; | |
| luaL_checktype(L, 1, LUA_TTABLE); | |
| // {args}, ?, tbl | |
| lua_newtable(L); | |
| // Validate and save in fenv so it isn't gc'ed: | |
| lua_getfield(L, 1, "printer"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| luaL_checktype(L, -1, LUA_TFUNCTION); | |
| lua_pushvalue(L, -1); | |
| // {args}, ?, tbl, printer, printer | |
| lua_setfield(L, -3, "printer"); | |
| js_printer_ctx* print_ctx = (js_printer_ctx*) | |
| lua_newuserdata(L, sizeof(js_printer_ctx)); | |
| // {args}, ?, tbl, printer, printer_ctx | |
| lua_setfield(L, -3, "printer_ctx"); | |
| // {args}, ?, tbl, printer | |
| lua_getfield(L, LUA_REGISTRYINDEX, "yajl.refs"); | |
| // {args}, ?, tbl, printer, refs | |
| lua_insert(L, -2); | |
| // {args}, ?, tbl, refs, printer | |
| print_ctx->printer_ref = luaL_ref(L, -2); | |
| print_ctx->L = L; | |
| print = &js_printer; | |
| ctx = print_ctx; | |
| } | |
| lua_pop(L, 1); | |
| // {args}, ?, tbl | |
| // Get the indent and save so it isn't gc'ed: | |
| lua_getfield(L, 1, "indent"); | |
| if ( ! lua_isnil(L, -1) ) { | |
| cfg.beautify = 1; | |
| cfg.indentString = lua_tostring(L, -1); | |
| lua_setfield(L, -2, "indent"); | |
| } else { | |
| lua_pop(L, 1); | |
| } | |
| // {args}, ?, tbl | |
| // Sucks that yajl's generator doesn't keep track of this for me | |
| // (this is a stack of strings "array" and "object" so I can keep | |
| // track of what to "close"): | |
| lua_newtable(L); | |
| lua_setfield(L, -2, "stack"); | |
| // {args}, ?, tbl | |
| yajl_gen* handle = (yajl_gen*)lua_newuserdata(L, sizeof(yajl_gen)); | |
| *handle = yajl_gen_alloc2(print, &cfg, NULL, ctx); | |
| // {args}, ?, tbl, ud, meta | |
| luaL_getmetatable(L, "yajl.generator.meta"); | |
| lua_setmetatable(L, -2); | |
| // {args}, ?, tbl, ud | |
| lua_insert(L, -2); | |
| // {args}, ?, ud, tbl | |
| lua_setfenv(L, -2); | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static void js_create_parser_mt(lua_State *L) { | |
| luaL_newmetatable(L, "yajl.parser.meta"); // {} | |
| lua_pushcfunction(L, js_parser_delete); | |
| lua_setfield(L, -2, "__gc"); | |
| lua_pop(L, 1); // <empty> | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static void js_create_generator_mt(lua_State *L) { | |
| luaL_newmetatable(L, "yajl.generator.meta"); // {} | |
| lua_pushvalue(L, -1); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, js_generator_delete); | |
| lua_setfield(L, -2, "__gc"); | |
| lua_pushcfunction(L, js_generator_value); | |
| lua_setfield(L, -2, "value"); | |
| lua_pushcfunction(L, js_generator_integer); | |
| lua_setfield(L, -2, "integer"); | |
| lua_pushcfunction(L, js_generator_double); | |
| lua_setfield(L, -2, "double"); | |
| lua_pushcfunction(L, js_generator_number); | |
| lua_setfield(L, -2, "number"); | |
| lua_pushcfunction(L, js_generator_string); | |
| lua_setfield(L, -2, "string"); | |
| lua_pushcfunction(L, js_generator_null); | |
| lua_setfield(L, -2, "null"); | |
| lua_pushcfunction(L, js_generator_boolean); | |
| lua_setfield(L, -2, "boolean"); | |
| lua_pushcfunction(L, js_generator_open_object); | |
| lua_setfield(L, -2, "open_object"); | |
| lua_pushcfunction(L, js_generator_open_array); | |
| lua_setfield(L, -2, "open_array"); | |
| lua_pushcfunction(L, js_generator_close); | |
| lua_setfield(L, -2, "close"); | |
| lua_pop(L, 1); // <empty> | |
| } | |
| static int js_null_tostring(lua_State* L) { | |
| lua_pushstring(L, "null"); | |
| return 1; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| static void js_create_null_mt(lua_State *L) { | |
| luaL_newmetatable(L, "yajl.null.meta"); // {} | |
| lua_pushcfunction(L, js_null_tostring); | |
| lua_setfield(L, -2, "__tostring"); | |
| lua_pop(L, 1); // <empty> | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| LUALIB_API int luaopen_yajl(lua_State *L) { | |
| js_create_parser_mt(L); | |
| js_create_generator_mt(L); | |
| js_create_null_mt(L); | |
| // Create the yajl.refs weak table: | |
| lua_createtable(L, 0, 2); | |
| lua_pushliteral(L, "v"); // tbl, "v" | |
| lua_setfield(L, -2, "__mode"); | |
| lua_pushvalue(L, -1); // tbl, tbl | |
| lua_setmetatable(L, -2); // tbl | |
| lua_setfield(L, LUA_REGISTRYINDEX, "yajl.refs"); | |
| lua_createtable(L, 0, 4); | |
| lua_pushcfunction(L, js_to_string); | |
| lua_setfield(L, -2, "to_string"); | |
| lua_pushcfunction(L, js_to_value); | |
| lua_setfield(L, -2, "to_value"); | |
| lua_pushcfunction(L, js_parser); | |
| lua_setfield(L, -2, "parser"); | |
| lua_pushcfunction(L, js_generator); | |
| lua_setfield(L, -2, "generator"); | |
| js_null = lua_newuserdata(L, 0); | |
| luaL_getmetatable(L, "yajl.null.meta"); | |
| lua_setmetatable(L, -2); | |
| lua_pushvalue(L, -1); | |
| lua_setfield(L, LUA_REGISTRYINDEX, "yajl.null"); | |
| lua_setfield(L, -2, "null"); | |
| return 1; | |
| } |