├── CMakeLists.txt ├── LICENSE.md ├── Makefile ├── README ├── lua_yajl.c ├── rockspecs ├── lua-yajl-2.0-1.rockspec └── lua-yajl-scm-1.rockspec ├── tap.lua ├── test.lua └── yajl.def /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2009 LuaDist. 2 | # Submitted by David Manura 3 | # Redistribution and use of this file is allowed according to the 4 | # terms of the MIT license. 5 | # For details see the COPYRIGHT file distributed with LuaDist. 6 | # Please note that the package source code is licensed under its own 7 | # license. 8 | 9 | PROJECT(lua-yajl C) 10 | CMAKE_MINIMUM_REQUIRED (VERSION 2.6) 11 | 12 | # Basic configurations 13 | SET(INSTALL_CMOD share/lua/cmod CACHE PATH "Directory to install Lua binary modules (configure lua via LUA_CPATH)") 14 | # / configs 15 | 16 | # Find yajl 17 | FIND_LIBRARY (YAJL_LIBRARY NAMES yajl) 18 | FIND_PATH (YAJL_INCLUDE_DIR yajl/yajl_parse.h 19 | ) # Find header 20 | INCLUDE(FindPackageHandleStandardArgs) 21 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(yajl DEFAULT_MSG YAJL_LIBRARY YAJL_INCLUDE_DIR) 22 | # / Find yajl 23 | 24 | # Find lua 25 | FIND_PACKAGE(Lua51 REQUIRED) 26 | # / Find lua 27 | 28 | # Define how to build yajl.so 29 | INCLUDE_DIRECTORIES(${YAJL_INCLUDE_DIR} ${LUA_INCLUDE_DIR}) 30 | ADD_LIBRARY(cmod_yajl MODULE 31 | lua_yajl.c yajl.def) 32 | SET_TARGET_PROPERTIES(cmod_yajl PROPERTIES PREFIX "") 33 | SET_TARGET_PROPERTIES(cmod_yajl PROPERTIES OUTPUT_NAME yajl) 34 | TARGET_LINK_LIBRARIES(cmod_yajl ${LUA_LIBRARIES} ${YAJL_LIBRARY}) 35 | # / build yajl.so 36 | 37 | # Define how to test yajl.so: 38 | INCLUDE(CTest) 39 | FIND_PROGRAM(LUA NAMES lua lua.bat) 40 | ADD_TEST(basic ${LUA} ${CMAKE_CURRENT_SOURCE_DIR}/test.lua ${CMAKE_CURRENT_SOURCE_DIR}/ ${CMAKE_CURRENT_BINARY_DIR}/) 41 | SET_TESTS_PROPERTIES(basic 42 | PROPERTIES 43 | FAIL_REGULAR_EXPRESSION 44 | "not ok") 45 | # / test yajl.so 46 | 47 | # Where to install stuff 48 | INSTALL (TARGETS cmod_yajl DESTINATION ${INSTALL_CMOD}) 49 | # / Where to install. 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | * Author : Brian Maher 4 | * Library : lua_yajl - Lua 5.1 interface to yajl. 5 | 6 | ## The MIT License 7 | 8 | Copyright (c) 2009-2024 Brian Maher 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile is based on LuaSec's Makefile. Thanks to the LuaSec developers. 2 | # Inform the location to intall the modules 3 | LUAPATH = /usr/share/lua/5.1 4 | LUACPATH = /usr/lib/lua/5.1 5 | INCDIR = -I/usr/include/lua5.1 6 | LIBDIR = -L/usr/lib 7 | 8 | # For Mac OS X: set the system version 9 | MACOSX_VERSION = 10.4 10 | 11 | CMOD = yajl.so 12 | OBJS = lua_yajl.o 13 | 14 | LIBS = -lyajl -llua -lm 15 | WARN = -Wall -pedantic 16 | 17 | BSD_CFLAGS = -O2 -fPIC $(WARN) $(INCDIR) $(DEFS) 18 | BSD_LDFLAGS = -O -shared -fPIC $(LIBDIR) 19 | 20 | LNX_CFLAGS = -O2 -fPIC $(WARN) $(INCDIR) $(DEFS) 21 | LNX_LDFLAGS = -O -shared -fPIC $(LIBDIR) 22 | 23 | MAC_ENV = env MACOSX_DEPLOYMENT_TARGET='$(MACVER)' 24 | MAC_CFLAGS = -O2 -fPIC -fno-common $(WARN) $(INCDIR) $(DEFS) 25 | MAC_LDFLAGS = -bundle -undefined dynamic_lookup -fPIC $(LIBDIR) 26 | 27 | CC = gcc 28 | LD = $(MYENV) gcc 29 | CFLAGS = $(MYCFLAGS) 30 | LDFLAGS = $(MYLDFLAGS) 31 | 32 | .PHONY: all clean install none linux bsd macosx 33 | 34 | all: 35 | @echo "Usage: $(MAKE) " 36 | @echo " * linux" 37 | @echo " * bsd" 38 | @echo " * macosx" 39 | 40 | install: $(CMOD) 41 | cp $(CMOD) $(LUACPATH) 42 | 43 | uninstall: 44 | rm $(LUACPATH)/yajl.so 45 | 46 | linux: 47 | @$(MAKE) $(CMOD) MYCFLAGS="$(LNX_CFLAGS)" MYLDFLAGS="$(LNX_LDFLAGS)" INCDIR="$(INCDIR)" LIBDIR="$(LIBDIR)" DEFS="$(DEFS)" 48 | 49 | bsd: 50 | @$(MAKE) $(CMOD) MYCFLAGS="$(BSD_CFLAGS)" MYLDFLAGS="$(BSD_LDFLAGS)" INCDIR="$(INCDIR)" LIBDIR="$(LIBDIR)" DEFS="$(DEFS)" 51 | 52 | macosx: 53 | @$(MAKE) $(CMOD) MYCFLAGS="$(MAC_CFLAGS)" MYLDFLAGS="$(MAC_LDFLAGS)" MYENV="$(MAC_ENV)" INCDIR="$(INCDIR)" LIBDIR="$(LIBDIR)" DEFS="$(DEFS)" 54 | 55 | clean: 56 | rm -f $(OBJS) $(CMOD) 57 | 58 | .c.o: 59 | $(CC) -c $(CFLAGS) $(DEFS) $(INCDIR) -o $@ $< 60 | 61 | $(CMOD): $(OBJS) 62 | $(LD) $(LDFLAGS) $(LIBDIR) $(OBJS) $(LIBS) -o $@ 63 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ********************************************************************** 2 | * Author : Brian Maher 3 | * Library : lua_yajl - Lua 5.1 interface to yajl. 4 | * 5 | * The MIT License 6 | * 7 | * Copyright (c) 2009 Brian Maher 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | ********************************************************************** 27 | 28 | To use this library, you need yajl version 2.x, get it here: 29 | http://lloyd.github.com/yajl/ 30 | 31 | To build this library, you can use CMake (which is how yajl is built): 32 | http://www.cmake.org/cmake/resources/software.html 33 | 34 | ...or you can use lua rocks. 35 | 36 | ...or you can use GNU Make. If the headers and/or library are 37 | installed in non-standard locations, simply set the LIBDIR and/or 38 | INCDIR make variables. For example: 39 | 40 | make macosx \ 41 | LIBDIR='-L/opt/lua/lib -L/opt/yajl/lib' \ 42 | INCDIR='-I/opt/lua/include -I/opt/yajl/include' 43 | 44 | Loading the library: 45 | 46 | If you built the library as a loadable package 47 | [local] yajl = require 'yajl' 48 | 49 | If you compiled the package statically into your application, call 50 | the function "luaopen_yajl(L)". It will create a table with the yajl 51 | functions and leave it on the stack. 52 | 53 | NOTE ON LARGE NUMBERS: 54 | 55 | JSON integers and floating points are handled with the 'double' C 56 | type, which is normally the native number format for Lua. We do 57 | NOT currently do error handling if a number is outside of range, 58 | instead the number will be truncated as specified by the strtod() 59 | C function. At some point in the future perhaps we will add 60 | support for custom error handlers if a double can not accurately 61 | represent the JSON number. (want to write this support!?) 62 | 63 | -- yajl functions -- 64 | 65 | yajl.null 66 | 67 | A light user data object that represents the JSON null value. We 68 | use this instead of nil to represent null since Lua tables can not 69 | have a value of nil (that would delete the key-value pair), and 70 | nil's can not exist at the end of an array. 71 | 72 | If you don't care about streaming, here are two convenience methods: 73 | 74 | string = yajl.to_string(lua_obj) 75 | 76 | Translate a lua object into a JSON representation by creating a 77 | generator and calling the 'value' method with the passed in 78 | lua_obj and returns the result. See generator:value() for more 79 | information about this operation. Note that information loss 80 | may happen in certain situations. For example, empty tables are 81 | always translated into empty arrays. 82 | 83 | lua_obj = yajl.to_value(string) 84 | 85 | Translate a string to a lua object. This is done in C by running 86 | code that is essentially the same as this (but faster): 87 | 88 | function to_value(string) 89 | local result 90 | local stack = { 91 | function(val) result = val end 92 | } 93 | local obj_key 94 | local events = { 95 | value: function(_, val) 96 | stack[#stack](val) 97 | end, 98 | open_array: function() 99 | local arr = {} 100 | stack[#stack](arr) 101 | table.insert(stack, function(val) 102 | table.insert(arr, val) 103 | end) 104 | end, 105 | open_object: function() 106 | local obj = {} 107 | stack[#stack](obj) 108 | table.insert(stack, function(val) 109 | obj[obj_key] = val 110 | end) 111 | end, 112 | object_key: function(_, val) 113 | obj_key = val 114 | end, 115 | close: function() 116 | stack[#stack] = nil 117 | end, 118 | } 119 | 120 | yajl.parser({ events: events })(string) 121 | return result 122 | end 123 | 124 | parser = yajl.parser { 125 | allow_comments: true, 126 | check_utf8: false, 127 | events: { 128 | value: function(events, value, type) ... end, 129 | open_object: function(events) ... end, 130 | object_key: function(events, string) ... end, 131 | open_array: function(events) ... end, 132 | close: function(events, type) ... end, 133 | }, 134 | } 135 | 136 | If allow_comments is true, then both // and /* ... */ javascript 137 | comments are allowed. If check_utf8 is true, then the parser will 138 | make sure all input is valid UTF8. 139 | 140 | The results of this function is a parser function: 141 | 142 | parser(string) 143 | 144 | Call it with a string to be parsed. 145 | 146 | The events table has various callback functions that will be 147 | called when the events happen. All callbacks are passed the 148 | events table as the first argument. Note that the yajl.generator 149 | conforms to this events interface. Here is the details of each 150 | callback function: 151 | 152 | events:value(value, type) 153 | 154 | Called when a JSON string, boolean, integer, double, or null 155 | is found in the input stream. The type parameter is one of 156 | these strings depending on what was found in the input stream: 157 | 158 | "string" - A JSON string was found. 159 | "boolean" - A JSON true or false was found. 160 | "number" - A JSON number was found. 161 | "null" - A JSON null was found. 162 | 163 | Note that nulls are NOT represented as a Lua nil value, 164 | instead they are the yajl.null value. 165 | 166 | events:open_object() 167 | 168 | An open object brace was found. 169 | 170 | events:object_key(string) 171 | 172 | If defined, instead of calling events:value(string, "string") 173 | when an object key is found, this method is called instead. 174 | 175 | events:open_array() 176 | 177 | An open array brace was found. 178 | 179 | events:close(type) 180 | 181 | A close array or object brace was found. Type is the string 182 | "object" or "array" depending on what type is being 183 | terminated. 184 | 185 | generator = yajl.generator { 186 | printer: function(string) print(string) end, 187 | indent: "\t", 188 | } 189 | 190 | Create a new generator with the specified printer callback, 191 | optionally specify a whitespace string to be used for indenting if 192 | you want "pretty" output. The printer must be a function that 193 | takes a single string parameter: 194 | 195 | printer(string) 196 | 197 | The returned value is ignored, string is the text to be 198 | written to the stream. Single characters are "printed" to the 199 | stream, so if writing to a file, be sure it is buffered. 200 | 201 | The returned generator object has the following methods: 202 | 203 | generator = generator:value(lua_object) 204 | 205 | Traverse the lua_object and call the appropriate generator 206 | methods. lua_object can be any of these types: 207 | 208 | number/boolean/string - Generates an appropriate JSON 209 | representation. 210 | 211 | nil/yajl.null - Generates a JSON null representation. 212 | 213 | metatable(lua_object).__gen_json(lua_object, generator) - 214 | If the lua_object has a metatable with a __gen_json 215 | function in the metatable, then the work of generating 216 | a JSON representation is delegated to that function. 217 | 218 | table - If all keys in the table are positive integers, 219 | generates a JSON array representation with element 1 220 | being the first element of the array. Otherwise, it 221 | is reprsented as a JSON object, and all keys are 222 | coorced into strings using tostring() lua function. 223 | The values are recursively traversed in depth first 224 | fashion. All keys that begin with two underscores are 225 | ignored. An empty table is always treated as an empty 226 | array. 227 | 228 | All other types are ignored (thread, userdata) unless there is 229 | an appropriate __gen_json metatable method. 230 | 231 | generator = generator:integer(number) 232 | 233 | Generate an integer JSON representation. 234 | 235 | generator = generator:double(number) 236 | 237 | Generate a floating point JSON representation. 238 | 239 | generator = generator:number(number) 240 | 241 | Generate a number JSON representation. If the number is 242 | infinity, we will generate the special 1e+666 number. If the 243 | number is NAN (not a number), then we generate -0. At least 244 | this way there is minimal information loss (since 1e+666 can 245 | not be represented in a double, that will be re-encoded as 246 | infinity). 247 | 248 | generator = generator:string(string) 249 | 250 | Generate a string JSON representation with proper escaping. 251 | 252 | generator = generator:null() 253 | 254 | Generate a null JSON representation. 255 | 256 | generator = generator:boolean(boolean) 257 | 258 | Generate a boolean JSON representation. 259 | 260 | generator = generator:open_object() 261 | 262 | Begin generating a JSON object. May only be followed with a 263 | call to generator:close() or generator:string(). 264 | 265 | generator = generator:open_array() 266 | 267 | Begin generating an array. 268 | 269 | generator = generator:close() 270 | 271 | End generation of an array or object. 272 | -------------------------------------------------------------------------------- /lua_yajl.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2009-2024 Brian Maher 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #define js_check_generator(L, narg) \ 32 | (yajl_gen*)luaL_checkudata((L), (narg), "yajl.generator.meta") 33 | 34 | static void* js_null; 35 | 36 | #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 502 37 | #define lua_objlen(x, y) lua_rawlen(x, y) 38 | #define lua_setfenv(x, y) lua_setuservalue(x, y) 39 | #define lua_getfenv(x, y) lua_getuservalue(x, y) 40 | #endif 41 | 42 | static int js_generator(lua_State *L); 43 | static int js_generator_value(lua_State *L); 44 | static void js_parser_assert(lua_State* L, 45 | yajl_status status, 46 | yajl_handle* handle, 47 | const unsigned char* json_text, 48 | size_t json_text_len, 49 | const char* file, 50 | int line); 51 | static int got_map_key(lua_State* L); 52 | static int got_map_value(lua_State* L); 53 | 54 | 55 | static double todouble(lua_State* L, const char* val, size_t len) { 56 | /* Convert into number using a temporary */ 57 | char* tmp = (char*)lua_newuserdata(L, len+1); 58 | double num; 59 | memcpy(tmp, val, len); 60 | tmp[len] = '\0'; 61 | num = strtod(tmp, NULL); 62 | /* 63 | if ((num == HUGE_VAL || num == -HUGE_VAL) && 64 | errno == ERANGE) 65 | { 66 | TODO: Add appropriate handling of large numbers by delegating. 67 | } 68 | TODO: How can we tell if there was information loss? aka the 69 | number of significant digits in the string exceeds the 70 | significant digits in the double. 71 | */ 72 | lua_pop(L, 1); 73 | return num; 74 | } 75 | 76 | 77 | static int js_to_string(lua_State *L) { 78 | yajl_gen* gen; 79 | const unsigned char *buf; 80 | size_t len; 81 | 82 | lua_pushcfunction(L, js_generator); 83 | /* convert_me, {extra}, ?, js_gen */ 84 | if ( lua_istable(L, 2) ) { 85 | /* Be sure printer is not defined: */ 86 | lua_pushliteral(L, "printer"); 87 | lua_pushnil(L); 88 | lua_rawset(L, 2); 89 | lua_pushvalue(L, 2); 90 | /* convert_me, {extra}, ?, js_gen, {extra} */ 91 | } else { 92 | lua_newtable(L); 93 | /* convert_me, {extra}, ?, js_gen, {} */ 94 | } 95 | lua_call(L, 1, 1); 96 | /* convert_me, {extra}, ?, gen_ud */ 97 | lua_pushcfunction(L, js_generator_value); 98 | /* convert_me, {extra}, ?, gen_ud, js_gen_val */ 99 | lua_pushvalue(L, -2); 100 | /* convert_me, {extra}, ?, gen_ud, js_gen_val, gen_ud */ 101 | lua_pushvalue(L, 1); 102 | /* convert_me, {extra}, ?, gen_ud, js_gen_val, gen_ud, convert_me */ 103 | lua_call(L, 2, 0); 104 | /* convert_me, {extra}, ?, gen_ud */ 105 | gen = js_check_generator(L, -1); 106 | yajl_gen_get_buf(*gen, &buf, &len); 107 | /* Copy into results: */ 108 | lua_pushlstring(L, (char*)buf, len); 109 | yajl_gen_clear(*gen); 110 | return 1; 111 | } 112 | 113 | /* See STRATEGY section below */ 114 | static int to_value_null(void* ctx) { 115 | lua_State* L = (lua_State*)ctx; 116 | 117 | lua_getfield(L, LUA_REGISTRYINDEX, "yajl.null"); 118 | (lua_tocfunction(L, -2))(L); 119 | 120 | return 1; 121 | } 122 | 123 | /* See STRATEGY section below */ 124 | static int to_value_boolean(void* ctx, int val) { 125 | lua_State* L = (lua_State*)ctx; 126 | 127 | lua_pushboolean(L, val); 128 | (lua_tocfunction(L, -2))(L); 129 | 130 | return 1; 131 | } 132 | 133 | /* See STRATEGY section below */ 134 | static int to_value_number(void* ctx, const char* val, size_t len) { 135 | lua_State* L = (lua_State*)ctx; 136 | lua_pushnumber(L, todouble(L, val, len)); 137 | (lua_tocfunction(L, -2))(L); 138 | 139 | return 1; 140 | } 141 | 142 | /* See STRATEGY section below */ 143 | static int to_value_string(void* ctx, const unsigned char *val, size_t len) { 144 | lua_State* L = (lua_State*)ctx; 145 | 146 | lua_pushlstring(L, (const char*)val, len); 147 | (lua_tocfunction(L, -2))(L); 148 | 149 | return 1; 150 | } 151 | 152 | /* See STRATEGY section below */ 153 | static int got_map_value(lua_State* L) { 154 | /* ..., Table, Key, Func, Value */ 155 | lua_insert(L, -2); 156 | lua_pop(L, 1); 157 | lua_rawset(L, -3); 158 | lua_pushnil(L); /* Store future key here. */ 159 | lua_pushcfunction(L, got_map_key); 160 | 161 | return 0; /* Ignored. */ 162 | } 163 | 164 | /* See STRATEGY section below */ 165 | static int got_map_key(lua_State* L) { 166 | lua_replace(L, -3); 167 | lua_pop(L, 1); 168 | lua_pushcfunction(L, got_map_value); 169 | 170 | return 0; /* Ignored. */ 171 | } 172 | 173 | /* See STRATEGY section below */ 174 | static int got_array_value(lua_State* L) { 175 | /* ..., Table, Integer, Func, Value */ 176 | lua_rawseti(L, -4, lua_tointeger(L, -3)); 177 | lua_pushinteger(L, lua_tointeger(L, -2)+1); 178 | lua_replace(L, -3); 179 | 180 | return 0; /* Ignored. */ 181 | } 182 | 183 | /* See STRATEGY section below */ 184 | static int to_value_start_map(void* ctx) { 185 | lua_State* L = (lua_State*)ctx; 186 | 187 | /* The layout of the stack for "objects" is: 188 | - Table we are appending to. 189 | - Storage for the "last key found". 190 | - Function to call in to_value_* functions. 191 | - Value pushed on the stack by to_value_* functions. 192 | */ 193 | if ( ! lua_checkstack(L, 4) ) { 194 | /* TODO: So far, YAJL seems to be fine with the longjmp, but 195 | perhaps we should do errors a different way? */ 196 | return luaL_error(L, "lua stack overflow"); 197 | } 198 | 199 | lua_newtable(L); 200 | lua_pushnil(L); /* Store future key here. */ 201 | lua_pushcfunction(L, got_map_key); 202 | 203 | return 1; 204 | } 205 | 206 | /* See STRATEGY section below */ 207 | static int to_value_start_array(void* ctx) { 208 | lua_State* L = (lua_State*)ctx; 209 | 210 | /* The layout of the stack for "arrays" is: 211 | - Table we are appending to. 212 | - The index to use for the next insertion. 213 | - Function to call in to_value_* functions. 214 | - Value pushed on the stack by to_value_* functions. 215 | */ 216 | if ( ! lua_checkstack(L, 4) ) { 217 | /* TODO: So far, YAJL seems to be fine with the longjmp, but 218 | perhaps we should do errors a different way? */ 219 | return luaL_error(L, "lua stack overflow"); 220 | } 221 | 222 | lua_newtable(L); 223 | lua_pushinteger(L, 1); 224 | lua_pushcfunction(L, got_array_value); 225 | 226 | return 1; 227 | } 228 | 229 | /* See STRATEGY section below */ 230 | static int to_value_end(void* ctx) { 231 | lua_State* L = (lua_State*)ctx; 232 | 233 | /* Simply pop the stack and call the cfunction: */ 234 | lua_pop(L, 2); 235 | (lua_tocfunction(L, -2))(L); 236 | 237 | return 1; 238 | } 239 | 240 | /* See STRATEGY section below */ 241 | static int noop(lua_State* L) { 242 | return 0; 243 | } 244 | 245 | /* See STRATEGY section below */ 246 | static yajl_callbacks js_to_value_callbacks = { 247 | to_value_null, 248 | to_value_boolean, 249 | NULL, 250 | NULL, 251 | to_value_number, 252 | to_value_string, 253 | to_value_start_map, 254 | to_value_string, 255 | to_value_end, 256 | to_value_start_array, 257 | to_value_end, 258 | }; 259 | 260 | 261 | /* STRATEGY: 262 | * 263 | * Each of the js_to_value_callbacks perform these actions: 264 | * 265 | * [1] Push a new value onto the top of the Lua stack. 266 | * 267 | * [2] Call the function that was at the top of the Lua stack before 268 | * step [1] occurred. 269 | * 270 | * The purpose of the function call in [2] is to take the value at the 271 | * top of the stack and store it in the appropriate location. 272 | * Initially, the function is the noop (no operation) function which 273 | * does nothing. Therefore we know that the final result is on the 274 | * top of the Lua stack. 275 | * 276 | * The to_value_start_map and to_value_start_array callbacks are 277 | * different since they need to use a bit of the Lua stack to store 278 | * some state information. When these callbacks are ran, they perform 279 | * these actions: 280 | * 281 | * [a] Push a new table which will represent the final "array" or 282 | * "object" onto the top of the Lua stack. 283 | * 284 | * [b] Allocate space for the "key" (in the case of arrays, this is 285 | * the index into the array to use as part of the next insertion) 286 | * 287 | * [c] Push the got_array_value or got_map_key function. 288 | * 289 | * The got_array_value function will take the value at the top of the 290 | * stack and insert it into the table created in step [a]. It will 291 | * then increment the index created in step [b]. As a final step, it 292 | * removes the value at the top of the stack. 293 | * 294 | * The got_map_key function simply takes the value at the top of the 295 | * stack and stores it in the space allocated by step [b] above. It 296 | * then replaces the function pushed onto the stack by step [c] with 297 | * the got_map_value function. As a final step, it removes the value 298 | * at the top of the stack. 299 | * 300 | * The got_map_value function takes the value at the top of the stack 301 | * and inserts it into the table created in step [a] with the key 302 | * whose space was allocated in step [b]. The function pushed onto 303 | * the stack by step [c] is then restored back to the got_map_key 304 | * function. As a final step, it removes the value at the top of the 305 | * stack. 306 | */ 307 | static int js_to_value(lua_State *L) { 308 | yajl_handle* handle; 309 | size_t len; 310 | const unsigned char* buff = (const unsigned char*) luaL_checklstring(L, 1, &len); 311 | 312 | if ( NULL == buff ) return 0; 313 | 314 | // Ideally we would stack allocate the yajl_handle, however we need 315 | // to ensure yajl_free is called on the handle in case lua_error() 316 | // is called (which may happen if js_parser_assert() fails). 317 | handle = (yajl_handle*)lua_newuserdata(L, sizeof(yajl_handle)); 318 | 319 | *handle = yajl_alloc(&js_to_value_callbacks, NULL, (void*)L); 320 | luaL_getmetatable(L, "yajl.parser.meta"); 321 | lua_setmetatable(L, -2); 322 | 323 | lua_pushcfunction(L, noop); 324 | 325 | if ( lua_istable(L, 2) ) { 326 | lua_getfield(L, 2, "allow_comments"); 327 | if ( ! lua_isnil(L, -1) ) { 328 | yajl_config(*handle, yajl_allow_comments, lua_toboolean(L, -1)); 329 | } 330 | lua_pop(L, 1); 331 | 332 | lua_getfield(L, 2, "check_utf8"); 333 | if ( ! lua_isnil(L, -1) ) { 334 | yajl_config(*handle, yajl_dont_validate_strings, !lua_toboolean(L, -1)); 335 | } 336 | lua_pop(L, 1); 337 | } 338 | 339 | js_parser_assert(L, 340 | yajl_parse(*handle, buff, len), 341 | handle, 342 | buff, 343 | len, 344 | __FILE__, 345 | __LINE__); 346 | 347 | js_parser_assert(L, 348 | yajl_complete_parse(*handle), 349 | handle, 350 | buff, 351 | len, 352 | __FILE__, 353 | __LINE__); 354 | 355 | return 1; 356 | } 357 | 358 | static int js_parser_null(void *ctx) { 359 | lua_State *L=(lua_State*)ctx; 360 | lua_getfield(L, lua_upvalueindex(2), "value"); 361 | if ( ! lua_isnil(L, -1) ) { 362 | lua_pushvalue(L, lua_upvalueindex(2)); 363 | lua_getfield(L, LUA_REGISTRYINDEX, "yajl.null"); 364 | lua_pushliteral(L, "null"); 365 | lua_call(L, 3, 0); 366 | } else { 367 | lua_pop(L, 1); 368 | } 369 | 370 | return 1; 371 | } 372 | 373 | static int js_parser_boolean(void *ctx, int val) { 374 | lua_State *L=(lua_State*)ctx; 375 | 376 | lua_getfield(L, lua_upvalueindex(2), "value"); 377 | if ( ! lua_isnil(L, -1) ) { 378 | lua_pushvalue(L, lua_upvalueindex(2)); 379 | lua_pushboolean(L, val); 380 | lua_pushliteral(L, "boolean"); 381 | lua_call(L, 3, 0); 382 | } else { 383 | lua_pop(L, 1); 384 | } 385 | 386 | return 1; 387 | } 388 | 389 | static int js_parser_number(void *ctx, const char* buffer, size_t buffer_len) { 390 | lua_State *L=(lua_State*)ctx; 391 | 392 | lua_getfield(L, lua_upvalueindex(2), "value"); 393 | if ( ! lua_isnil(L, -1) ) { 394 | lua_pushvalue(L, lua_upvalueindex(2)); 395 | lua_pushnumber(L, todouble(L, buffer, buffer_len)); 396 | lua_pushliteral(L, "number"); 397 | lua_call(L, 3, 0); 398 | } else { 399 | lua_pop(L, 1); 400 | } 401 | 402 | return 1; 403 | } 404 | 405 | static int js_parser_string(void *ctx, const unsigned char *val, size_t len) { 406 | lua_State *L=(lua_State*)ctx; 407 | 408 | lua_getfield(L, lua_upvalueindex(2), "value"); 409 | if ( ! lua_isnil(L, -1) ) { 410 | lua_pushvalue(L, lua_upvalueindex(2)); 411 | lua_pushlstring(L, (const char*)val, len); 412 | lua_pushliteral(L, "string"); 413 | lua_call(L, 3, 0); 414 | } else { 415 | lua_pop(L, 1); 416 | } 417 | 418 | return 1; 419 | } 420 | 421 | static int js_parser_start_map(void *ctx) { 422 | lua_State *L=(lua_State*)ctx; 423 | 424 | lua_getfield(L, lua_upvalueindex(2), "open_object"); 425 | if ( ! lua_isnil(L, -1) ) { 426 | lua_pushvalue(L, lua_upvalueindex(2)); 427 | lua_call(L, 1, 0); 428 | } else { 429 | lua_pop(L, 1); 430 | } 431 | 432 | return 1; 433 | } 434 | 435 | static int js_parser_map_key(void *ctx, const unsigned char *val, size_t len) { 436 | lua_State *L=(lua_State*)ctx; 437 | 438 | /* TODO: Do we want to fall-back to calling "value"? */ 439 | lua_getfield(L, lua_upvalueindex(2), "object_key"); 440 | if ( ! lua_isnil(L, -1) ) { 441 | lua_pushvalue(L, lua_upvalueindex(2)); 442 | lua_pushlstring(L, (const char*)val, len); 443 | lua_call(L, 2, 0); 444 | } else { 445 | lua_pop(L, 1); 446 | } 447 | 448 | return 1; 449 | } 450 | 451 | static int js_parser_end_map(void *ctx) { 452 | lua_State *L=(lua_State*)ctx; 453 | 454 | lua_getfield(L, lua_upvalueindex(2), "close"); 455 | if ( ! lua_isnil(L, -1) ) { 456 | lua_pushvalue(L, lua_upvalueindex(2)); 457 | lua_pushliteral(L, "object"); 458 | lua_call(L, 2, 0); 459 | } else { 460 | lua_pop(L, 1); 461 | } 462 | 463 | return 1; 464 | } 465 | 466 | static int js_parser_start_array(void *ctx) { 467 | lua_State *L=(lua_State*)ctx; 468 | 469 | lua_getfield(L, lua_upvalueindex(2), "open_array"); 470 | if ( ! lua_isnil(L, -1) ) { 471 | lua_pushvalue(L, lua_upvalueindex(2)); 472 | lua_call(L, 1, 0); 473 | } else { 474 | lua_pop(L, 1); 475 | } 476 | 477 | return 1; 478 | } 479 | 480 | static int js_parser_end_array(void *ctx) { 481 | lua_State *L=(lua_State*)ctx; 482 | 483 | lua_getfield(L, lua_upvalueindex(2), "close"); 484 | if ( ! lua_isnil(L, -1) ) { 485 | lua_pushvalue(L, lua_upvalueindex(2)); 486 | lua_pushliteral(L, "array"); 487 | lua_call(L, 2, 0); 488 | } else { 489 | lua_pop(L, 1); 490 | } 491 | 492 | return 1; 493 | } 494 | 495 | static yajl_callbacks js_parser_callbacks = { 496 | js_parser_null, 497 | js_parser_boolean, 498 | NULL, 499 | NULL, 500 | js_parser_number, 501 | js_parser_string, 502 | js_parser_start_map, 503 | js_parser_map_key, 504 | js_parser_end_map, 505 | js_parser_start_array, 506 | js_parser_end_array 507 | }; 508 | 509 | static void js_parser_assert(lua_State* L, 510 | yajl_status status, 511 | yajl_handle* handle, 512 | const unsigned char* json_text, 513 | size_t json_text_len, 514 | const char* file, 515 | int line) 516 | { 517 | int verbose = 1; 518 | unsigned char* msg; 519 | 520 | switch ( status ) { 521 | case yajl_status_ok: 522 | return; 523 | case yajl_status_client_canceled: 524 | lua_pushfstring(L, "Unreachable: yajl_status_client_canceled should never be returned since all callbacks return true at %s line %d", 525 | file, line); 526 | break; 527 | case yajl_status_error: 528 | msg = yajl_get_error(*handle, verbose, json_text, json_text_len); 529 | lua_pushfstring(L, "InvalidJSONInput: %s at %s line %d", msg, file, line); 530 | yajl_free_error(*handle, msg); 531 | break; 532 | } 533 | lua_error(L); 534 | } 535 | 536 | static int js_parser_parse(lua_State *L) { 537 | yajl_handle* handle = (yajl_handle*) 538 | lua_touserdata(L, lua_upvalueindex(1)); 539 | if ( lua_isnil(L, 1) ) { 540 | js_parser_assert(L, 541 | yajl_complete_parse(*handle), 542 | handle, 543 | NULL, 544 | 0, 545 | __FILE__, 546 | __LINE__); 547 | } else { 548 | size_t len; 549 | const unsigned char* buff = (const unsigned char*) luaL_checklstring(L, 1, &len); 550 | if ( NULL == buff ) return 0; 551 | js_parser_assert(L, 552 | yajl_parse(*handle, buff, len), 553 | handle, 554 | buff, 555 | len, 556 | __FILE__, 557 | __LINE__); 558 | } 559 | return 0; 560 | } 561 | 562 | static int js_parser_delete(lua_State *L) { 563 | luaL_checktype(L, 1, LUA_TUSERDATA); 564 | yajl_free(*(yajl_handle*)lua_touserdata(L, 1)); 565 | return 0; 566 | } 567 | 568 | static int js_parser(lua_State *L) { 569 | yajl_handle* handle; 570 | 571 | luaL_checktype(L, 1, LUA_TTABLE); 572 | 573 | handle = (yajl_handle*)lua_newuserdata(L, sizeof(yajl_handle)); 574 | 575 | *handle = yajl_alloc(&js_parser_callbacks, NULL, (void*)L); 576 | luaL_getmetatable(L, "yajl.parser.meta"); 577 | lua_setmetatable(L, -2); 578 | 579 | lua_getfield(L, 1, "allow_comments"); 580 | if ( ! lua_isnil(L, -1) ) { 581 | yajl_config(*handle, yajl_allow_comments, lua_toboolean(L, -1)); 582 | } 583 | lua_pop(L, 1); 584 | 585 | lua_getfield(L, 1, "check_utf8"); 586 | if ( ! lua_isnil(L, -1) ) { 587 | yajl_config(*handle, yajl_dont_validate_strings, !lua_toboolean(L, -1)); 588 | } 589 | lua_pop(L, 1); 590 | 591 | lua_getfield(L, 1, "events"); 592 | 593 | /* Create callback function that calls yajl_parse[_complete]() 594 | 595 | upvalue(1) = yajl_handle* 596 | upvalue(2) = events table */ 597 | lua_pushcclosure(L, &js_parser_parse, 2); 598 | 599 | return 1; 600 | } 601 | 602 | static int js_generator_delete(lua_State *L) { 603 | yajl_gen* handle = js_check_generator(L, 1); 604 | yajl_gen_free(*handle); 605 | return 0; 606 | } 607 | 608 | static void js_generator_assert(lua_State *L, 609 | yajl_gen_status status, 610 | const char* file, 611 | int line) 612 | { 613 | switch ( status ) { 614 | case yajl_gen_status_ok: 615 | case yajl_gen_generation_complete: 616 | return; 617 | case yajl_gen_keys_must_be_strings: 618 | 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); 619 | break; 620 | case yajl_max_depth_exceeded: 621 | lua_pushfstring(L, "StackOverflow: YAJL's max generation depth was exceeded at %s line %d", file, line); 622 | break; 623 | case yajl_gen_in_error_state: 624 | lua_pushfstring(L, "AlreadyInError: generator method was called when the generator is already in an error state at %s line %d", file, line); 625 | break; 626 | default: 627 | lua_pushfstring(L, "Unreachable: yajl_gen_status (%d) not recognized at %s line %d", status, file, line); 628 | break; 629 | } 630 | lua_error(L); 631 | } 632 | 633 | static int js_generator_integer(lua_State *L) { 634 | js_generator_assert(L, 635 | yajl_gen_integer(*js_check_generator(L, 1), 636 | luaL_checkinteger(L, 2)), 637 | __FILE__, __LINE__); 638 | return 0; 639 | } 640 | 641 | static int js_generator_double(lua_State *L) { 642 | js_generator_assert(L, 643 | yajl_gen_double(*js_check_generator(L, 1), 644 | luaL_checknumber(L, 2)), 645 | __FILE__, __LINE__); 646 | return 0; 647 | } 648 | 649 | static int js_generator_number(lua_State *L) { 650 | 651 | /* It would be better to make it so an arbitrary string can be 652 | used here, however we would then need to validate that the 653 | generated string is a JSON number which is a bit beyond scope 654 | at this point. Perhaps in the future we will loosen this 655 | restriction, it is always easier to loosen restrictions than it 656 | is to make new restrictions that break other people's code. */ 657 | double num = luaL_checknumber(L, 2); 658 | 659 | size_t len; 660 | const char* str; 661 | 662 | /* These are special cases, not sure how better to represent them 663 | :-(. */ 664 | if ( num == HUGE_VAL ) { 665 | str = "1e+666"; 666 | len = 6; 667 | } else if ( num == -HUGE_VAL ) { 668 | str = "-1e+666"; 669 | len = 7; 670 | } else if ( isnan(num) ) { 671 | str = "-0"; 672 | len = 2; 673 | } else { 674 | str = luaL_checklstring(L, 2, &len); 675 | } 676 | js_generator_assert(L, 677 | yajl_gen_number(*js_check_generator(L, 1), 678 | str, len), 679 | __FILE__, __LINE__); 680 | return 0; 681 | } 682 | 683 | static int js_generator_string(lua_State *L) { 684 | size_t len; 685 | const unsigned char* str = (const unsigned char*)luaL_checklstring(L, 2, &len); 686 | js_generator_assert(L, 687 | yajl_gen_string(*js_check_generator(L, 1), 688 | str, len), 689 | __FILE__, __LINE__); 690 | return 0; 691 | } 692 | 693 | static int js_generator_null(lua_State *L) { 694 | js_generator_assert(L, 695 | yajl_gen_null(*js_check_generator(L, 1)), 696 | __FILE__, __LINE__); 697 | return 0; 698 | } 699 | 700 | static int js_generator_boolean(lua_State *L) { 701 | luaL_checktype(L, 2, LUA_TBOOLEAN); 702 | js_generator_assert(L, 703 | yajl_gen_bool(*js_check_generator(L, 1), 704 | lua_toboolean(L, 2)), 705 | __FILE__, __LINE__); 706 | return 0; 707 | } 708 | 709 | #define JS_OPEN_OBJECT 1 710 | #define JS_OPEN_ARRAY 2 711 | 712 | static int js_generator_open_object(lua_State *L) { 713 | js_generator_assert(L, 714 | yajl_gen_map_open(*js_check_generator(L, 1)), 715 | __FILE__, __LINE__); 716 | /* Why doesn't yajl_gen keep track of this!? */ 717 | lua_getfenv(L, 1); 718 | lua_getfield(L, -1, "stack"); 719 | lua_pushinteger(L, JS_OPEN_OBJECT); 720 | lua_rawseti(L, -2, lua_objlen(L, -2) + 1); 721 | return 0; 722 | } 723 | 724 | static int js_generator_open_array(lua_State *L) { 725 | js_generator_assert(L, 726 | yajl_gen_array_open(*js_check_generator(L, 1)), 727 | __FILE__, __LINE__); 728 | /* Why doesn't yajl_gen keep track of this!? */ 729 | lua_getfenv(L, 1); 730 | lua_getfield(L, -1, "stack"); 731 | lua_pushinteger(L, JS_OPEN_ARRAY); 732 | lua_rawseti(L, -2, lua_objlen(L, -2) + 1); 733 | return 0; 734 | } 735 | 736 | static int js_generator_close(lua_State *L) { 737 | lua_Integer type; 738 | 739 | /* Why doesn't yajl_gen keep track of this!? */ 740 | lua_getfenv(L, 1); 741 | lua_getfield(L, -1, "stack"); 742 | lua_rawgeti(L, -1, lua_objlen(L, -1)); 743 | if ( lua_isnil(L, -1) ) { 744 | lua_pushfstring(L, "StackUnderflow: Attempt to call close() when no array or object has been opened at %s line %d", __FILE__, __LINE__); 745 | lua_error(L); 746 | } 747 | type = lua_tointeger(L, -1); 748 | switch ( type ) { 749 | case JS_OPEN_OBJECT: 750 | js_generator_assert(L, 751 | yajl_gen_map_close(*js_check_generator(L, 1)), 752 | __FILE__, __LINE__); 753 | break; 754 | case JS_OPEN_ARRAY: 755 | js_generator_assert(L, 756 | yajl_gen_array_close(*js_check_generator(L, 1)), 757 | __FILE__, __LINE__); 758 | break; 759 | default: 760 | lua_pushfstring(L, "Unreachable: internal 'stack' contained invalid integer (%d) at %s line %d", type, __FILE__, __LINE__); 761 | lua_error(L); 762 | } 763 | /* delete the top of the "stack": */ 764 | lua_pop(L, 1); 765 | lua_pushnil(L); 766 | lua_rawseti(L, -2, lua_objlen(L, -2)); 767 | 768 | return 0; 769 | } 770 | 771 | static int js_generator_value(lua_State *L) { 772 | int max; 773 | int is_array; 774 | int type = lua_type(L, 2); 775 | 776 | switch ( type ) { 777 | case LUA_TNIL: 778 | return js_generator_null(L); 779 | case LUA_TNUMBER: 780 | return js_generator_number(L); 781 | case LUA_TBOOLEAN: 782 | return js_generator_boolean(L); 783 | case LUA_TSTRING: 784 | return js_generator_string(L); 785 | case LUA_TUSERDATA: 786 | if ( lua_topointer(L, 2) == js_null ) { 787 | return js_generator_null(L); 788 | } 789 | // FALL THRU 790 | case LUA_TLIGHTUSERDATA: 791 | case LUA_TTABLE: 792 | case LUA_TFUNCTION: 793 | case LUA_TTHREAD: 794 | if ( luaL_getmetafield(L, 2, "__gen_json") ) { 795 | if ( lua_isfunction(L, -1) ) { 796 | lua_settop(L, 3); /* gen, obj, func */ 797 | lua_insert(L, 1); /* func, gen, obj */ 798 | lua_insert(L, 2); /* func, obj, gen */ 799 | lua_call(L, 2, 0); 800 | return 0; 801 | } 802 | lua_pop(L, 1); 803 | } 804 | 805 | /* Simply ignore it, perhaps we should warn? */ 806 | if ( type != LUA_TTABLE ) { 807 | /* Must coerce into a string: */ 808 | lua_getglobal(L, "tostring"); 809 | lua_insert(L, 2); 810 | lua_call(L, 1, 1); 811 | return js_generator_string(L); 812 | } 813 | 814 | max = 0; 815 | is_array = 1; 816 | 817 | /* First iterate over the table to see if it is an array: */ 818 | lua_pushnil(L); 819 | while ( lua_next(L, 2) != 0 ) { 820 | if ( lua_type(L, -2) == LUA_TNUMBER ) { 821 | double num = lua_tonumber(L, -2); 822 | if ( num == floor(num) ) { 823 | if ( num > max ) max = num; 824 | } else { 825 | lua_pop(L, 2); 826 | is_array = 0; 827 | break; 828 | } 829 | } else { 830 | lua_pop(L, 2); 831 | is_array = 0; 832 | break; 833 | } 834 | lua_pop(L, 1); 835 | } 836 | 837 | if ( is_array ) { 838 | int i; 839 | js_generator_open_array(L); 840 | for ( i=1; i <= max; i++ ) { 841 | lua_pushinteger(L, i); 842 | lua_gettable(L, 2); 843 | 844 | /* RECURSIVE CALL: 845 | gen, obj, ?, val, func, gen, val */ 846 | lua_pushcfunction(L, js_generator_value); 847 | lua_pushvalue(L, 1); 848 | lua_pushvalue(L, -3); 849 | lua_call(L, 2, 0); 850 | 851 | lua_pop(L, 1); 852 | } 853 | } else { 854 | js_generator_open_object(L); 855 | 856 | lua_pushnil(L); 857 | while ( lua_next(L, 2) != 0 ) { 858 | /* gen, obj, ?, key, val, func, gen, key */ 859 | lua_pushcfunction(L, js_generator_string); 860 | lua_pushvalue(L, 1); 861 | if ( lua_isstring(L, -4) ) { 862 | lua_pushvalue(L, -4); 863 | } else { 864 | /* Must coerce into a string: */ 865 | lua_getglobal(L, "tostring"); 866 | lua_pushvalue(L, -5); 867 | lua_call(L, 1, 1); 868 | } 869 | lua_call(L, 2, 0); 870 | 871 | 872 | /* RECURSIVE CALL: 873 | gen, obj, ?, key, val, func, gen, val */ 874 | lua_pushcfunction(L, js_generator_value); 875 | lua_pushvalue(L, 1); 876 | lua_pushvalue(L, -3); 877 | lua_call(L, 2, 0); 878 | 879 | lua_pop(L, 1); 880 | } 881 | } 882 | js_generator_close(L); 883 | return 0; 884 | case LUA_TNONE: 885 | lua_pushfstring(L, "MissingArgument: second parameter to js_generator_value() must be defined at %s line %d", type, __FILE__, __LINE__); 886 | break; 887 | default: 888 | lua_pushfstring(L, "Unreachable: js_generator_value passed lua type (%d) not recognized at %s line %d", type, __FILE__, __LINE__); 889 | } 890 | /* Shouldn't get here: */ 891 | lua_error(L); 892 | return 0; 893 | } 894 | 895 | typedef struct { 896 | lua_State* L; 897 | int printer_ref; 898 | } js_printer_ctx; 899 | 900 | static void js_printer(void* void_ctx, const char* str, size_t len) { 901 | js_printer_ctx* ctx = (js_printer_ctx*)void_ctx; 902 | lua_State* L = ctx->L; 903 | 904 | /* refs */ 905 | lua_getfield(L, LUA_REGISTRYINDEX, "yajl.refs"); 906 | /* refs, printer */ 907 | lua_rawgeti(L, -1, ctx->printer_ref); 908 | if ( lua_isfunction(L, -1) ) { 909 | lua_pushlstring(L, str, len); 910 | /* Not sure if yajl can handle longjmp's if this errors... */ 911 | lua_call(L, 1, 0); 912 | lua_pop(L, 1); 913 | } else { 914 | lua_pop(L, 2); 915 | } 916 | } 917 | 918 | static int js_generator(lua_State *L) { 919 | yajl_print_t print = NULL; 920 | void * ctx = NULL; 921 | yajl_gen* handle; 922 | 923 | luaL_checktype(L, 1, LUA_TTABLE); 924 | 925 | /* {args}, ?, tbl */ 926 | lua_newtable(L); 927 | 928 | /* Validate and save in registry so it isn't gc'ed: */ 929 | lua_getfield(L, 1, "printer"); 930 | if ( ! lua_isnil(L, -1) ) { 931 | js_printer_ctx* print_ctx; 932 | 933 | luaL_checktype(L, -1, LUA_TFUNCTION); 934 | 935 | lua_pushvalue(L, -1); 936 | 937 | /* {args}, ?, tbl, printer, printer */ 938 | lua_setfield(L, -3, "printer"); 939 | 940 | print_ctx = (js_printer_ctx*) 941 | lua_newuserdata(L, sizeof(js_printer_ctx)); 942 | /* {args}, ?, tbl, printer, printer_ctx */ 943 | 944 | lua_setfield(L, -3, "printer_ctx"); 945 | /* {args}, ?, tbl, printer */ 946 | 947 | lua_getfield(L, LUA_REGISTRYINDEX, "yajl.refs"); 948 | /* {args}, ?, tbl, printer, refs */ 949 | lua_insert(L, -2); 950 | /* {args}, ?, tbl, refs, printer */ 951 | print_ctx->printer_ref = luaL_ref(L, -2); 952 | print_ctx->L = L; 953 | print = &js_printer; 954 | ctx = print_ctx; 955 | } 956 | lua_pop(L, 1); 957 | /* {args}, ?, tbl */ 958 | 959 | /* Sucks that yajl's generator doesn't keep track of this for me 960 | (this is a stack of strings "array" and "object" so I can keep 961 | track of what to "close"): */ 962 | lua_newtable(L); 963 | lua_setfield(L, -2, "stack"); 964 | 965 | /* {args}, ?, tbl */ 966 | handle = (yajl_gen*)lua_newuserdata(L, sizeof(yajl_gen)); 967 | *handle = yajl_gen_alloc(NULL); 968 | /* {args}, ?, tbl, ud */ 969 | 970 | if ( print ) { 971 | yajl_gen_config(*handle, yajl_gen_print_callback, print, ctx); 972 | } 973 | 974 | /* Get the indent and save so it isn't gc'ed: */ 975 | lua_getfield(L, 1, "indent"); 976 | if ( ! lua_isnil(L, -1) ) { 977 | yajl_gen_config(*handle, yajl_gen_beautify, 1); 978 | yajl_gen_config(*handle, yajl_gen_indent_string, lua_tostring(L, -1)); 979 | lua_setfield(L, -3, "indent"); 980 | } else { 981 | lua_pop(L, 1); 982 | } 983 | /* {args}, ?, tbl, ud */ 984 | 985 | /* {args}, ?, tbl, ud, meta */ 986 | luaL_getmetatable(L, "yajl.generator.meta"); 987 | lua_setmetatable(L, -2); 988 | /* {args}, ?, tbl, ud */ 989 | 990 | lua_insert(L, -2); 991 | /* {args}, ?, ud, tbl */ 992 | lua_setfenv(L, -2); 993 | 994 | return 1; 995 | } 996 | 997 | static void js_create_parser_mt(lua_State *L) { 998 | luaL_newmetatable(L, "yajl.parser.meta"); 999 | 1000 | lua_pushcfunction(L, js_parser_delete); 1001 | lua_setfield(L, -2, "__gc"); 1002 | 1003 | lua_pop(L, 1); 1004 | } 1005 | 1006 | static void js_create_generator_mt(lua_State *L) { 1007 | luaL_newmetatable(L, "yajl.generator.meta"); 1008 | 1009 | lua_pushvalue(L, -1); 1010 | lua_setfield(L, -2, "__index"); 1011 | 1012 | lua_pushcfunction(L, js_generator_delete); 1013 | lua_setfield(L, -2, "__gc"); 1014 | 1015 | lua_pushcfunction(L, js_generator_value); 1016 | lua_setfield(L, -2, "value"); 1017 | 1018 | lua_pushcfunction(L, js_generator_integer); 1019 | lua_setfield(L, -2, "integer"); 1020 | 1021 | lua_pushcfunction(L, js_generator_double); 1022 | lua_setfield(L, -2, "double"); 1023 | 1024 | lua_pushcfunction(L, js_generator_number); 1025 | lua_setfield(L, -2, "number"); 1026 | 1027 | lua_pushcfunction(L, js_generator_string); 1028 | lua_setfield(L, -2, "string"); 1029 | 1030 | lua_pushcfunction(L, js_generator_null); 1031 | lua_setfield(L, -2, "null"); 1032 | 1033 | lua_pushcfunction(L, js_generator_boolean); 1034 | lua_setfield(L, -2, "boolean"); 1035 | 1036 | lua_pushcfunction(L, js_generator_open_object); 1037 | lua_setfield(L, -2, "open_object"); 1038 | 1039 | lua_pushcfunction(L, js_generator_open_array); 1040 | lua_setfield(L, -2, "open_array"); 1041 | 1042 | lua_pushcfunction(L, js_generator_close); 1043 | lua_setfield(L, -2, "close"); 1044 | 1045 | lua_pop(L, 1); 1046 | } 1047 | 1048 | static int js_null_tostring(lua_State* L) { 1049 | lua_pushstring(L, "null"); 1050 | return 1; 1051 | } 1052 | 1053 | static void js_create_null_mt(lua_State *L) { 1054 | luaL_newmetatable(L, "yajl.null.meta"); 1055 | 1056 | lua_pushcfunction(L, js_null_tostring); 1057 | lua_setfield(L, -2, "__tostring"); 1058 | 1059 | lua_pop(L, 1); 1060 | } 1061 | 1062 | LUALIB_API int luaopen_yajl(lua_State *L) { 1063 | js_create_parser_mt(L); 1064 | js_create_generator_mt(L); 1065 | js_create_null_mt(L); 1066 | 1067 | /* Create the yajl.refs weak table: */ 1068 | lua_createtable(L, 0, 2); 1069 | lua_pushliteral(L, "v"); /* tbl, "v" */ 1070 | lua_setfield(L, -2, "__mode"); 1071 | lua_pushvalue(L, -1); /* tbl, tbl */ 1072 | lua_setmetatable(L, -2); /* tbl */ 1073 | lua_setfield(L, LUA_REGISTRYINDEX, "yajl.refs"); 1074 | 1075 | lua_createtable(L, 0, 4); 1076 | 1077 | lua_pushcfunction(L, js_to_string); 1078 | lua_setfield(L, -2, "to_string"); 1079 | 1080 | lua_pushcfunction(L, js_to_value); 1081 | lua_setfield(L, -2, "to_value"); 1082 | 1083 | lua_pushcfunction(L, js_parser); 1084 | lua_setfield(L, -2, "parser"); 1085 | 1086 | lua_pushcfunction(L, js_generator); 1087 | lua_setfield(L, -2, "generator"); 1088 | 1089 | js_null = lua_newuserdata(L, 0); 1090 | luaL_getmetatable(L, "yajl.null.meta"); 1091 | lua_setmetatable(L, -2); 1092 | 1093 | lua_pushvalue(L, -1); 1094 | lua_setfield(L, LUA_REGISTRYINDEX, "yajl.null"); 1095 | 1096 | lua_setfield(L, -2, "null"); 1097 | 1098 | return 1; 1099 | } 1100 | -------------------------------------------------------------------------------- /rockspecs/lua-yajl-2.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-yajl" 2 | version = "2.0-1" 3 | source = { 4 | url = "git://github.com/brimworks/lua-yajl.git", 5 | branch = "v2.0", 6 | } 7 | description = { 8 | summary = "Integrate the yajl JSON library with Lua.", 9 | homepage = "http://github.com/brimworks/lua-yajl", 10 | license = "MIT/X11", 11 | } 12 | external_dependencies = { 13 | YAJL = { 14 | header = "yajl/yajl_parse.h", 15 | library = "yajl", 16 | }, 17 | } 18 | dependencies = { 19 | "lua >= 5.1", 20 | } 21 | build = { 22 | type = "builtin", 23 | modules = { 24 | yajl = { 25 | sources = { "lua_yajl.c" }, 26 | libraries = { "yajl" }, 27 | }, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /rockspecs/lua-yajl-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-yajl" 2 | version = "scm-1" 3 | source = { 4 | url = "git://github.com/brimworks/lua-yajl.git", 5 | } 6 | description = { 7 | summary = "Integrate the yajl JSON library with Lua.", 8 | homepage = "http://github.com/brimworks/lua-yajl", 9 | license = "MIT/X11", 10 | } 11 | external_dependencies = { 12 | YAJL = { 13 | header = "yajl/yajl_parse.h", 14 | library = "yajl", 15 | }, 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | yajl = { 24 | sources = { "lua_yajl.c" }, 25 | libraries = { "yajl" }, 26 | }, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /tap.lua: -------------------------------------------------------------------------------- 1 | local tap = {} 2 | local counter = 1 3 | 4 | function tap.ok(assert_true, desc) 5 | local msg = ( assert_true and "ok " or "not ok " ) .. counter 6 | if ( desc ) then 7 | msg = msg .. " - " .. desc 8 | end 9 | print(msg) 10 | counter = counter + 1 11 | end 12 | 13 | return tap 14 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | print "1..5" 2 | 3 | local tap = require("tap") 4 | local yajl = require("yajl") 5 | local ok = tap.ok 6 | 7 | function main() 8 | test_simple() 9 | test_issue_13() 10 | null_at_end_of_array() 11 | null_object_value() 12 | weird_numbers() 13 | number_in_string() 14 | test_generator() 15 | test_to_value() 16 | end 17 | 18 | function to_value(string) 19 | local result 20 | local stack = { 21 | function(val) result = val end 22 | } 23 | local obj_key 24 | local events = { 25 | value = function(_, val) 26 | stack[#stack](val) 27 | end, 28 | open_array = function() 29 | local arr = {} 30 | local idx = 1 31 | stack[#stack](arr) 32 | table.insert(stack, function(val) 33 | arr[idx] = val 34 | idx = idx + 1 35 | end) 36 | end, 37 | open_object = function() 38 | local obj = {} 39 | stack[#stack](obj) 40 | table.insert(stack, function(val) 41 | obj[obj_key] = val 42 | end) 43 | end, 44 | object_key = function(_, val) 45 | obj_key = val 46 | end, 47 | close = function() 48 | stack[#stack] = nil 49 | end, 50 | } 51 | 52 | yajl.parser({ events = events })(string) 53 | return result 54 | end 55 | 56 | function test_issue_13() 57 | local custom = { __gen_json = function(self, gen) 58 | gen:string("custom json serializer") 59 | end 60 | } 61 | setmetatable(custom, custom) 62 | local val = { 63 | func = function() end, 64 | float = 1.5, 65 | integer = 5, 66 | string = "hello", 67 | empty = {}, -- defaults to an empty array. 68 | ["false"] = false, 69 | custom = custom, 70 | ostr_key = { key = "value" }, 71 | obool_key = { [true] = true }, 72 | onull_key = { [yajl.null] = yajl.null }, 73 | } 74 | -- Round trip it: 75 | local got = yajl.to_value(yajl.to_string(val)) 76 | ok(string.find(got.func, "^function: "), "expect 'function: .*', got '" .. tostring(got.func) .. "'") 77 | ok(got.float == 1.5, "expect 1.5, got " .. tostring(got.float)) 78 | ok(got.integer == 5, "expect 5, got " .. tostring(got.integer)) 79 | ok(got.string == "hello", "expect 'hello', got '" .. tostring(got.string) .. "'") 80 | ok(type(got.empty) == "table" and #got.empty == 0, "expect empty table, got " .. type(got.empty) .. " len=" .. #got.empty) 81 | ok(got["false"] == false, "expected false, got " .. tostring(got["false"])) 82 | ok(got.custom == "custom json serializer", "expected custom json serializer to run, got " .. tostring(got.custom)) 83 | ok(type(got.ostr_key) == "table" and got.ostr_key.key == "value", "got " .. tostring(got.ostr_key)) 84 | ok(type(got.obool_key) == "table" and got.obool_key["true"] == true, "got " .. tostring(got.obool_key)) 85 | ok(type(got.onull_key) == "table" and got.onull_key["null"] == yajl.null, "got " .. tostring(got.onull_key)) 86 | end 87 | 88 | function test_to_value() 89 | local json = '[["float",1.5,"integer",5,"string","hello","empty",[],"false",false,"custom","custom json serializer","ostr_key",{"key":"value"},"obool_key",{"true":true},"onull_key",{"null":null}],10,10.3,10.3,"a string",null,false,true]' 90 | local val = yajl.to_value(json) 91 | local got = yajl.to_string(val); 92 | 93 | ok(got == json, 94 | "yajl.to_value(" .. json .. ") -> " .. got) 95 | end 96 | 97 | function test_generator() 98 | local strings = {} 99 | local generator = yajl.generator { 100 | printer = function(string) 101 | table.insert(strings, string) 102 | end 103 | } 104 | 105 | local custom = { __gen_json = function(self, gen) 106 | gen:string("custom json serializer") 107 | end 108 | } 109 | setmetatable(custom, custom) 110 | 111 | generator:open_array() 112 | generator:value({ "float", 1.5, 113 | "integer", 5, 114 | "string", "hello", 115 | "empty", {}, -- defaults to an empty array. 116 | "false", false, 117 | "custom", custom, 118 | "ostr_key", { key = "value" }, 119 | "obool_key", { [true] = true }, 120 | "onull_key", { [yajl.null] = yajl.null }, 121 | }) 122 | generator:integer(10) 123 | generator:double(10.3) 124 | generator:number(10.3) 125 | generator:string("a string") 126 | generator:null() 127 | generator:boolean(false) 128 | generator:boolean(true) 129 | generator:open_object() 130 | generator:close() 131 | generator:close() 132 | 133 | local expect = '[["float",1.5,"integer",5,"string","hello","empty",[],"false",false,"custom","custom json serializer","ostr_key",{"key":"value"},"obool_key",{"true":true},"onull_key",{"null":null}],10,10.300000000000000711,10.3,"a string",null,false,true,{}]' 134 | local got = table.concat(strings) 135 | ok(expect == got, expect .. " == " .. got) 136 | end 137 | 138 | function test_simple() 139 | -- Thanks to fab13n@github for this bug report: 140 | -- https://github.com/brimworks/lua-yajl/issues/8 141 | assert(yajl.to_value(yajl.to_string(0)) == 0) 142 | 143 | local expect = 144 | '['.. 145 | '"float",1.5,'.. 146 | '"integer",5,'.. 147 | '"true",true,'.. 148 | '"false",false,'.. 149 | '"null",null,'.. 150 | '"string","hello",'.. 151 | '"array",[1,2],'.. 152 | '"object",{"key":"value"}'.. 153 | ']' 154 | 155 | -- Input to to_value matches the output of to_string: 156 | local got = yajl.to_string(to_value(expect)) 157 | ok(expect == got, expect .. " == " .. tostring(got)) 158 | end 159 | 160 | function null_at_end_of_array() 161 | local expect = '["something",null]' 162 | local got = yajl.to_string(to_value(expect)) 163 | ok(expect == got, expect .. " == " .. tostring(got)) 164 | end 165 | 166 | function null_object_value() 167 | local expect = '{"something":null}' 168 | local got = yajl.to_string(to_value(expect)) 169 | ok(expect == got, expect .. " == " .. tostring(got)) 170 | end 171 | 172 | function weird_numbers() 173 | -- See note in README about how we are broken when it comes to big numbers. 174 | local expect = '[1e+666,-1e+666,-0]' 175 | local got = yajl.to_string(to_value(expect)) 176 | ok(expect == got, expect .. " == " .. tostring(got)) 177 | 178 | local nan = 0/0 179 | got = yajl.to_string { nan }; 180 | ok("[-0]" == got, "NaN converts to -0 (got: " .. got .. ")") 181 | end 182 | 183 | function number_in_string() 184 | -- Thanks to agentzh for this bug report! 185 | local expect = '{"2":"two"}' 186 | local got = yajl.to_string {["2"]="two"} 187 | ok(expect == got, expect .. " == " .. tostring(got)) 188 | end 189 | 190 | main() -------------------------------------------------------------------------------- /yajl.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | luaopen_yajl 3 | --------------------------------------------------------------------------------