From 4b0c4ca7fb0aa9207af3398e04534b23fbb88f27 Mon Sep 17 00:00:00 2001 From: Josh Haberman Date: Fri, 9 Sep 2016 14:03:25 -0700 Subject: New upb_msg code and Lua bindings around it. There are still some things that are unfinished, but we are at parity with what Lua had before. --- upb/bindings/lua/msg.c | 1073 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1073 insertions(+) create mode 100644 upb/bindings/lua/msg.c (limited to 'upb/bindings/lua/msg.c') diff --git a/upb/bindings/lua/msg.c b/upb/bindings/lua/msg.c new file mode 100644 index 0000000..6783b23 --- /dev/null +++ b/upb/bindings/lua/msg.c @@ -0,0 +1,1073 @@ +/* +** lupb_msg -- Message/Array/Map objects in Lua/C that wrap upb/msg.h +*/ + +#include +#include +#include +#include +#include +#include "lauxlib.h" +#include "upb/bindings/lua/upb.h" +#include "upb/handlers.h" +#include "upb/msg.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_alloc *****************************************************************/ + +typedef struct { + upb_alloc alloc; + lua_State *L; +} lupb_alloc; + +char lupb_alloc_cache_key; + +static void *lupb_alloc_func(upb_alloc *alloc, void *ptr, size_t oldsize, + size_t size) { + lupb_alloc *lalloc = (lupb_alloc*)alloc; + void *ud; + + /* We read this every time in case the user changes the Lua alloc function. */ + lua_Alloc func = lua_getallocf(lalloc->L, &ud); + + return func(ud, ptr, oldsize, size); +} + +static void lupb_alloc_pushnew(lua_State *L) { + lupb_alloc *lalloc = lua_newuserdata(L, sizeof(lupb_alloc)); + + lalloc->alloc.func = &lupb_alloc_func; + lalloc->L = L; +} + +/* Returns the global lupb_alloc func that was created in our luaopen(). + * Callers can be guaranteed that it will be alive as long as |L| is. */ +static upb_alloc *lupb_alloc_get(lua_State *L) { + lupb_alloc *lalloc; + + lua_pushlightuserdata(L, &lupb_alloc_cache_key); + lua_gettable(L, LUA_REGISTRYINDEX); + lalloc = lua_touserdata(L, -1); + UPB_ASSERT(lalloc); + lua_pop(L, 1); + + return &lalloc->alloc; +} + +static void lupb_alloc_initsingleton(lua_State *L) { + lua_pushlightuserdata(L, &lupb_alloc_cache_key); + lupb_alloc_pushnew(L); + lua_settable(L, LUA_REGISTRYINDEX); +} + +#define ADD_BYTES(ptr, bytes) ((void*)((char*)ptr + bytes)) + + +/* 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. */ + +upb_arena *lupb_arena_check(lua_State *L, int narg) { + return luaL_checkudata(L, narg, LUPB_ARENA); +} + +int lupb_arena_new(lua_State *L) { + upb_arena *a = lupb_newuserdata(L, sizeof(upb_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. */ + upb_arena_init(a); + + return 1; +} + +static int lupb_arena_gc(lua_State *L) { + upb_arena *a = lupb_arena_check(L, 1); + upb_arena_uninit(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_msglayout *l); + +/* 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) { + const lupb_msgfactory *lfactory = lupb_msgfactory_check(L, narg); + + 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, + upb_msgfactory_getlayout(lfactory->factory, 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(msgdef) + */ +static int lupb_msgfactory_getmsgclass(lua_State *L) { + lupb_msgfactory_pushmsgclass(L, 1, lupb_msgdef_check(L, 2)); + 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 **************************************************************/ + +#define LUPB_MSGCLASS_FACTORY 1 +#define LUPB_MSGCLASS_MSGDEF 2 + +struct lupb_msgclass { + const upb_msglayout *layout; + const lupb_msgfactory *lfactory; +}; + +/* Checks that the given object is a lupb_msg with the given lmsgclass. */ +static upb_msgval lupb_msg_typecheck(lua_State *L, int narg, + const lupb_msgclass *lmsgclass); + +/* 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); + +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_handlers *lupb_msgclass_getmergehandlers(lua_State *L, int narg) { + lupb_msgclass *lmsgclass = lupb_msgclass_check(L, narg); + return upb_msgfactory_getmergehandlers( + lmsgclass->lfactory->factory, upb_msglayout_msgdef(lmsgclass->layout)); +} + +/** + * 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(upb_msglayout_msgdef(expected->layout)), + upb_msgdef_fullname(upb_msglayout_msgdef(actual->layout))); + } +} + +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_msglayout *l) { + const lupb_msgfactory *lfactory = lupb_msgfactory_check(L, factory); + lupb_msgclass *lmc = lupb_newuserdata(L, sizeof(*lmc), LUPB_MSGCLASS); + + UPB_ASSERT(l); + lupb_uservalseti(L, -1, LUPB_MSGCLASS_FACTORY, factory); + lmc->layout = l; + lmc->lfactory = lfactory; + + 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) { + switch (type) { + case UPB_TYPE_INT32: + case UPB_TYPE_ENUM: + return upb_msgval_int32(lupb_checkint32(L, narg)); + case UPB_TYPE_INT64: + return upb_msgval_int64(lupb_checkint64(L, narg)); + case UPB_TYPE_UINT32: + return upb_msgval_uint32(lupb_checkuint32(L, narg)); + case UPB_TYPE_UINT64: + return upb_msgval_uint64(lupb_checkuint64(L, narg)); + case UPB_TYPE_DOUBLE: + return upb_msgval_double(lupb_checkdouble(L, narg)); + case UPB_TYPE_FLOAT: + return upb_msgval_float(lupb_checkfloat(L, narg)); + case UPB_TYPE_BOOL: + return upb_msgval_bool(lupb_checkbool(L, narg)); + case UPB_TYPE_STRING: + case UPB_TYPE_BYTES: { + size_t len; + const char *ptr = lupb_checkstring(L, narg, &len); + return upb_msgval_str(ptr, len); + } + case UPB_TYPE_MESSAGE: + UPB_ASSERT(lmsgclass); + return lupb_msg_typecheck(L, narg, lmsgclass); + } + UPB_UNREACHABLE(); +} + +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, upb_msgval_getint32(val)); + return; + case UPB_TYPE_INT64: + lupb_pushint64(L, upb_msgval_getint64(val)); + return; + case UPB_TYPE_UINT32: + lupb_pushuint32(L, upb_msgval_getuint32(val)); + return; + case UPB_TYPE_UINT64: + lupb_pushuint64(L, upb_msgval_getuint64(val)); + return; + case UPB_TYPE_DOUBLE: + lupb_pushdouble(L, upb_msgval_getdouble(val)); + return; + case UPB_TYPE_FLOAT: + lupb_pushfloat(L, upb_msgval_getfloat(val)); + return; + case UPB_TYPE_BOOL: + lua_pushboolean(L, upb_msgval_getbool(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. */ + lupb_msgclass *lmsgclass; + upb_array *arr; +} 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); + + if (upb_array_type(larray->arr) != 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)upb_array_type(larray->arr)); + } + + if (upb_array_type(larray->arr) == UPB_TYPE_MESSAGE) { + lupb_msgclass_typecheck(L, lupb_msg_getsubmsgclass(L, msg, f), + larray->lmsgclass); + } + + return upb_msgval_arr(larray->arr); +} + +/** + * 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. :( */ +} + +static int lupb_array_gc(lua_State *L) { + lupb_array *larray = lupb_array_check(L, 1); + upb_array_uninit(larray->arr); + return 0; +} + +/* lupb_array Public API */ + +static int lupb_array_new(lua_State *L) { + lupb_array *larray; + upb_fieldtype_t type; + 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) + upb_array_sizeof(type), LUPB_ARRAY); + larray->lmsgclass = lmsgclass; + larray->arr = ADD_BYTES(larray, sizeof(*larray)); + upb_array_init(larray->arr, type, lupb_alloc_get(L)); + + return 1; +} + +static int lupb_array_newindex(lua_State *L) { + lupb_array *larray = lupb_array_check(L, 1); + upb_fieldtype_t type = upb_array_type(larray->arr); + uint32_t n = lupb_array_checkindex(L, 2, upb_array_size(larray->arr) + 1); + upb_msgval msgval = lupb_tomsgval(L, type, 3, larray->lmsgclass); + + 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); + upb_array *array = larray->arr; + uint32_t n = lupb_array_checkindex(L, 2, upb_array_size(array)); + upb_fieldtype_t type = upb_array_type(array); + + if (lupb_istypewrapped(type)) { + lupb_uservalgeti(L, 1, n); + } else { + lupb_pushmsgval(L, upb_array_type(array), upb_array_get(array, n)); + } + + 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[] = { + {"__gc", lupb_array_gc}, + {"__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_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_ASSERT(entry && key_field && value_field); + + if (upb_map_keytype(map) != upb_fielddef_type(key_field)) { + luaL_error(L, "Map key type invalid"); + } + + if (upb_map_valuetype(map) != upb_fielddef_type(value_field)) { + luaL_error(L, "Map had incorrect value type (expected: %s, got: %s)", + upb_fielddef_type(value_field), upb_map_valuetype(map)); + } + + if (upb_map_valuetype(map) == UPB_TYPE_MESSAGE) { + lupb_msgclass_typecheck( + L, lupb_msg_msgclassfor(L, msg, upb_fielddef_msgsubdef(value_field)), + lmap->value_lmsgclass); + } + + return upb_msgval_map(map); +} + +static int lupb_map_gc(lua_State *L) { + lupb_map *lmap = lupb_map_check(L, 1); + upb_map_uninit(lmap->map); + return 0; +} + +/* 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; + 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) + upb_map_sizeof(key_type, value_type), LUPB_MAP); + lmap->map = ADD_BYTES(lmap, sizeof(*lmap)); + + 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->value_lmsgclass = value_lmsgclass; + upb_map_init(lmap->map, key_type, value_type, lupb_alloc_get(L)); + + 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 = upb_map_valuetype(map); + /* We don't always use "key", but this call checks the key type. */ + upb_msgval key = lupb_tomsgval(L, upb_map_keytype(map), 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, upb_map_valuetype(map), 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, upb_map_keytype(map), 2, NULL); + + if (lua_isnil(L, 3)) { + /* Delete from map. */ + upb_map_del(map, key); + + if (lupb_istypewrapped(upb_map_valuetype(map))) { + /* 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, upb_map_valuetype(map), 3, lmap->value_lmsgclass); + + upb_map_set(map, key, val, NULL); + + if (lupb_istypewrapped(upb_map_valuetype(map))) { + /* 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); + upb_map *map = lmap->map; + + if (upb_mapiter_done(i)) { + return 0; + } + + lupb_pushmsgval(L, upb_map_keytype(map), upb_mapiter_key(i)); + lupb_pushmsgval(L, upb_map_valuetype(map), 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(upb_map_keytype(lmap->map)) || + lupb_istypewrapped(upb_map_valuetype(lmap->map))) { + /* 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[] = { + {"__gc", lupb_map_gc}, + {"__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; +} + +static upb_msgval lupb_msg_typecheck(lua_State *L, int narg, + const lupb_msgclass *lmsgclass) { + lupb_msg *msg = lupb_msg_check(L, narg); + lupb_msgclass_typecheck(L, lmsgclass, msg->lmsgclass); + return upb_msgval_msg(msg); +} + +const upb_msgdef *lupb_msg_checkdef(lua_State *L, int narg) { + return upb_msglayout_msgdef(lupb_msg_check(L, narg)->lmsgclass->layout); +} + +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 = upb_msglayout_msgdef(msg->lmsgclass->layout); + 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, void *msg) { + 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) { + lupb_msgclass *lmsgclass = lupb_msgclass_check(L, narg); + size_t size = sizeof(lupb_msg) + upb_msg_sizeof(lmsgclass->layout); + lupb_msg *lmsg = lupb_newuserdata(L, size, LUPB_MSG); + + lmsg->lmsgclass = lmsgclass; + lmsg->msg = ADD_BYTES(lmsg, sizeof(*lmsg)); + upb_msg_init(lmsg->msg, lmsgclass->layout, lupb_alloc_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.ptr, val.str.len); + lupb_uservalseti(L, 1, lupb_fieldindex(f), -1); + } + } + } + } else { + lupb_pushmsgval(L, upb_fielddef_type(f), upb_msg_get(lmsg->msg, f, l)); + } + + 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_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, lmsg->lmsgclass->layout); + + 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_alloc_initsingleton(L); +} -- cgit v1.2.3