├── .gitignore ├── t ├── test.lua ├── mod2.c └── mod1.c ├── rotable.h ├── README.md └── rotable.c /.gitignore: -------------------------------------------------------------------------------- 1 | # docco output directory 2 | #docs/ 3 | 4 | # local files for remembering stuff 5 | HISTO 6 | TODO 7 | 8 | # temporary files 9 | .*.swp 10 | 11 | # object files 12 | *.o 13 | *.obj 14 | 15 | # libraries 16 | *.so 17 | *.dll 18 | *.a 19 | *.lib 20 | *.exp 21 | 22 | # executables 23 | *.exe 24 | 25 | # precompiled Lua bytecode files 26 | *.luac 27 | 28 | # LuaRocks packages 29 | *.rock 30 | 31 | -------------------------------------------------------------------------------- /t/test.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | local mod1 = require( "mod1" ) 4 | local obj1 = require( "mod1.idx" ) 5 | local mod2 = require( "mod2" ) 6 | local obj2 = require( "mod2.idx" ) 7 | 8 | mod1.func1() 9 | mod1.func2() 10 | mod1.func3() 11 | mod1.func4() 12 | 13 | mod2.gunc1() 14 | mod2.gunc2() 15 | mod2.gunc3() 16 | mod2.gunc4() 17 | 18 | obj1.func1() 19 | obj1.func2() 20 | obj2.gunc3() 21 | obj2.gunc4() 22 | 23 | print( mod1, #mod1 ) 24 | if not _VERSION:match( "5%.1$" ) then 25 | for k,v in pairs( mod1 ) do 26 | print( k, v ) 27 | end 28 | end 29 | print( mod2, #mod2 ) 30 | if not _VERSION:match( "5%.1$" ) then 31 | for k, v in pairs( mod2 ) do 32 | print( k, v ) 33 | end 34 | local f, t, s = pairs( mod2 ) 35 | print( pcall( f, io.stdout ) ) 36 | end 37 | 38 | -------------------------------------------------------------------------------- /rotable.h: -------------------------------------------------------------------------------- 1 | #ifndef ROTABLE_H_ 2 | #define ROTABLE_H_ 3 | 4 | #include 5 | 6 | /* exactly the same as luaL_Reg, but since we are on small embedded 7 | * microcontrollers, we don't assume that you have `lauxlib.h` 8 | * available in your build! */ 9 | typedef struct rotable_Reg { 10 | char const* name; 11 | lua_CFunction func; 12 | } rotable_Reg; 13 | 14 | #ifndef ROTABLE_EXPORT 15 | # define ROTABLE_EXPORT extern 16 | #endif 17 | 18 | /* compatible with `luaL_newlib()`, and works with `luaL_Reg` *and* 19 | * `rotable_Reg` arrays (in case you don't use `lauxlib.h`) */ 20 | ROTABLE_EXPORT void rotable_newlib( lua_State* L, void const* reg ); 21 | 22 | /* Since userdatas can not be used as `__index` meta methods directly 23 | * this function creates a C closure that looks up keys in a given 24 | * `rotable_Reg` array. */ 25 | ROTABLE_EXPORT void rotable_newidx( lua_State* L, void const* reg ); 26 | 27 | #endif /* ROTABLE_H_ */ 28 | 29 | -------------------------------------------------------------------------------- /t/mod2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "rotable.h" 4 | 5 | 6 | static int func1( lua_State* L ) { 7 | (void)L; 8 | printf( "gunc1()\n" ); 9 | return 0; 10 | } 11 | 12 | static int func2( lua_State* L ) { 13 | (void)L; 14 | printf( "gunc2()\n" ); 15 | return 0; 16 | } 17 | 18 | static int func3( lua_State* L ) { 19 | (void)L; 20 | printf( "gunc3()\n" ); 21 | return 0; 22 | } 23 | 24 | static int func4( lua_State* L ) { 25 | (void)L; 26 | printf( "gunc4()\n" ); 27 | return 0; 28 | } 29 | 30 | static rotable_Reg const funcs[] = { 31 | { "gunc3", func3 }, 32 | { "gunc2", func2 }, 33 | { "gunc1", func1 }, 34 | { "gunc4", func4 }, 35 | { 0, 0 }, 36 | }; 37 | 38 | 39 | int luaopen_mod2( lua_State* L ) { 40 | rotable_newlib( L, funcs ); 41 | return 1; 42 | } 43 | 44 | 45 | int luaopen_mod2_idx( lua_State* L ) { 46 | lua_newtable( L ); 47 | lua_newtable( L ); 48 | rotable_newidx( L, funcs ); 49 | lua_setfield( L, -2, "__index" ); 50 | lua_setmetatable( L, -2 ); 51 | return 1; 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /t/mod1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "rotable.h" 5 | 6 | 7 | static int func1( lua_State* L ) { 8 | (void)L; 9 | printf( "func1()\n" ); 10 | return 0; 11 | } 12 | 13 | static int func2( lua_State* L ) { 14 | (void)L; 15 | printf( "func2()\n" ); 16 | return 0; 17 | } 18 | 19 | static int func3( lua_State* L ) { 20 | (void)L; 21 | printf( "func3()\n" ); 22 | return 0; 23 | } 24 | 25 | static int func4( lua_State* L ) { 26 | (void)L; 27 | printf( "func4()\n" ); 28 | return 0; 29 | } 30 | 31 | static luaL_Reg const funcs[] = { 32 | { "func1", func1 }, 33 | { "func2", func2 }, 34 | { "func3", func3 }, 35 | { "func4", func4 }, 36 | { "func5", func4 }, 37 | { "func6", func4 }, 38 | { 0, 0 }, 39 | }; 40 | 41 | 42 | int luaopen_mod1( lua_State* L ) { 43 | rotable_newlib( L, funcs ); 44 | return 1; 45 | } 46 | 47 | 48 | int luaopen_mod1_idx( lua_State* L ) { 49 | lua_newtable( L ); 50 | lua_newtable( L ); 51 | rotable_newidx( L, funcs ); 52 | lua_setfield( L, -2, "__index" ); 53 | lua_setmetatable( L, -2 ); 54 | return 1; 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rotable # 2 | 3 | Tables in Lua use quite some dynamic memory. To reduce this memory 4 | consumption on small embedded microcontrollers the [eLua project][1] 5 | introduced `rotable`s (read-only tables) that can be stored in ROM and 6 | behave mostly like ordinary Lua tables (except that you can't mutate 7 | them, of course). These `rotable`s are particularly useful for module 8 | tables which usually don't change anyway and mostly contain strings as 9 | keys and C function pointers as values. 10 | 11 | This project provides a limited imitation of eLua's `rotable`s without 12 | the need to patch Lua. On the plus side, more recent Lua versions (5.2 13 | and 5.3) are supported. The other features of eLua's [Lua Tiny RAM 14 | patch][2] and the [Emergency GC patch][3] have been [incorporated in 15 | recent Lua versions][4] anyway. 16 | 17 | 18 | ## API ## 19 | 20 | /* [-0, +1, m] */ 21 | void rotable_newlib( lua_State* L, luaL_Reg const l[] ); 22 | 23 | This function creates and pushes a `rotable` onto the Lua stack. If 24 | you don't have the auxiliary library available, you can use the type 25 | `rotable_Reg` instead of `luaL_Reg` in the above signature. A 26 | `rotable` is a small userdata (so you *do* have some dynamic memory 27 | allocation per `rotable`) that contains a pointer to the given 28 | `luaL_Reg` array and behaves mostly like a table. If the string keys 29 | in the `luaL_Reg` array are sorted lexicographically in ascending 30 | order (which is highly recommended), access is more efficient because 31 | a binary search is used. Otherwise every access uses a linear search 32 | for the key. 33 | 34 | In contrast to the original `rotable`s in the eLua project, this 35 | implementation only supports string keys and C function values. If you 36 | need anything else, you can consider using a `rotable` as fallback 37 | using an `__index` metamethod on a normal table. 38 | 39 | 40 | /* [-0, +1, m] */ 41 | void rotable_newidx( lua_State* L, luaL_Reg const l[] ); 42 | 43 | Since userdata values can't be used as `__index` meta methods, this 44 | function creates and pushes a custom C closure that looks up keys in 45 | the given `luaL_Reg` array. It can be used as `__index` meta method 46 | instead. 47 | 48 | 49 | ## License ## 50 | 51 | **rotable** is *copyrighted free software* distributed under the MIT 52 | license (the same license as Lua 5.1). The full license text follows: 53 | 54 | rotable (c) 2017 Philipp Janda 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining 57 | a copy of this software and associated documentation files (the 58 | "Software"), to deal in the Software without restriction, including 59 | without limitation the rights to use, copy, modify, merge, publish, 60 | distribute, sublicense, and/or sell copies of the Software, and to 61 | permit persons to whom the Software is furnished to do so, subject to 62 | the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be 65 | included in all copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 68 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 69 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 70 | IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY 71 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 72 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 73 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 74 | 75 | 76 | [1]: http://www.eluaproject.net/ 77 | [2]: http://www.eluaproject.net/doc/v0.9/en_arch_ltr.html 78 | [3]: http://www.eluaproject.net/doc/v0.9/en_elua_egc.html 79 | [4]: http://www.lua.org/manual/5.2/readme.html#changes 80 | 81 | -------------------------------------------------------------------------------- /rotable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "rotable.h" 6 | 7 | 8 | /* The lookup code uses binary search on sorted `rotable_Reg` arrays 9 | * to find functions/methods. For a small number of elements a linear 10 | * search might be faster. */ 11 | #ifndef ROTABLE_BINSEARCH_MIN 12 | # define ROTABLE_BINSEARCH_MIN 5 13 | #endif 14 | 15 | 16 | typedef struct { 17 | #if LUA_VERSION_NUM < 503 18 | /* on Lua 5.3 we use a lightuserdata in the uservalue of the 19 | * userdata to hold the pointer to the luaL_Reg structure. Older 20 | * Lua versions have to store it in the userdata payload. */ 21 | rotable_Reg const* p; 22 | #endif 23 | /* number of elements in the luaL_Reg array *if* it is sorted by 24 | * name. We can use binary search in this case. Otherwise we need 25 | * linear searches anyway and we can scan for the final `{NULL,0}` 26 | * entry. `n` is 0 in this case. */ 27 | int n; 28 | } rotable; 29 | 30 | 31 | static char const unique_address[ 1 ] = { 0 }; 32 | 33 | 34 | static int reg_compare(void const* a, void const* b) { 35 | return strcmp( (char const*)a, ((rotable_Reg const*)b)->name ); 36 | } 37 | 38 | 39 | static rotable* check_rotable( lua_State* L, int idx, char const* func ) { 40 | rotable* t = (rotable*)lua_touserdata( L, idx ); 41 | if( t ) { 42 | if( lua_getmetatable( L, idx ) ) { 43 | lua_pushlightuserdata( L, (void*)unique_address ); 44 | lua_rawget( L, LUA_REGISTRYINDEX ); 45 | if( !lua_rawequal( L, -1, -2 ) ) 46 | t = 0; 47 | lua_pop( L, 2 ); 48 | } 49 | } 50 | if( !t ) { 51 | char const* type = lua_typename( L, lua_type( L, idx ) ); 52 | if( lua_type( L, idx ) == LUA_TLIGHTUSERDATA ) { 53 | type = "light userdata"; 54 | } else if( lua_getmetatable( L, idx ) ) { 55 | lua_getfield( L, -1, "__name" ); 56 | lua_replace( L, -2 ); /* we don't need the metatable anymore */ 57 | if( lua_type( L, -1 ) == LUA_TSTRING ) 58 | type = lua_tostring( L, -1 ); 59 | } 60 | lua_pushfstring( L, "bad argument #%d to '%s' " 61 | "(rotable expected, got %s)", idx, func, type ); 62 | lua_error( L ); 63 | } 64 | return t; 65 | } 66 | 67 | 68 | static rotable_Reg const* find_key( rotable_Reg const* p, int n, 69 | char const* s ) { 70 | if( s ) { 71 | if( n >= ROTABLE_BINSEARCH_MIN ) { /* binary search */ 72 | return (rotable_Reg const*)bsearch( s, p, n, sizeof( *p ), reg_compare ); 73 | } else { /* use linear scan */ 74 | for( ; p->func; ++p ) { 75 | if( 0 == reg_compare( s, p ) ) 76 | return p; 77 | } 78 | } 79 | } 80 | return 0; 81 | } 82 | 83 | 84 | static int rotable_func_index( lua_State* L ) { 85 | char const* s = lua_tostring( L, 2 ); 86 | rotable_Reg const* p = (rotable_Reg const*)lua_touserdata( L, lua_upvalueindex( 1 ) ); 87 | int n = lua_tointeger( L, lua_upvalueindex( 2 ) ); 88 | p = find_key( p, n, s ); 89 | if( p ) 90 | lua_pushcfunction( L, p->func ); 91 | else 92 | lua_pushnil( L ); 93 | return 1; 94 | } 95 | 96 | 97 | static int rotable_udata_index( lua_State* L ) { 98 | rotable* t = (rotable*)lua_touserdata( L, 1 ); 99 | char const* s = lua_tostring( L, 2 ); 100 | #if LUA_VERSION_NUM < 503 101 | rotable_Reg const* p = t->p; 102 | #else 103 | rotable_Reg const* p = 0; 104 | lua_getuservalue( L, 1 ); 105 | p = (rotable_Reg const*)lua_touserdata( L, -1 ); 106 | #endif 107 | p = find_key( p, t->n, s ); 108 | if( p ) 109 | lua_pushcfunction( L, p->func ); 110 | else 111 | lua_pushnil( L ); 112 | return 1; 113 | } 114 | 115 | 116 | static int rotable_udata_len( lua_State* L ) { 117 | lua_pushinteger( L, 0 ); 118 | return 1; 119 | } 120 | 121 | 122 | static int rotable_iter( lua_State* L ) { 123 | rotable* t = check_rotable( L, 1, "__pairs iterator" ); 124 | char const* s = lua_tostring( L, 2 ); 125 | rotable_Reg const* q = 0; 126 | #if LUA_VERSION_NUM < 503 127 | rotable_Reg const* p = t->p; 128 | #else 129 | rotable_Reg const* p = 0; 130 | lua_getuservalue( L, 1 ); 131 | p = (rotable_Reg const*)lua_touserdata( L, -1 ); 132 | #endif 133 | if( s ) { 134 | if( t->n >= ROTABLE_BINSEARCH_MIN ) { /* binary search */ 135 | q = (rotable_Reg const*)bsearch( s, p, t->n, sizeof( *p ), reg_compare ); 136 | if( q ) 137 | ++q; 138 | else 139 | q = p + t->n; 140 | } else { /* use linear scan */ 141 | for( q = p; q->func; ++q ) { 142 | if( 0 == reg_compare( s, q ) ) { 143 | ++q; 144 | break; 145 | } 146 | } 147 | } 148 | } else 149 | q = p; 150 | if( q->func ) { 151 | lua_pushstring( L, q->name ); 152 | lua_pushcfunction( L, q->func ); 153 | return 2; 154 | } 155 | return 0; 156 | } 157 | 158 | 159 | static int rotable_udata_pairs( lua_State* L ) { 160 | lua_pushcfunction( L, rotable_iter ); 161 | lua_pushvalue( L, 1 ); 162 | lua_pushnil( L ); 163 | return 3; 164 | } 165 | 166 | 167 | ROTABLE_EXPORT void rotable_newlib( lua_State* L, void const* v ) { 168 | rotable_Reg const* reg = (rotable_Reg const*)v; 169 | rotable* t = (rotable*)lua_newuserdata( L, sizeof( *t ) ); 170 | lua_pushlightuserdata( L, (void*)unique_address ); 171 | lua_rawget( L, LUA_REGISTRYINDEX ); 172 | if( !lua_istable( L, -1 ) ) { 173 | lua_pop( L, 1 ); 174 | lua_createtable( L, 0, 5 ); 175 | lua_pushcfunction( L, rotable_udata_index ); 176 | lua_setfield( L, -2, "__index" ); 177 | lua_pushcfunction( L, rotable_udata_len ); 178 | lua_setfield( L, -2, "__len" ); 179 | lua_pushcfunction( L, rotable_udata_pairs ); 180 | lua_setfield( L, -2, "__pairs" ); 181 | lua_pushboolean( L, 0 ); 182 | lua_setfield( L, -2, "__metatable" ); 183 | lua_pushliteral( L, "rotable" ); 184 | lua_setfield( L, -2, "__name" ); 185 | lua_pushlightuserdata( L, (void*)unique_address ); 186 | lua_pushvalue( L, -2 ); 187 | lua_rawset( L, LUA_REGISTRYINDEX ); 188 | } 189 | lua_setmetatable( L, -2 ); 190 | #if LUA_VERSION_NUM < 503 191 | t->p = reg; 192 | #else 193 | lua_pushlightuserdata( L, (void*)reg ); 194 | lua_setuservalue( L, -2 ); 195 | #endif 196 | t->n = 0; 197 | if( reg->func ) { 198 | int i = 1; 199 | for( ; reg[ i ].func; ++i ) { 200 | if( strcmp( reg[ i-1 ].name, reg[ i ].name ) >= 0 ) 201 | return; 202 | } 203 | t->n = i; 204 | } 205 | } 206 | 207 | 208 | ROTABLE_EXPORT void rotable_newidx( lua_State* L, void const* v ) { 209 | rotable_Reg const* reg = (rotable_Reg const*)v; 210 | int i = 0; 211 | lua_pushlightuserdata( L, (void*)v); 212 | for( ; reg[ i ].func; ++i ) { 213 | if( strcmp( reg[ i-1 ].name, reg[ i ].name ) >= 0 ) { 214 | i = 0; 215 | break; 216 | } 217 | } 218 | if( i >= ROTABLE_BINSEARCH_MIN ) { 219 | lua_pushinteger( L, i ); 220 | lua_pushcclosure( L, rotable_func_index, 2 ); 221 | } else 222 | lua_pushcclosure( L, rotable_func_index, 1 ); 223 | } 224 | 225 | --------------------------------------------------------------------------------