| /* |
| ** lupb_msg -- Message/Array/Map objects in Lua/C that wrap upb/msg.h |
| */ |
| |
| #include "upb/msg.h" |
| |
| #include <float.h> |
| #include <math.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "lauxlib.h" |
| #include "upb/bindings/lua/upb.h" |
| #include "upb/handlers.h" |
| #include "upb/port_def.inc" |
| #include "upb/reflection.h" |
| |
| /* |
| * Message/Array/Map objects can be constructed in one of two ways: |
| * |
| * 1. To point to existing msg/array/map data inside an arena. |
| * 2. To create and uniquely own some brand new data. |
| * |
| * Case (1) is for when we've parsed some data into an arena (which is faster |
| * than parsing directly into Lua objects) or when we're pointing at some |
| * read-only data (like custom options in a def). |
| * |
| * Case (2) is for when a user creates the object directly in Lua. |
| * |
| * We use the userval of container objects (Message/Array/Map) to store |
| * references to sub-objects (Strings/Messages/Arrays/Maps). But we need to |
| * keep the userval in sync with the underlying upb_msg/upb_array/upb_map. |
| * We populate the userval lazily from the underlying data. |
| * |
| * This means that no one may remove/replace any String/Message/Array/Map |
| * field/entry in the underlying upb_{msg,array,map} behind our back. It's ok |
| * for entries to be added or for primitives to be modified, but *replacing* |
| * sub-containers is not. |
| * |
| * Luckily parse/merge follow this rule. However clear does not, so it's not |
| * safe to clear behind our back. |
| */ |
| |
| #define LUPB_ARENA "lupb.arena" |
| |
| #define LUPB_MSGCLASS "lupb.msgclass" |
| #define LUPB_MSGFACTORY "lupb.msgfactory" |
| |
| #define LUPB_ARRAY "lupb.array" |
| #define LUPB_MAP "lupb.map" |
| #define LUPB_MSG "lupb.msg" |
| #define LUPB_STRING "lupb.string" |
| |
| static int lupb_msg_pushnew(lua_State *L, int narg); |
| |
| /* Lazily creates the uservalue if it doesn't exist. */ |
| static void lupb_getuservalue(lua_State *L, int index) { |
| lua_getuservalue(L, index); |
| if (lua_isnil(L, -1)) { |
| /* Lazily create and set userval. */ |
| lua_pop(L, 1); /* nil. */ |
| lua_pushvalue(L, index); /* userdata copy. */ |
| lua_newtable(L); |
| lua_setuservalue(L, -2); |
| lua_pop(L, 1); /* userdata copy. */ |
| lua_getuservalue(L, index); |
| } |
| assert(!lua_isnil(L, -1)); |
| } |
| |
| static void lupb_uservalseti(lua_State *L, int userdata, int index, int val) { |
| lupb_getuservalue(L, userdata); |
| lua_pushvalue(L, val); |
| lua_rawseti(L, -2, index); |
| lua_pop(L, 1); /* Uservalue. */ |
| } |
| |
| static void lupb_uservalgeti(lua_State *L, int userdata, int index) { |
| lupb_getuservalue(L, userdata); |
| lua_rawgeti(L, -1, index); |
| lua_insert(L, -2); |
| lua_pop(L, 1); /* Uservalue. */ |
| } |
| |
| /* Pushes a new userdata with the given metatable. */ |
| static void *lupb_newuserdata(lua_State *L, size_t size, const char *type) { |
| void *ret = lua_newuserdata(L, size); |
| |
| /* Set metatable. */ |
| luaL_getmetatable(L, type); |
| UPB_ASSERT(!lua_isnil(L, -1)); /* Should have been created by luaopen_upb. */ |
| lua_setmetatable(L, -2); |
| |
| /* We don't set a uservalue here -- we lazily create it later if necessary. */ |
| |
| return ret; |
| } |
| |
| |
| /* lupb_arena *****************************************************************/ |
| |
| /* lupb_arena only exists to wrap a upb_arena. It is never exposed to users; |
| * it is an internal memory management detail. Other objects refer to this |
| * object from their userdata to keep the arena-owned data alive. */ |
| |
| typedef struct { |
| upb_arena *arena; |
| } lupb_arena; |
| |
| upb_arena *lupb_arena_check(lua_State *L, int narg) { |
| lupb_arena *a = luaL_checkudata(L, narg, LUPB_ARENA); |
| return a ? a->arena : NULL; |
| } |
| |
| int lupb_arena_new(lua_State *L) { |
| lupb_arena *a = lupb_newuserdata(L, sizeof(lupb_arena), LUPB_ARENA); |
| |
| /* TODO(haberman): use Lua alloc func as block allocator? Would need to |
| * verify that all cases of upb_malloc in msg/table are longjmp-safe. */ |
| a->arena = upb_arena_new(); |
| |
| return 1; |
| } |
| |
| char lupb_arena_cache_key; |
| |
| /* Returns the global lupb_arena func that was created in our luaopen(). |
| * Callers can be guaranteed that it will be alive as long as |L| is. |
| * TODO(haberman): we shouldn't use a global arena! We should have |
| * one arena for a parse, or per independently-created message. */ |
| upb_arena *lupb_arena_get(lua_State *L) { |
| upb_arena *arena; |
| |
| lua_pushlightuserdata(L, &lupb_arena_cache_key); |
| lua_gettable(L, LUA_REGISTRYINDEX); |
| arena = lua_touserdata(L, -1); |
| UPB_ASSERT(arena); |
| lua_pop(L, 1); |
| |
| return arena; |
| } |
| |
| static void lupb_arena_initsingleton(lua_State *L) { |
| lua_pushlightuserdata(L, &lupb_arena_cache_key); |
| lupb_arena_new(L); |
| lua_settable(L, LUA_REGISTRYINDEX); |
| } |
| |
| static int lupb_arena_gc(lua_State *L) { |
| upb_arena *a = lupb_arena_check(L, 1); |
| upb_arena_free(a); |
| return 0; |
| } |
| |
| static const struct luaL_Reg lupb_arena_mm[] = { |
| {"__gc", lupb_arena_gc}, |
| {NULL, NULL} |
| }; |
| |
| |
| /* lupb_msgfactory ************************************************************/ |
| |
| /* Userval contains a map of: |
| * [1] -> SymbolTable (to keep GC-reachable) |
| * [const upb_msgdef*] -> [lupb_msgclass userdata] |
| */ |
| |
| #define LUPB_MSGFACTORY_SYMTAB 1 |
| |
| typedef struct lupb_msgfactory { |
| upb_msgfactory *factory; |
| } lupb_msgfactory; |
| |
| static int lupb_msgclass_pushnew(lua_State *L, int factory, |
| const upb_msgdef *md); |
| |
| /* lupb_msgfactory helpers. */ |
| |
| static lupb_msgfactory *lupb_msgfactory_check(lua_State *L, int narg) { |
| return luaL_checkudata(L, narg, LUPB_MSGFACTORY); |
| } |
| |
| static void lupb_msgfactory_pushmsgclass(lua_State *L, int narg, |
| const upb_msgdef *md) { |
| lupb_getuservalue(L, narg); |
| lua_pushlightuserdata(L, (void*)md); |
| lua_rawget(L, -2); |
| |
| if (lua_isnil(L, -1)) { |
| lua_pop(L, 1); |
| /* TODO: verify md is in symtab? */ |
| lupb_msgclass_pushnew(L, narg, md); |
| |
| /* Set in userval. */ |
| lua_pushlightuserdata(L, (void*)md); |
| lua_pushvalue(L, -2); |
| lua_rawset(L, -4); |
| } |
| } |
| |
| static int lupb_msgfactory_gc(lua_State *L) { |
| lupb_msgfactory *lfactory = lupb_msgfactory_check(L, 1); |
| |
| if (lfactory->factory) { |
| upb_msgfactory_free(lfactory->factory); |
| lfactory->factory = NULL; |
| } |
| |
| return 0; |
| } |
| |
| /* lupb_msgfactory Public API. */ |
| |
| /** |
| * lupb_msgfactory_new() |
| * |
| * Handles: |
| * msgfactory = upb.MessageFactory(symtab) |
| * |
| * Creates a new, empty MessageFactory for the given SymbolTable. |
| * Message classes will be created on demand when the user calls |
| * msgfactory.get_message_class(). |
| */ |
| static int lupb_msgfactory_new(lua_State *L) { |
| const upb_symtab *symtab = lupb_symtab_check(L, 1); |
| |
| lupb_msgfactory *lmsgfactory = |
| lupb_newuserdata(L, sizeof(lupb_msgfactory), LUPB_MSGFACTORY); |
| lmsgfactory->factory = upb_msgfactory_new(symtab); |
| lupb_uservalseti(L, -1, LUPB_MSGFACTORY_SYMTAB, 1); |
| |
| return 1; |
| } |
| |
| /** |
| * lupb_msgfactory_getmsgclass() |
| * |
| * Handles: |
| * MessageClass = factory.get_message_class(message_name) |
| */ |
| static int lupb_msgfactory_getmsgclass(lua_State *L) { |
| lupb_msgfactory *lfactory = lupb_msgfactory_check(L, 1); |
| const upb_symtab *symtab = upb_msgfactory_symtab(lfactory->factory); |
| const upb_msgdef *m = upb_symtab_lookupmsg(symtab, luaL_checkstring(L, 2)); |
| |
| if (!m) { |
| luaL_error(L, "No such message type: %s\n", lua_tostring(L, 2)); |
| } |
| |
| lupb_msgfactory_pushmsgclass(L, 1, m); |
| |
| return 1; |
| } |
| |
| static const struct luaL_Reg lupb_msgfactory_m[] = { |
| {"get_message_class", lupb_msgfactory_getmsgclass}, |
| {NULL, NULL} |
| }; |
| |
| static const struct luaL_Reg lupb_msgfactory_mm[] = { |
| {"__gc", lupb_msgfactory_gc}, |
| {NULL, NULL} |
| }; |
| |
| |
| /* lupb_msgclass **************************************************************/ |
| |
| /* Userval contains a map of: |
| * [1] -> MessageFactory (to keep GC-reachable) |
| * [const upb_msgdef*] -> [lupb_msgclass userdata] |
| */ |
| |
| #define LUPB_MSGCLASS_FACTORY 1 |
| |
| struct lupb_msgclass { |
| const upb_msglayout *layout; |
| const upb_msgdef *msgdef; |
| }; |
| |
| /* Type-checks for assigning to a message field. */ |
| static upb_msgval lupb_array_typecheck(lua_State *L, int narg, int msg, |
| const upb_fielddef *f); |
| static upb_msgval lupb_map_typecheck(lua_State *L, int narg, int msg, |
| const upb_fielddef *f); |
| static const lupb_msgclass *lupb_msg_getsubmsgclass(lua_State *L, int narg, |
| const upb_fielddef *f); |
| static const lupb_msgclass *lupb_msg_msgclassfor(lua_State *L, int narg, |
| const upb_msgdef *md); |
| |
| const lupb_msgclass *lupb_msgclass_check(lua_State *L, int narg) { |
| return luaL_checkudata(L, narg, LUPB_MSGCLASS); |
| } |
| |
| const upb_msglayout *lupb_msgclass_getlayout(lua_State *L, int narg) { |
| return lupb_msgclass_check(L, narg)->layout; |
| } |
| |
| const upb_msgdef *lupb_msgclass_getmsgdef(const lupb_msgclass *lmsgclass) { |
| return lmsgclass->msgdef; |
| } |
| |
| /** |
| * lupb_msgclass_typecheck() |
| * |
| * Verifies that the expected msgclass matches the actual. If not, raises a Lua |
| * error. |
| */ |
| static void lupb_msgclass_typecheck(lua_State *L, const lupb_msgclass *expected, |
| const lupb_msgclass *actual) { |
| if (expected != actual) { |
| luaL_error(L, "Message had incorrect type, expected '%s', got '%s'", |
| upb_msgdef_fullname(expected->msgdef), |
| upb_msgdef_fullname(actual->msgdef)); |
| } |
| } |
| |
| static const lupb_msgclass *lupb_msgclass_msgclassfor(lua_State *L, int narg, |
| const upb_msgdef *md) { |
| lupb_uservalgeti(L, narg, LUPB_MSGCLASS_FACTORY); |
| lupb_msgfactory_pushmsgclass(L, -1, md); |
| return lupb_msgclass_check(L, -1); |
| } |
| |
| /** |
| * lupb_msgclass_getsubmsgclass() |
| * |
| * Given a MessageClass at index |narg| and the submessage field |f|, returns |
| * the message class for this field. |
| * |
| * Currently we do a hash table lookup for this. If we wanted we could try to |
| * optimize this by caching these pointers in our msgclass, in an array indexed |
| * by field index. We would still need to fall back to calling msgclassfor(), |
| * unless we wanted to eagerly create message classes for all submessages. But |
| * for big schemas that might be a lot of things to build, and we might end up |
| * not using most of them. */ |
| static const lupb_msgclass *lupb_msgclass_getsubmsgclass(lua_State *L, int narg, |
| const upb_fielddef *f) { |
| if (upb_fielddef_type(f) != UPB_TYPE_MESSAGE) { |
| return NULL; |
| } |
| |
| return lupb_msgclass_msgclassfor(L, narg, upb_fielddef_msgsubdef(f)); |
| } |
| |
| static int lupb_msgclass_pushnew(lua_State *L, int factory, |
| const upb_msgdef *md) { |
| const lupb_msgfactory *lfactory = lupb_msgfactory_check(L, factory); |
| lupb_msgclass *lmc = lupb_newuserdata(L, sizeof(*lmc), LUPB_MSGCLASS); |
| |
| lupb_uservalseti(L, -1, LUPB_MSGCLASS_FACTORY, factory); |
| lmc->layout = upb_msgfactory_getlayout(lfactory->factory, md); |
| lmc->msgdef = md; |
| |
| return 1; |
| } |
| |
| /* MessageClass Public API. */ |
| |
| /** |
| * lupb_msgclass_call() |
| * |
| * Handles: |
| * msg = MessageClass() |
| * |
| * Creates a new message from the given MessageClass. |
| */ |
| static int lupb_msgclass_call(lua_State *L) { |
| lupb_msg_pushnew(L, 1); |
| return 1; |
| } |
| |
| static const struct luaL_Reg lupb_msgclass_mm[] = { |
| {"__call", lupb_msgclass_call}, |
| {NULL, NULL} |
| }; |
| |
| |
| /* upb <-> Lua type conversion ************************************************/ |
| |
| static bool lupb_istypewrapped(upb_fieldtype_t type) { |
| return type == UPB_TYPE_STRING || type == UPB_TYPE_BYTES || |
| type == UPB_TYPE_MESSAGE; |
| } |
| |
| static upb_msgval lupb_tomsgval(lua_State *L, upb_fieldtype_t type, int narg, |
| const lupb_msgclass *lmsgclass) { |
| upb_msgval ret; |
| switch (type) { |
| case UPB_TYPE_INT32: |
| case UPB_TYPE_ENUM: |
| ret.int32_val = lupb_checkint32(L, narg); |
| break; |
| case UPB_TYPE_INT64: |
| ret.int64_val = lupb_checkint64(L, narg); |
| break; |
| case UPB_TYPE_UINT32: |
| ret.uint32_val = lupb_checkuint32(L, narg); |
| break; |
| case UPB_TYPE_UINT64: |
| ret.uint64_val = lupb_checkuint64(L, narg); |
| break; |
| case UPB_TYPE_DOUBLE: |
| ret.double_val = lupb_checkdouble(L, narg); |
| break; |
| case UPB_TYPE_FLOAT: |
| ret.float_val = lupb_checkfloat(L, narg); |
| break; |
| case UPB_TYPE_BOOL: |
| ret.bool_val = lupb_checkbool(L, narg); |
| break; |
| case UPB_TYPE_STRING: |
| case UPB_TYPE_BYTES: { |
| size_t len; |
| const char *ptr = lupb_checkstring(L, narg, &len); |
| ret.str_val = upb_strview_make(ptr, len); |
| break; |
| } |
| case UPB_TYPE_MESSAGE: |
| UPB_ASSERT(lmsgclass); |
| ret.msg_val = lupb_msg_checkmsg(L, narg, lmsgclass); |
| break; |
| } |
| return ret; |
| } |
| |
| static void lupb_pushmsgval(lua_State *L, upb_fieldtype_t type, |
| upb_msgval val) { |
| switch (type) { |
| case UPB_TYPE_INT32: |
| case UPB_TYPE_ENUM: |
| lupb_pushint32(L, val.int32_val); |
| return; |
| case UPB_TYPE_INT64: |
| lupb_pushint64(L, val.int64_val); |
| return; |
| case UPB_TYPE_UINT32: |
| lupb_pushuint32(L, val.uint32_val); |
| return; |
| case UPB_TYPE_UINT64: |
| lupb_pushuint64(L, val.uint64_val); |
| return; |
| case UPB_TYPE_DOUBLE: |
| lupb_pushdouble(L, val.double_val); |
| return; |
| case UPB_TYPE_FLOAT: |
| lupb_pushfloat(L, val.float_val); |
| return; |
| case UPB_TYPE_BOOL: |
| lua_pushboolean(L, val.bool_val); |
| return; |
| case UPB_TYPE_STRING: |
| case UPB_TYPE_BYTES: |
| case UPB_TYPE_MESSAGE: |
| break; /* Shouldn't call this function. */ |
| } |
| UPB_UNREACHABLE(); |
| } |
| |
| |
| /* lupb_array *****************************************************************/ |
| |
| /* A strongly typed array. Implemented by wrapping upb_array. |
| * |
| * - we only allow integer indices. |
| * - all entries must have the correct type. |
| * - we do not allow "holes" in the array; you can only assign to an existing |
| * index or one past the end (which will grow the array by one). |
| * |
| * For string/submessage entries we keep in the userval: |
| * |
| * [number index] -> [lupb_string/lupb_msg userdata] |
| */ |
| |
| typedef struct { |
| /* Only needed for array of message. This wastes space in the non-message |
| * case but simplifies the code. Could optimize away if desired. */ |
| const lupb_msgclass *lmsgclass; |
| upb_array *arr; |
| upb_fieldtype_t type; |
| } lupb_array; |
| |
| #define ARRAY_MSGCLASS_INDEX 0 |
| |
| static lupb_array *lupb_array_check(lua_State *L, int narg) { |
| return luaL_checkudata(L, narg, LUPB_ARRAY); |
| } |
| |
| /** |
| * lupb_array_typecheck() |
| * |
| * Verifies that the lupb_array object at index |narg| can be safely assigned |
| * to the field |f| of the lupb_msg object at index |msg|. If this is safe, |
| * returns a upb_msgval representing the array. Otherwise, throws a Lua error. |
| */ |
| static upb_msgval lupb_array_typecheck(lua_State *L, int narg, int msg, |
| const upb_fielddef *f) { |
| lupb_array *larray = lupb_array_check(L, narg); |
| upb_msgval val; |
| |
| if (larray->type != upb_fielddef_type(f) || |
| lupb_msg_getsubmsgclass(L, msg, f) != larray->lmsgclass) { |
| luaL_error(L, "Array had incorrect type (expected: %d, got: %d)", |
| (int)upb_fielddef_type(f), (int)larray->type); |
| } |
| |
| if (larray->type == UPB_TYPE_MESSAGE) { |
| lupb_msgclass_typecheck(L, lupb_msg_getsubmsgclass(L, msg, f), |
| larray->lmsgclass); |
| } |
| |
| val.array_val = larray->arr; |
| return val; |
| } |
| |
| /** |
| * lupb_array_checkindex() |
| * |
| * Checks the array index at Lua stack index |narg| to verify that it is an |
| * integer between 1 and |max|, inclusively. Also corrects it to be zero-based |
| * for C. |
| * |
| * We use "int" because of lua_rawseti/lua_rawgeti -- can re-evaluate if we want |
| * arrays bigger than 2^31. |
| */ |
| static int lupb_array_checkindex(lua_State *L, int narg, uint32_t max) { |
| uint32_t n = lupb_checkuint32(L, narg); |
| if (n == 0 || n > max || n > INT_MAX) { |
| luaL_error(L, "Invalid array index: expected between 1 and %d", (int)max); |
| } |
| return n - 1; /* Lua uses 1-based indexing. :( */ |
| } |
| |
| /* lupb_array Public API */ |
| |
| static int lupb_array_new(lua_State *L) { |
| lupb_array *larray; |
| upb_fieldtype_t type; |
| const lupb_msgclass *lmsgclass = NULL; |
| |
| if (lua_type(L, 1) == LUA_TNUMBER) { |
| type = lupb_checkfieldtype(L, 1); |
| } else { |
| type = UPB_TYPE_MESSAGE; |
| lmsgclass = lupb_msgclass_check(L, 1); |
| lupb_uservalseti(L, -1, ARRAY_MSGCLASS_INDEX, 1); /* GC-root lmsgclass. */ |
| } |
| |
| larray = lupb_newuserdata(L, sizeof(*larray), LUPB_ARRAY); |
| larray->type = type; |
| larray->lmsgclass = lmsgclass; |
| larray->arr = upb_array_new(lupb_arena_get(L), type); |
| |
| return 1; |
| } |
| |
| static int lupb_array_newindex(lua_State *L) { |
| lupb_array *larray = lupb_array_check(L, 1); |
| size_t size = upb_array_size(larray->arr); |
| upb_fieldtype_t type = larray->type; |
| uint32_t n = lupb_array_checkindex(L, 2, size + 1); |
| upb_msgval msgval = lupb_tomsgval(L, type, 3, larray->lmsgclass); |
| |
| if (n == size) { |
| upb_array_append(larray->arr, msgval, lupb_arena_get(L)); |
| } else { |
| upb_array_set(larray->arr, n, msgval); |
| } |
| |
| if (lupb_istypewrapped(type)) { |
| lupb_uservalseti(L, 1, n, 3); |
| } |
| |
| return 0; /* 1 for chained assignments? */ |
| } |
| |
| static int lupb_array_index(lua_State *L) { |
| lupb_array *larray = lupb_array_check(L, 1); |
| size_t size = upb_array_size(larray->arr); |
| uint32_t n = lupb_array_checkindex(L, 2, size); |
| upb_fieldtype_t type = larray->type; |
| |
| if (lupb_istypewrapped(type)) { |
| lupb_uservalgeti(L, 1, n); |
| } else { |
| upb_msgval val = upb_array_get(larray->arr, n); |
| lupb_pushmsgval(L, type, val); |
| } |
| |
| return 1; |
| } |
| |
| static int lupb_array_len(lua_State *L) { |
| lupb_array *larray = lupb_array_check(L, 1); |
| lua_pushnumber(L, upb_array_size(larray->arr)); |
| return 1; |
| } |
| |
| static const struct luaL_Reg lupb_array_mm[] = { |
| {"__index", lupb_array_index}, |
| {"__len", lupb_array_len}, |
| {"__newindex", lupb_array_newindex}, |
| {NULL, NULL} |
| }; |
| |
| |
| /* lupb_map *******************************************************************/ |
| |
| /* A map object. Implemented by wrapping upb_map. |
| * |
| * When the value type is string/bytes/message, the userval consists of: |
| * |
| * [Lua number/string] -> [lupb_string/lupb_msg userdata] |
| * |
| * For other value types we don't use the userdata. |
| */ |
| |
| typedef struct { |
| const lupb_msgclass *value_lmsgclass; |
| upb_fieldtype_t key_type; |
| upb_fieldtype_t value_type; |
| upb_map *map; |
| } lupb_map; |
| |
| #define MAP_MSGCLASS_INDEX 0 |
| |
| /* lupb_map internal functions */ |
| |
| static lupb_map *lupb_map_check(lua_State *L, int narg) { |
| return luaL_checkudata(L, narg, LUPB_ARRAY); |
| } |
| |
| /** |
| * lupb_map_typecheck() |
| * |
| * Checks that the lupb_map at index |narg| can be safely assigned to the |
| * field |f| of the message at index |msg|. If so, returns a upb_msgval for |
| * this map. Otherwise, raises a Lua error. |
| */ |
| static upb_msgval lupb_map_typecheck(lua_State *L, int narg, int msg, |
| const upb_fielddef *f) { |
| lupb_map *lmap = lupb_map_check(L, narg); |
| upb_map *map = lmap->map; |
| const upb_msgdef *entry = upb_fielddef_msgsubdef(f); |
| const upb_fielddef *key_field = upb_msgdef_itof(entry, UPB_MAPENTRY_KEY); |
| const upb_fielddef *value_field = upb_msgdef_itof(entry, UPB_MAPENTRY_VALUE); |
| upb_msgval val; |
| |
| UPB_ASSERT(entry && key_field && value_field); |
| |
| if (lmap->key_type != upb_fielddef_type(key_field)) { |
| luaL_error(L, "Map had incorrect field type (expected: %s, got: %s)", |
| upb_fielddef_type(key_field), lmap->key_type); |
| } |
| |
| if (lmap->value_type != upb_fielddef_type(value_field)) { |
| luaL_error(L, "Map had incorrect value type (expected: %s, got: %s)", |
| upb_fielddef_type(value_field), lmap->value_type); |
| } |
| |
| if (lmap->value_type == UPB_TYPE_MESSAGE) { |
| lupb_msgclass_typecheck( |
| L, lupb_msg_msgclassfor(L, msg, upb_fielddef_msgsubdef(value_field)), |
| lmap->value_lmsgclass); |
| } |
| |
| val.map_val = map; |
| return val; |
| } |
| |
| /* lupb_map Public API */ |
| |
| /** |
| * lupb_map_new |
| * |
| * Handles: |
| * new_map = upb.Map(key_type, value_type) |
| */ |
| static int lupb_map_new(lua_State *L) { |
| lupb_map *lmap; |
| upb_fieldtype_t key_type = lupb_checkfieldtype(L, 1); |
| upb_fieldtype_t value_type; |
| const lupb_msgclass *value_lmsgclass = NULL; |
| |
| if (lua_type(L, 2) == LUA_TNUMBER) { |
| value_type = lupb_checkfieldtype(L, 2); |
| } else { |
| value_type = UPB_TYPE_MESSAGE; |
| } |
| |
| lmap = lupb_newuserdata(L, sizeof(*lmap), LUPB_MAP); |
| |
| if (value_type == UPB_TYPE_MESSAGE) { |
| value_lmsgclass = lupb_msgclass_check(L, 2); |
| lupb_uservalseti(L, -1, MAP_MSGCLASS_INDEX, 2); /* GC-root lmsgclass. */ |
| } |
| |
| lmap->key_type = key_type; |
| lmap->value_type = value_type; |
| lmap->value_lmsgclass = value_lmsgclass; |
| lmap->map = upb_map_new(lupb_arena_get(L), key_type, value_type); |
| |
| return 1; |
| } |
| |
| /** |
| * lupb_map_index |
| * |
| * Handles: |
| * map[key] |
| */ |
| static int lupb_map_index(lua_State *L) { |
| lupb_map *lmap = lupb_map_check(L, 1); |
| upb_map *map = lmap->map; |
| upb_fieldtype_t valtype = lmap->value_type; |
| /* We don't always use "key", but this call checks the key type. */ |
| upb_msgval key = lupb_tomsgval(L, lmap->key_type, 2, NULL); |
| |
| if (lupb_istypewrapped(valtype)) { |
| /* Userval contains the full map, lookup there by key. */ |
| lupb_getuservalue(L, 1); |
| lua_pushvalue(L, 2); |
| lua_rawget(L, -2); |
| |
| if (lua_isnil(L, -1)) { |
| /* TODO: lazy read from upb_map */ |
| } |
| } else { |
| /* Lookup in upb_map. */ |
| upb_msgval val; |
| if (upb_map_get(map, key, &val)) { |
| lupb_pushmsgval(L, lmap->value_type, val); |
| } else { |
| lua_pushnil(L); |
| } |
| } |
| |
| return 1; |
| } |
| |
| /** |
| * lupb_map_len |
| * |
| * Handles: |
| * map_len = #map |
| */ |
| static int lupb_map_len(lua_State *L) { |
| lupb_map *lmap = lupb_map_check(L, 1); |
| lua_pushnumber(L, upb_map_size(lmap->map)); |
| return 1; |
| } |
| |
| /** |
| * lupb_map_newindex |
| * |
| * Handles: |
| * map[key] = val |
| * map[key] = nil # to remove from map |
| */ |
| static int lupb_map_newindex(lua_State *L) { |
| lupb_map *lmap = lupb_map_check(L, 1); |
| upb_map *map = lmap->map; |
| upb_msgval key = lupb_tomsgval(L, lmap->key_type, 2, NULL); |
| |
| if (lua_isnil(L, 3)) { |
| /* Delete from map. */ |
| upb_map_delete(map, key); |
| |
| if (lupb_istypewrapped(lmap->value_type)) { |
| /* Delete in userval. */ |
| lupb_getuservalue(L, 1); |
| lua_pushvalue(L, 2); |
| lua_pushnil(L); |
| lua_rawset(L, -3); |
| lua_pop(L, 1); |
| } |
| } else { |
| /* Set in map. */ |
| upb_msgval val = |
| lupb_tomsgval(L, lmap->value_type, 3, lmap->value_lmsgclass); |
| |
| upb_map_set(map, key, val, lupb_arena_get(L)); |
| |
| if (lupb_istypewrapped(lmap->value_type)) { |
| /* Set in userval. */ |
| lupb_getuservalue(L, 1); |
| lua_pushvalue(L, 2); |
| lua_pushvalue(L, 3); |
| lua_rawset(L, -3); |
| lua_pop(L, 1); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* upb_mapiter [[[ */ |
| |
| static int lupb_mapiter_next(lua_State *L) { |
| upb_mapiter *i = lua_touserdata(L, lua_upvalueindex(1)); |
| lupb_map *lmap = lupb_map_check(L, 1); |
| |
| if (upb_mapiter_done(i)) { |
| return 0; |
| } |
| |
| lupb_pushmsgval(L, lmap->key_type, upb_mapiter_key(i)); |
| lupb_pushmsgval(L, lmap->value_type, upb_mapiter_value(i)); |
| upb_mapiter_next(i); |
| |
| return 2; |
| } |
| |
| static int lupb_map_pairs(lua_State *L) { |
| lupb_map *lmap = lupb_map_check(L, 1); |
| |
| if (lupb_istypewrapped(lmap->key_type) || |
| lupb_istypewrapped(lmap->value_type)) { |
| /* Complex key or value type. |
| * Sync upb_map to userval if necessary, then iterate over userval. */ |
| |
| /* TODO: Lua tables don't know how many entries they have, gah!. */ |
| return 1; |
| } else { |
| /* Simple key and value type, iterate over the upb_map directly. */ |
| upb_mapiter *i = lua_newuserdata(L, upb_mapiter_sizeof()); |
| |
| upb_mapiter_begin(i, lmap->map); |
| lua_pushvalue(L, 1); |
| |
| /* Upvalues are [upb_mapiter, lupb_map]. */ |
| lua_pushcclosure(L, &lupb_mapiter_next, 2); |
| |
| return 1; |
| } |
| } |
| |
| /* upb_mapiter ]]] */ |
| |
| static const struct luaL_Reg lupb_map_mm[] = { |
| {"__index", lupb_map_index}, |
| {"__len", lupb_map_len}, |
| {"__newindex", lupb_map_newindex}, |
| {"__pairs", lupb_map_pairs}, |
| {NULL, NULL} |
| }; |
| |
| |
| /* lupb_msg *******************************************************************/ |
| |
| /* A message object. Implemented by wrapping upb_msg. |
| * |
| * Our userval contains: |
| * |
| * - [0] -> our message class |
| * - [lupb_fieldindex(f)] -> [lupb_{string,array,map,msg} userdata] |
| * |
| * Fields with scalar number/bool types don't go in the userval. |
| */ |
| |
| #define LUPB_MSG_MSGCLASSINDEX 0 |
| #define LUPB_MSG_ARENA -1 |
| |
| int lupb_fieldindex(const upb_fielddef *f) { |
| return upb_fielddef_index(f) + 1; /* 1-based Lua arrays. */ |
| } |
| |
| |
| typedef struct { |
| const lupb_msgclass *lmsgclass; |
| upb_msg *msg; |
| } lupb_msg; |
| |
| /* lupb_msg helpers */ |
| |
| static bool in_userval(const upb_fielddef *f) { |
| return lupb_istypewrapped(upb_fielddef_type(f)) || upb_fielddef_isseq(f) || |
| upb_fielddef_ismap(f); |
| } |
| |
| lupb_msg *lupb_msg_check(lua_State *L, int narg) { |
| lupb_msg *msg = luaL_checkudata(L, narg, LUPB_MSG); |
| if (!msg->lmsgclass) luaL_error(L, "called into dead msg"); |
| return msg; |
| } |
| |
| const upb_msg *lupb_msg_checkmsg(lua_State *L, int narg, |
| const lupb_msgclass *lmsgclass) { |
| lupb_msg *lmsg = lupb_msg_check(L, narg); |
| lupb_msgclass_typecheck(L, lmsgclass, lmsg->lmsgclass); |
| return lmsg->msg; |
| } |
| |
| upb_msg *lupb_msg_checkmsg2(lua_State *L, int narg, |
| const upb_msglayout **layout) { |
| lupb_msg *lmsg = lupb_msg_check(L, narg); |
| *layout = lmsg->lmsgclass->layout; |
| return lmsg->msg; |
| } |
| |
| const upb_msgdef *lupb_msg_checkdef(lua_State *L, int narg) { |
| return lupb_msg_check(L, narg)->lmsgclass->msgdef; |
| } |
| |
| static const upb_fielddef *lupb_msg_checkfield(lua_State *L, |
| const lupb_msg *msg, |
| int fieldarg) { |
| size_t len; |
| const char *fieldname = luaL_checklstring(L, fieldarg, &len); |
| const upb_msgdef *msgdef = msg->lmsgclass->msgdef; |
| const upb_fielddef *f = upb_msgdef_ntof(msgdef, fieldname, len); |
| |
| if (!f) { |
| const char *msg = lua_pushfstring(L, "no such field: %s", fieldname); |
| luaL_argerror(L, fieldarg, msg); |
| return NULL; /* Never reached. */ |
| } |
| |
| return f; |
| } |
| |
| static const lupb_msgclass *lupb_msg_msgclassfor(lua_State *L, int narg, |
| const upb_msgdef *md) { |
| lupb_uservalgeti(L, narg, LUPB_MSG_MSGCLASSINDEX); |
| return lupb_msgclass_msgclassfor(L, -1, md); |
| } |
| |
| static const lupb_msgclass *lupb_msg_getsubmsgclass(lua_State *L, int narg, |
| const upb_fielddef *f) { |
| lupb_uservalgeti(L, narg, LUPB_MSG_MSGCLASSINDEX); |
| return lupb_msgclass_getsubmsgclass(L, -1, f); |
| } |
| |
| int lupb_msg_pushref(lua_State *L, int msgclass, upb_msg *msg) { |
| const lupb_msgclass *lmsgclass = lupb_msgclass_check(L, msgclass); |
| lupb_msg *lmsg = lupb_newuserdata(L, sizeof(lupb_msg), LUPB_MSG); |
| |
| lmsg->lmsgclass = lmsgclass; |
| lmsg->msg = msg; |
| |
| lupb_uservalseti(L, -1, LUPB_MSG_MSGCLASSINDEX, msgclass); |
| lupb_uservalseti(L, -1, LUPB_MSG_ARENA, -2); |
| |
| return 1; |
| } |
| |
| /* lupb_msg Public API */ |
| |
| /** |
| * lupb_msg_pushnew |
| * |
| * Handles: |
| * new_msg = MessageClass() |
| */ |
| static int lupb_msg_pushnew(lua_State *L, int narg) { |
| const lupb_msgclass *lmsgclass = lupb_msgclass_check(L, narg); |
| lupb_msg *lmsg = lupb_newuserdata(L, sizeof(lupb_msg), LUPB_MSG); |
| |
| lmsg->lmsgclass = lmsgclass; |
| lmsg->msg = upb_msg_new(lmsgclass->layout, lupb_arena_get(L)); |
| |
| lupb_uservalseti(L, -1, LUPB_MSG_MSGCLASSINDEX, narg); |
| |
| return 1; |
| } |
| |
| /** |
| * lupb_msg_index |
| * |
| * Handles: |
| * msg.foo |
| * msg["foo"] |
| * msg[field_descriptor] # (for extensions) (TODO) |
| */ |
| static int lupb_msg_index(lua_State *L) { |
| lupb_msg *lmsg = lupb_msg_check(L, 1); |
| const upb_fielddef *f = lupb_msg_checkfield(L, lmsg, 2); |
| const upb_msglayout *l = lmsg->lmsgclass->layout; |
| |
| if (in_userval(f)) { |
| lupb_uservalgeti(L, 1, lupb_fieldindex(f)); |
| |
| if (lua_isnil(L, -1)) { |
| /* Check if we need to lazily create wrapper. */ |
| if (upb_fielddef_isseq(f)) { |
| /* TODO(haberman) */ |
| } else if (upb_fielddef_issubmsg(f)) { |
| /* TODO(haberman) */ |
| } else { |
| UPB_ASSERT(upb_fielddef_isstring(f)); |
| if (upb_msg_has(lmsg->msg, f, l)) { |
| upb_msgval val = upb_msg_get(lmsg->msg, f, l); |
| lua_pop(L, 1); |
| lua_pushlstring(L, val.str_val.data, val.str_val.size); |
| lupb_uservalseti(L, 1, lupb_fieldindex(f), -1); |
| } |
| } |
| } |
| } else { |
| upb_msgval val = upb_msg_get(lmsg->msg, f, l); |
| lupb_pushmsgval(L, upb_fielddef_type(f), val); |
| } |
| |
| return 1; |
| } |
| |
| /** |
| * lupb_msg_newindex() |
| * |
| * Handles: |
| * msg.foo = bar |
| * msg["foo"] = bar |
| * msg[field_descriptor] = bar # (for extensions) (TODO) |
| */ |
| static int lupb_msg_newindex(lua_State *L) { |
| lupb_msg *lmsg = lupb_msg_check(L, 1); |
| const upb_fielddef *f = lupb_msg_checkfield(L, lmsg, 2); |
| upb_fieldtype_t type = upb_fielddef_type(f); |
| upb_arena *arena = lupb_arena_get(L); |
| const upb_msglayout *layout = lmsg->lmsgclass->layout; |
| upb_msgval msgval; |
| |
| /* Typecheck and get msgval. */ |
| |
| if (upb_fielddef_isseq(f)) { |
| msgval = lupb_array_typecheck(L, 3, 1, f); |
| } else if (upb_fielddef_ismap(f)) { |
| msgval = lupb_map_typecheck(L, 3, 1, f); |
| } else { |
| const lupb_msgclass *lmsgclass = NULL; |
| |
| if (type == UPB_TYPE_MESSAGE) { |
| lmsgclass = lupb_msg_getsubmsgclass(L, 1, f); |
| } |
| |
| msgval = lupb_tomsgval(L, type, 3, lmsgclass); |
| } |
| |
| /* Set in upb_msg and userval (if necessary). */ |
| |
| upb_msg_set(lmsg->msg, f, msgval, layout, arena); |
| |
| if (in_userval(f)) { |
| lupb_uservalseti(L, 1, lupb_fieldindex(f), 3); |
| } |
| |
| return 0; /* 1 for chained assignments? */ |
| } |
| |
| static const struct luaL_Reg lupb_msg_mm[] = { |
| {"__index", lupb_msg_index}, |
| {"__newindex", lupb_msg_newindex}, |
| {NULL, NULL} |
| }; |
| |
| |
| /* lupb_msg toplevel **********************************************************/ |
| |
| static const struct luaL_Reg lupb_msg_toplevel_m[] = { |
| {"Array", lupb_array_new}, |
| {"Map", lupb_map_new}, |
| {"MessageFactory", lupb_msgfactory_new}, |
| {NULL, NULL} |
| }; |
| |
| void lupb_msg_registertypes(lua_State *L) { |
| lupb_setfuncs(L, lupb_msg_toplevel_m); |
| |
| lupb_register_type(L, LUPB_ARENA, NULL, lupb_arena_mm); |
| lupb_register_type(L, LUPB_MSGCLASS, NULL, lupb_msgclass_mm); |
| lupb_register_type(L, LUPB_MSGFACTORY, lupb_msgfactory_m, lupb_msgfactory_mm); |
| lupb_register_type(L, LUPB_ARRAY, NULL, lupb_array_mm); |
| lupb_register_type(L, LUPB_MAP, NULL, lupb_map_mm); |
| lupb_register_type(L, LUPB_MSG, NULL, lupb_msg_mm); |
| |
| lupb_arena_initsingleton(L); |
| } |