Reorganize upb file structure This change moves almost everything in the `upb/` directory up one level, so that for example `upb/upb/generated_code_support.h` becomes just `upb/generated_code_support.h`. The only exceptions I made to this were that I left `upb/cmake` and `upb/BUILD` where they are, mostly because that avoids conflict with other files and the current locations seem reasonable for now. The `python/` directory is a little bit of a challenge because we had to merge the existing directory there with `upb/python/`. I made `upb/python/BUILD` into the BUILD file for the merged directory, and it effectively loads the contents of the other BUILD file via `python/build_targets.bzl`, but I plan to clean this up soon. PiperOrigin-RevId: 568651768
diff --git a/lua/BUILD.bazel b/lua/BUILD.bazel new file mode 100644 index 0000000..6216f9c --- /dev/null +++ b/lua/BUILD.bazel
@@ -0,0 +1,112 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +load( + "//bazel:build_defs.bzl", + "UPB_DEFAULT_COPTS", + "UPB_DEFAULT_CPPOPTS", +) +load( + "//lua:lua_proto_library.bzl", + "lua_proto_library", +) + +licenses(["notice"]) + +cc_library( + name = "lupb", + srcs = [ + "def.c", + "msg.c", + "upb.c", + ], + hdrs = [ + "upb.h", + ], + copts = UPB_DEFAULT_COPTS, + visibility = ["//visibility:public"], + deps = [ + "//upb:json", + "//upb:message", + "//upb:reflection", + "//upb:text", + "@lua//:liblua", + ], +) + +cc_binary( + name = "protoc-gen-lua", + srcs = ["upbc.cc"], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + "//src/google/protobuf/compiler:code_generator", + "@com_google_absl//absl/strings", + ], +) + +exports_files(["upb.lua"]) + +cc_test( + name = "test_lua", + srcs = ["main.c"], + args = ["$(location :test_upb.lua)"], + copts = UPB_DEFAULT_COPTS, + data = [ + "test_upb.lua", + "upb.lua", + ":descriptor_proto_lua", + ":empty_proto_lua", + ":test_messages_proto2_proto_lua", + ":test_messages_proto3_proto_lua", + ":test_proto_lua", + "//:descriptor_proto", + "//conformance:conformance_proto", + "//third_party/lunit:console.lua", + "//third_party/lunit:lunit.lua", + ], + linkstatic = 1, + deps = [ + ":lupb", + "@lua//:liblua", + ], +) + +proto_library( + name = "test_proto", + testonly = 1, + srcs = ["test.proto"], + deps = ["//:timestamp_proto"], +) + +lua_proto_library( + name = "test_proto_lua", + testonly = 1, + deps = [":test_proto"], +) + +lua_proto_library( + name = "descriptor_proto_lua", + deps = ["//:descriptor_proto"], +) + +lua_proto_library( + name = "empty_proto_lua", + deps = ["//:empty_proto"], +) + +lua_proto_library( + name = "test_messages_proto3_proto_lua", + testonly = 1, + deps = ["//src/google/protobuf:test_messages_proto3_proto"], +) + +lua_proto_library( + name = "test_messages_proto2_proto_lua", + testonly = 1, + deps = ["//src/google/protobuf:test_messages_proto2_proto"], +)
diff --git a/lua/README.md b/lua/README.md new file mode 100644 index 0000000..9374f26 --- /dev/null +++ b/lua/README.md
@@ -0,0 +1,8 @@ +# upb Lua bindings + +These are some bare-bones upb bindings for Lua. + +These bindings exist primarily for experimentation and testing. +They are incomplete and are not really intended for use in any application. +This is by no means a complete or supported protobuf library, and in fact +we don't even claim it to be functional.
diff --git a/lua/def.c b/lua/def.c new file mode 100644 index 0000000..9affe52 --- /dev/null +++ b/lua/def.c
@@ -0,0 +1,943 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "upb/reflection/def.h" + +#include <float.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "lauxlib.h" +#include "lua/upb.h" +#include "upb/reflection/message.h" + +#define LUPB_ENUMDEF "lupb.enumdef" +#define LUPB_ENUMVALDEF "lupb.enumvaldef" +#define LUPB_FIELDDEF "lupb.fielddef" +#define LUPB_FILEDEF "lupb.filedef" +#define LUPB_MSGDEF "lupb.msgdef" +#define LUPB_ONEOFDEF "lupb.oneof" +#define LUPB_SYMTAB "lupb.defpool" +#define LUPB_OBJCACHE "lupb.objcache" + +static void lupb_DefPool_pushwrapper(lua_State* L, int narg, const void* def, + const char* type); + +/* lupb_wrapper ***************************************************************/ + +/* Wrappers around upb def objects. The userval contains a reference to the + * defpool. */ + +#define LUPB_SYMTAB_INDEX 1 + +typedef struct { + const void* def; /* upb_MessageDef, upb_EnumDef, upb_OneofDef, etc. */ +} lupb_wrapper; + +static const void* lupb_wrapper_check(lua_State* L, int narg, + const char* type) { + lupb_wrapper* w = luaL_checkudata(L, narg, type); + return w->def; +} + +static void lupb_wrapper_pushdefpool(lua_State* L, int narg) { + lua_getiuservalue(L, narg, LUPB_SYMTAB_INDEX); +} + +/* lupb_wrapper_pushwrapper() + * + * For a given def wrapper at index |narg|, pushes a wrapper for the given |def| + * and the given |type|. The new wrapper will be part of the same defpool. */ +static void lupb_wrapper_pushwrapper(lua_State* L, int narg, const void* def, + const char* type) { + lupb_wrapper_pushdefpool(L, narg); + lupb_DefPool_pushwrapper(L, -1, def, type); + lua_replace(L, -2); /* Remove defpool from stack. */ +} + +/* lupb_MessageDef_pushsubmsgdef() + * + * Pops the msgdef wrapper at the top of the stack and replaces it with a msgdef + * wrapper for field |f| of this msgdef (submsg may not be direct, for example + * it may be the submessage of the map value). + */ +void lupb_MessageDef_pushsubmsgdef(lua_State* L, const upb_FieldDef* f) { + const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f); + assert(m); + lupb_wrapper_pushwrapper(L, -1, m, LUPB_MSGDEF); + lua_replace(L, -2); /* Replace msgdef with submsgdef. */ +} + +/* lupb_FieldDef **************************************************************/ + +const upb_FieldDef* lupb_FieldDef_check(lua_State* L, int narg) { + return lupb_wrapper_check(L, narg, LUPB_FIELDDEF); +} + +static int lupb_FieldDef_ContainingOneof(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + const upb_OneofDef* o = upb_FieldDef_ContainingOneof(f); + lupb_wrapper_pushwrapper(L, 1, o, LUPB_ONEOFDEF); + return 1; +} + +static int lupb_FieldDef_ContainingType(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + const upb_MessageDef* m = upb_FieldDef_ContainingType(f); + lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF); + return 1; +} + +static int lupb_FieldDef_Default(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + upb_CType type = upb_FieldDef_CType(f); + if (type == kUpb_CType_Message) { + return luaL_error(L, "Message fields do not have explicit defaults."); + } + lupb_pushmsgval(L, 0, type, upb_FieldDef_Default(f)); + return 1; +} + +static int lupb_FieldDef_Type(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushnumber(L, upb_FieldDef_Type(f)); + return 1; +} + +static int lupb_FieldDef_HasSubDef(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushboolean(L, upb_FieldDef_HasSubDef(f)); + return 1; +} + +static int lupb_FieldDef_Index(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushinteger(L, upb_FieldDef_Index(f)); + return 1; +} + +static int lupb_FieldDef_IsExtension(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushboolean(L, upb_FieldDef_IsExtension(f)); + return 1; +} + +static int lupb_FieldDef_Label(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushinteger(L, upb_FieldDef_Label(f)); + return 1; +} + +static int lupb_FieldDef_Name(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushstring(L, upb_FieldDef_Name(f)); + return 1; +} + +static int lupb_FieldDef_Number(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + int32_t num = upb_FieldDef_Number(f); + if (num) { + lua_pushinteger(L, num); + } else { + lua_pushnil(L); + } + return 1; +} + +static int lupb_FieldDef_IsPacked(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushboolean(L, upb_FieldDef_IsPacked(f)); + return 1; +} + +static int lupb_FieldDef_MessageSubDef(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f); + lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF); + return 1; +} + +static int lupb_FieldDef_EnumSubDef(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + const upb_EnumDef* e = upb_FieldDef_EnumSubDef(f); + lupb_wrapper_pushwrapper(L, 1, e, LUPB_ENUMDEF); + return 1; +} + +static int lupb_FieldDef_CType(lua_State* L) { + const upb_FieldDef* f = lupb_FieldDef_check(L, 1); + lua_pushinteger(L, upb_FieldDef_CType(f)); + return 1; +} + +static const struct luaL_Reg lupb_FieldDef_m[] = { + {"containing_oneof", lupb_FieldDef_ContainingOneof}, + {"containing_type", lupb_FieldDef_ContainingType}, + {"default", lupb_FieldDef_Default}, + {"descriptor_type", lupb_FieldDef_Type}, + {"has_subdef", lupb_FieldDef_HasSubDef}, + {"index", lupb_FieldDef_Index}, + {"is_extension", lupb_FieldDef_IsExtension}, + {"label", lupb_FieldDef_Label}, + {"name", lupb_FieldDef_Name}, + {"number", lupb_FieldDef_Number}, + {"packed", lupb_FieldDef_IsPacked}, + {"msgsubdef", lupb_FieldDef_MessageSubDef}, + {"enumsubdef", lupb_FieldDef_EnumSubDef}, + {"type", lupb_FieldDef_CType}, + {NULL, NULL}}; + +/* lupb_OneofDef **************************************************************/ + +const upb_OneofDef* lupb_OneofDef_check(lua_State* L, int narg) { + return lupb_wrapper_check(L, narg, LUPB_ONEOFDEF); +} + +static int lupb_OneofDef_ContainingType(lua_State* L) { + const upb_OneofDef* o = lupb_OneofDef_check(L, 1); + const upb_MessageDef* m = upb_OneofDef_ContainingType(o); + lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF); + return 1; +} + +static int lupb_OneofDef_Field(lua_State* L) { + const upb_OneofDef* o = lupb_OneofDef_check(L, 1); + int32_t idx = lupb_checkint32(L, 2); + int count = upb_OneofDef_FieldCount(o); + + if (idx < 0 || idx >= count) { + const char* msg = + lua_pushfstring(L, "index %d exceeds field count %d", idx, count); + return luaL_argerror(L, 2, msg); + } + + lupb_wrapper_pushwrapper(L, 1, upb_OneofDef_Field(o, idx), LUPB_FIELDDEF); + return 1; +} + +static int lupb_oneofiter_next(lua_State* L) { + const upb_OneofDef* o = lupb_OneofDef_check(L, lua_upvalueindex(1)); + int* index = lua_touserdata(L, lua_upvalueindex(2)); + const upb_FieldDef* f; + if (*index == upb_OneofDef_FieldCount(o)) return 0; + f = upb_OneofDef_Field(o, (*index)++); + lupb_wrapper_pushwrapper(L, lua_upvalueindex(1), f, LUPB_FIELDDEF); + return 1; +} + +static int lupb_OneofDef_Fields(lua_State* L) { + int* index = lua_newuserdata(L, sizeof(int)); + lupb_OneofDef_check(L, 1); + *index = 0; + + /* Closure upvalues are: oneofdef, index. */ + lua_pushcclosure(L, &lupb_oneofiter_next, 2); + return 1; +} + +static int lupb_OneofDef_len(lua_State* L) { + const upb_OneofDef* o = lupb_OneofDef_check(L, 1); + lua_pushinteger(L, upb_OneofDef_FieldCount(o)); + return 1; +} + +/* lupb_OneofDef_lookupfield() + * + * Handles: + * oneof.lookup_field(field_number) + * oneof.lookup_field(field_name) + */ +static int lupb_OneofDef_lookupfield(lua_State* L) { + const upb_OneofDef* o = lupb_OneofDef_check(L, 1); + const upb_FieldDef* f; + + switch (lua_type(L, 2)) { + case LUA_TNUMBER: + f = upb_OneofDef_LookupNumber(o, lua_tointeger(L, 2)); + break; + case LUA_TSTRING: + f = upb_OneofDef_LookupName(o, lua_tostring(L, 2)); + break; + default: { + const char* msg = lua_pushfstring(L, "number or string expected, got %s", + luaL_typename(L, 2)); + return luaL_argerror(L, 2, msg); + } + } + + lupb_wrapper_pushwrapper(L, 1, f, LUPB_FIELDDEF); + return 1; +} + +static int lupb_OneofDef_Name(lua_State* L) { + const upb_OneofDef* o = lupb_OneofDef_check(L, 1); + lua_pushstring(L, upb_OneofDef_Name(o)); + return 1; +} + +static const struct luaL_Reg lupb_OneofDef_m[] = { + {"containing_type", lupb_OneofDef_ContainingType}, + {"field", lupb_OneofDef_Field}, + {"fields", lupb_OneofDef_Fields}, + {"lookup_field", lupb_OneofDef_lookupfield}, + {"name", lupb_OneofDef_Name}, + {NULL, NULL}}; + +static const struct luaL_Reg lupb_OneofDef_mm[] = {{"__len", lupb_OneofDef_len}, + {NULL, NULL}}; + +/* lupb_MessageDef + * ****************************************************************/ + +typedef struct { + const upb_MessageDef* md; +} lupb_MessageDef; + +const upb_MessageDef* lupb_MessageDef_check(lua_State* L, int narg) { + return lupb_wrapper_check(L, narg, LUPB_MSGDEF); +} + +static int lupb_MessageDef_FieldCount(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + lua_pushinteger(L, upb_MessageDef_FieldCount(m)); + return 1; +} + +static int lupb_MessageDef_OneofCount(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + lua_pushinteger(L, upb_MessageDef_OneofCount(m)); + return 1; +} + +static bool lupb_MessageDef_pushnested(lua_State* L, int msgdef, int name) { + const upb_MessageDef* m = lupb_MessageDef_check(L, msgdef); + lupb_wrapper_pushdefpool(L, msgdef); + upb_DefPool* defpool = lupb_DefPool_check(L, -1); + lua_pop(L, 1); + + /* Construct full package.Message.SubMessage name. */ + lua_pushstring(L, upb_MessageDef_FullName(m)); + lua_pushstring(L, "."); + lua_pushvalue(L, name); + lua_concat(L, 3); + const char* nested_name = lua_tostring(L, -1); + + /* Try lookup. */ + const upb_MessageDef* nested = + upb_DefPool_FindMessageByName(defpool, nested_name); + if (!nested) return false; + lupb_wrapper_pushwrapper(L, msgdef, nested, LUPB_MSGDEF); + return true; +} + +/* lupb_MessageDef_Field() + * + * Handles: + * msg.field(field_number) -> fielddef + * msg.field(field_name) -> fielddef + */ +static int lupb_MessageDef_Field(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + const upb_FieldDef* f; + + switch (lua_type(L, 2)) { + case LUA_TNUMBER: + f = upb_MessageDef_FindFieldByNumber(m, lua_tointeger(L, 2)); + break; + case LUA_TSTRING: + f = upb_MessageDef_FindFieldByName(m, lua_tostring(L, 2)); + break; + default: { + const char* msg = lua_pushfstring(L, "number or string expected, got %s", + luaL_typename(L, 2)); + return luaL_argerror(L, 2, msg); + } + } + + lupb_wrapper_pushwrapper(L, 1, f, LUPB_FIELDDEF); + return 1; +} + +/* lupb_MessageDef_FindByNameWithSize() + * + * Handles: + * msg.lookup_name(name) -> fielddef or oneofdef + */ +static int lupb_MessageDef_FindByNameWithSize(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + const upb_FieldDef* f; + const upb_OneofDef* o; + + if (!upb_MessageDef_FindByName(m, lua_tostring(L, 2), &f, &o)) { + lua_pushnil(L); + } else if (o) { + lupb_wrapper_pushwrapper(L, 1, o, LUPB_ONEOFDEF); + } else { + lupb_wrapper_pushwrapper(L, 1, f, LUPB_FIELDDEF); + } + + return 1; +} + +/* lupb_MessageDef_Name() + * + * Handles: + * msg.name() -> string + */ +static int lupb_MessageDef_Name(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + lua_pushstring(L, upb_MessageDef_Name(m)); + return 1; +} + +static int lupb_msgfielditer_next(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, lua_upvalueindex(1)); + int* index = lua_touserdata(L, lua_upvalueindex(2)); + const upb_FieldDef* f; + if (*index == upb_MessageDef_FieldCount(m)) return 0; + f = upb_MessageDef_Field(m, (*index)++); + lupb_wrapper_pushwrapper(L, lua_upvalueindex(1), f, LUPB_FIELDDEF); + return 1; +} + +static int lupb_MessageDef_Fields(lua_State* L) { + int* index = lua_newuserdata(L, sizeof(int)); + lupb_MessageDef_check(L, 1); + *index = 0; + + /* Closure upvalues are: msgdef, index. */ + lua_pushcclosure(L, &lupb_msgfielditer_next, 2); + return 1; +} + +static int lupb_MessageDef_File(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + const upb_FileDef* file = upb_MessageDef_File(m); + lupb_wrapper_pushwrapper(L, 1, file, LUPB_FILEDEF); + return 1; +} + +static int lupb_MessageDef_FullName(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + lua_pushstring(L, upb_MessageDef_FullName(m)); + return 1; +} + +static int lupb_MessageDef_index(lua_State* L) { + if (!lupb_MessageDef_pushnested(L, 1, 2)) { + luaL_error(L, "No such nested message"); + } + return 1; +} + +static int lupb_msgoneofiter_next(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, lua_upvalueindex(1)); + int* index = lua_touserdata(L, lua_upvalueindex(2)); + const upb_OneofDef* o; + if (*index == upb_MessageDef_OneofCount(m)) return 0; + o = upb_MessageDef_Oneof(m, (*index)++); + lupb_wrapper_pushwrapper(L, lua_upvalueindex(1), o, LUPB_ONEOFDEF); + return 1; +} + +static int lupb_MessageDef_Oneofs(lua_State* L) { + int* index = lua_newuserdata(L, sizeof(int)); + lupb_MessageDef_check(L, 1); + *index = 0; + + /* Closure upvalues are: msgdef, index. */ + lua_pushcclosure(L, &lupb_msgoneofiter_next, 2); + return 1; +} + +static int lupb_MessageDef_IsMapEntry(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + lua_pushboolean(L, upb_MessageDef_IsMapEntry(m)); + return 1; +} + +static int lupb_MessageDef_Syntax(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + lua_pushinteger(L, upb_MessageDef_Syntax(m)); + return 1; +} + +static int lupb_MessageDef_tostring(lua_State* L) { + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + lua_pushfstring(L, "<upb.MessageDef name=%s, field_count=%d>", + upb_MessageDef_FullName(m), + (int)upb_MessageDef_FieldCount(m)); + return 1; +} + +static const struct luaL_Reg lupb_MessageDef_mm[] = { + {"__call", lupb_MessageDef_call}, + {"__index", lupb_MessageDef_index}, + {"__len", lupb_MessageDef_FieldCount}, + {"__tostring", lupb_MessageDef_tostring}, + {NULL, NULL}}; + +static const struct luaL_Reg lupb_MessageDef_m[] = { + {"field", lupb_MessageDef_Field}, + {"fields", lupb_MessageDef_Fields}, + {"field_count", lupb_MessageDef_FieldCount}, + {"file", lupb_MessageDef_File}, + {"full_name", lupb_MessageDef_FullName}, + {"lookup_name", lupb_MessageDef_FindByNameWithSize}, + {"name", lupb_MessageDef_Name}, + {"oneof_count", lupb_MessageDef_OneofCount}, + {"oneofs", lupb_MessageDef_Oneofs}, + {"syntax", lupb_MessageDef_Syntax}, + {"_map_entry", lupb_MessageDef_IsMapEntry}, + {NULL, NULL}}; + +/* lupb_EnumDef ***************************************************************/ + +const upb_EnumDef* lupb_EnumDef_check(lua_State* L, int narg) { + return lupb_wrapper_check(L, narg, LUPB_ENUMDEF); +} + +static int lupb_EnumDef_len(lua_State* L) { + const upb_EnumDef* e = lupb_EnumDef_check(L, 1); + lua_pushinteger(L, upb_EnumDef_ValueCount(e)); + return 1; +} + +static int lupb_EnumDef_File(lua_State* L) { + const upb_EnumDef* e = lupb_EnumDef_check(L, 1); + const upb_FileDef* file = upb_EnumDef_File(e); + lupb_wrapper_pushwrapper(L, 1, file, LUPB_FILEDEF); + return 1; +} + +/* lupb_EnumDef_Value() + * + * Handles: + * enum.value(number) -> enumval + * enum.value(name) -> enumval + */ +static int lupb_EnumDef_Value(lua_State* L) { + const upb_EnumDef* e = lupb_EnumDef_check(L, 1); + const upb_EnumValueDef* ev; + + switch (lua_type(L, 2)) { + case LUA_TNUMBER: + ev = upb_EnumDef_FindValueByNumber(e, lupb_checkint32(L, 2)); + break; + case LUA_TSTRING: + ev = upb_EnumDef_FindValueByName(e, lua_tostring(L, 2)); + break; + default: { + const char* msg = lua_pushfstring(L, "number or string expected, got %s", + luaL_typename(L, 2)); + return luaL_argerror(L, 2, msg); + } + } + + lupb_wrapper_pushwrapper(L, 1, ev, LUPB_ENUMVALDEF); + return 1; +} + +static const struct luaL_Reg lupb_EnumDef_mm[] = {{"__len", lupb_EnumDef_len}, + {NULL, NULL}}; + +static const struct luaL_Reg lupb_EnumDef_m[] = { + {"file", lupb_EnumDef_File}, {"value", lupb_EnumDef_Value}, {NULL, NULL}}; + +/* lupb_EnumValueDef + * ************************************************************/ + +const upb_EnumValueDef* lupb_enumvaldef_check(lua_State* L, int narg) { + return lupb_wrapper_check(L, narg, LUPB_ENUMVALDEF); +} + +static int lupb_EnumValueDef_Enum(lua_State* L) { + const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1); + const upb_EnumDef* e = upb_EnumValueDef_Enum(ev); + lupb_wrapper_pushwrapper(L, 1, e, LUPB_ENUMDEF); + return 1; +} + +static int lupb_EnumValueDef_FullName(lua_State* L) { + const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1); + lua_pushstring(L, upb_EnumValueDef_FullName(ev)); + return 1; +} + +static int lupb_EnumValueDef_Name(lua_State* L) { + const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1); + lua_pushstring(L, upb_EnumValueDef_Name(ev)); + return 1; +} + +static int lupb_EnumValueDef_Number(lua_State* L) { + const upb_EnumValueDef* ev = lupb_enumvaldef_check(L, 1); + lupb_pushint32(L, upb_EnumValueDef_Number(ev)); + return 1; +} + +static const struct luaL_Reg lupb_enumvaldef_m[] = { + {"enum", lupb_EnumValueDef_Enum}, + {"full_name", lupb_EnumValueDef_FullName}, + {"name", lupb_EnumValueDef_Name}, + {"number", lupb_EnumValueDef_Number}, + {NULL, NULL}}; + +/* lupb_FileDef ***************************************************************/ + +const upb_FileDef* lupb_FileDef_check(lua_State* L, int narg) { + return lupb_wrapper_check(L, narg, LUPB_FILEDEF); +} + +static int lupb_FileDef_Dependency(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + int index = luaL_checkint(L, 2); + const upb_FileDef* dep = upb_FileDef_Dependency(f, index); + lupb_wrapper_pushwrapper(L, 1, dep, LUPB_FILEDEF); + return 1; +} + +static int lupb_FileDef_DependencyCount(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + lua_pushnumber(L, upb_FileDef_DependencyCount(f)); + return 1; +} + +static int lupb_FileDef_enum(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + int index = luaL_checkint(L, 2); + const upb_EnumDef* e = upb_FileDef_TopLevelEnum(f, index); + lupb_wrapper_pushwrapper(L, 1, e, LUPB_ENUMDEF); + return 1; +} + +static int lupb_FileDef_enumcount(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + lua_pushnumber(L, upb_FileDef_TopLevelEnumCount(f)); + return 1; +} + +static int lupb_FileDef_msg(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + int index = luaL_checkint(L, 2); + const upb_MessageDef* m = upb_FileDef_TopLevelMessage(f, index); + lupb_wrapper_pushwrapper(L, 1, m, LUPB_MSGDEF); + return 1; +} + +static int lupb_FileDef_msgcount(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + lua_pushnumber(L, upb_FileDef_TopLevelMessageCount(f)); + return 1; +} + +static int lupb_FileDef_Name(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + lua_pushstring(L, upb_FileDef_Name(f)); + return 1; +} + +static int lupb_FileDef_Package(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + lua_pushstring(L, upb_FileDef_Package(f)); + return 1; +} + +static int lupb_FileDef_Pool(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + const upb_DefPool* defpool = upb_FileDef_Pool(f); + lupb_wrapper_pushwrapper(L, 1, defpool, LUPB_SYMTAB); + return 1; +} + +static int lupb_FileDef_Syntax(lua_State* L) { + const upb_FileDef* f = lupb_FileDef_check(L, 1); + lua_pushnumber(L, upb_FileDef_Syntax(f)); + return 1; +} + +static const struct luaL_Reg lupb_FileDef_m[] = { + {"dep", lupb_FileDef_Dependency}, + {"depcount", lupb_FileDef_DependencyCount}, + {"enum", lupb_FileDef_enum}, + {"enumcount", lupb_FileDef_enumcount}, + {"msg", lupb_FileDef_msg}, + {"msgcount", lupb_FileDef_msgcount}, + {"name", lupb_FileDef_Name}, + {"package", lupb_FileDef_Package}, + {"defpool", lupb_FileDef_Pool}, + {"syntax", lupb_FileDef_Syntax}, + {NULL, NULL}}; + +/* lupb_DefPool + * ****************************************************************/ + +/* The defpool owns all defs. Thus GC-rooting the defpool ensures that all + * underlying defs stay alive. + * + * The defpool's userval is a cache of def* -> object. */ + +#define LUPB_CACHE_INDEX 1 + +typedef struct { + upb_DefPool* defpool; +} lupb_DefPool; + +upb_DefPool* lupb_DefPool_check(lua_State* L, int narg) { + lupb_DefPool* ldefpool = luaL_checkudata(L, narg, LUPB_SYMTAB); + if (!ldefpool->defpool) { + luaL_error(L, "called into dead object"); + } + return ldefpool->defpool; +} + +void lupb_DefPool_pushwrapper(lua_State* L, int narg, const void* def, + const char* type) { + narg = lua_absindex(L, narg); + assert(luaL_testudata(L, narg, LUPB_SYMTAB)); + + if (def == NULL) { + lua_pushnil(L); + return; + } + + lua_getiuservalue(L, narg, LUPB_CACHE_INDEX); /* Get cache. */ + + /* Index by "def" pointer. */ + lua_rawgetp(L, -1, def); + + /* Stack is now: cache, cached value. */ + if (lua_isnil(L, -1)) { + /* Create new wrapper. */ + lupb_wrapper* w = lupb_newuserdata(L, sizeof(*w), 1, type); + w->def = def; + lua_replace(L, -2); /* Replace nil */ + + /* Set defpool as userval. */ + lua_pushvalue(L, narg); + lua_setiuservalue(L, -2, LUPB_SYMTAB_INDEX); + + /* Add wrapper to the the cache. */ + lua_pushvalue(L, -1); + lua_rawsetp(L, -3, def); + } + + lua_replace(L, -2); /* Remove cache, leaving only the wrapper. */ +} + +/* upb_DefPool_New() + * + * Handles: + * upb.DefPool() -> <new instance> + */ +static int lupb_DefPool_New(lua_State* L) { + lupb_DefPool* ldefpool = + lupb_newuserdata(L, sizeof(*ldefpool), 1, LUPB_SYMTAB); + ldefpool->defpool = upb_DefPool_New(); + + /* Create our object cache. */ + lua_newtable(L); + + /* Cache metatable: specifies that values are weak. */ + lua_createtable(L, 0, 1); + lua_pushstring(L, "v"); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L, -2); + + /* Put the defpool itself in the cache metatable. */ + lua_pushvalue(L, -2); + lua_rawsetp(L, -2, ldefpool->defpool); + + /* Set the cache as our userval. */ + lua_setiuservalue(L, -2, LUPB_CACHE_INDEX); + + return 1; +} + +static int lupb_DefPool_gc(lua_State* L) { + lupb_DefPool* ldefpool = luaL_checkudata(L, 1, LUPB_SYMTAB); + upb_DefPool_Free(ldefpool->defpool); + ldefpool->defpool = NULL; + return 0; +} + +static int lupb_DefPool_AddFile(lua_State* L) { + size_t len; + upb_DefPool* s = lupb_DefPool_check(L, 1); + const char* str = luaL_checklstring(L, 2, &len); + upb_Arena* arena = lupb_Arena_pushnew(L); + const google_protobuf_FileDescriptorProto* file; + const upb_FileDef* file_def; + upb_Status status; + + upb_Status_Clear(&status); + file = google_protobuf_FileDescriptorProto_parse(str, len, arena); + + if (!file) { + luaL_argerror(L, 2, "failed to parse descriptor"); + } + + file_def = upb_DefPool_AddFile(s, file, &status); + lupb_checkstatus(L, &status); + + lupb_DefPool_pushwrapper(L, 1, file_def, LUPB_FILEDEF); + + return 1; +} + +static int lupb_DefPool_addset(lua_State* L) { + size_t i, n, len; + const google_protobuf_FileDescriptorProto* const* files; + google_protobuf_FileDescriptorSet* set; + upb_DefPool* s = lupb_DefPool_check(L, 1); + const char* str = luaL_checklstring(L, 2, &len); + upb_Arena* arena = lupb_Arena_pushnew(L); + upb_Status status; + + upb_Status_Clear(&status); + set = google_protobuf_FileDescriptorSet_parse(str, len, arena); + + if (!set) { + luaL_argerror(L, 2, "failed to parse descriptor"); + } + + files = google_protobuf_FileDescriptorSet_file(set, &n); + for (i = 0; i < n; i++) { + upb_DefPool_AddFile(s, files[i], &status); + lupb_checkstatus(L, &status); + } + + return 0; +} + +static int lupb_DefPool_FindMessageByName(lua_State* L) { + const upb_DefPool* s = lupb_DefPool_check(L, 1); + const upb_MessageDef* m = + upb_DefPool_FindMessageByName(s, luaL_checkstring(L, 2)); + lupb_DefPool_pushwrapper(L, 1, m, LUPB_MSGDEF); + return 1; +} + +static int lupb_DefPool_FindEnumByName(lua_State* L) { + const upb_DefPool* s = lupb_DefPool_check(L, 1); + const upb_EnumDef* e = upb_DefPool_FindEnumByName(s, luaL_checkstring(L, 2)); + lupb_DefPool_pushwrapper(L, 1, e, LUPB_ENUMDEF); + return 1; +} + +static int lupb_DefPool_FindEnumByNameval(lua_State* L) { + const upb_DefPool* s = lupb_DefPool_check(L, 1); + const upb_EnumValueDef* e = + upb_DefPool_FindEnumByNameval(s, luaL_checkstring(L, 2)); + lupb_DefPool_pushwrapper(L, 1, e, LUPB_ENUMVALDEF); + return 1; +} + +static int lupb_DefPool_tostring(lua_State* L) { + lua_pushfstring(L, "<upb.DefPool>"); + return 1; +} + +static const struct luaL_Reg lupb_DefPool_m[] = { + {"add_file", lupb_DefPool_AddFile}, + {"add_set", lupb_DefPool_addset}, + {"lookup_msg", lupb_DefPool_FindMessageByName}, + {"lookup_enum", lupb_DefPool_FindEnumByName}, + {"lookup_enumval", lupb_DefPool_FindEnumByNameval}, + {NULL, NULL}}; + +static const struct luaL_Reg lupb_DefPool_mm[] = { + {"__gc", lupb_DefPool_gc}, + {"__tostring", lupb_DefPool_tostring}, + {NULL, NULL}}; + +/* lupb toplevel **************************************************************/ + +static void lupb_setfieldi(lua_State* L, const char* field, int i) { + lua_pushinteger(L, i); + lua_setfield(L, -2, field); +} + +static const struct luaL_Reg lupbdef_toplevel_m[] = { + {"DefPool", lupb_DefPool_New}, {NULL, NULL}}; + +void lupb_def_registertypes(lua_State* L) { + lupb_setfuncs(L, lupbdef_toplevel_m); + + /* Register types. */ + lupb_register_type(L, LUPB_ENUMDEF, lupb_EnumDef_m, lupb_EnumDef_mm); + lupb_register_type(L, LUPB_ENUMVALDEF, lupb_enumvaldef_m, NULL); + lupb_register_type(L, LUPB_FIELDDEF, lupb_FieldDef_m, NULL); + lupb_register_type(L, LUPB_FILEDEF, lupb_FileDef_m, NULL); + lupb_register_type(L, LUPB_MSGDEF, lupb_MessageDef_m, lupb_MessageDef_mm); + lupb_register_type(L, LUPB_ONEOFDEF, lupb_OneofDef_m, lupb_OneofDef_mm); + lupb_register_type(L, LUPB_SYMTAB, lupb_DefPool_m, lupb_DefPool_mm); + + /* Register constants. */ + lupb_setfieldi(L, "LABEL_OPTIONAL", kUpb_Label_Optional); + lupb_setfieldi(L, "LABEL_REQUIRED", kUpb_Label_Required); + lupb_setfieldi(L, "LABEL_REPEATED", kUpb_Label_Repeated); + + lupb_setfieldi(L, "TYPE_DOUBLE", kUpb_CType_Double); + lupb_setfieldi(L, "TYPE_FLOAT", kUpb_CType_Float); + lupb_setfieldi(L, "TYPE_INT64", kUpb_CType_Int64); + lupb_setfieldi(L, "TYPE_UINT64", kUpb_CType_UInt64); + lupb_setfieldi(L, "TYPE_INT32", kUpb_CType_Int32); + lupb_setfieldi(L, "TYPE_BOOL", kUpb_CType_Bool); + lupb_setfieldi(L, "TYPE_STRING", kUpb_CType_String); + lupb_setfieldi(L, "TYPE_MESSAGE", kUpb_CType_Message); + lupb_setfieldi(L, "TYPE_BYTES", kUpb_CType_Bytes); + lupb_setfieldi(L, "TYPE_UINT32", kUpb_CType_UInt32); + lupb_setfieldi(L, "TYPE_ENUM", kUpb_CType_Enum); + + lupb_setfieldi(L, "DESCRIPTOR_TYPE_DOUBLE", kUpb_FieldType_Double); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_FLOAT", kUpb_FieldType_Float); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_INT64", kUpb_FieldType_Int64); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_UINT64", kUpb_FieldType_UInt64); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_INT32", kUpb_FieldType_Int32); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_FIXED64", kUpb_FieldType_Fixed64); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_FIXED32", kUpb_FieldType_Fixed32); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_BOOL", kUpb_FieldType_Bool); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_STRING", kUpb_FieldType_String); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_GROUP", kUpb_FieldType_Group); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_MESSAGE", kUpb_FieldType_Message); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_BYTES", kUpb_FieldType_Bytes); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_UINT32", kUpb_FieldType_UInt32); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_ENUM", kUpb_FieldType_Enum); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_SFIXED32", kUpb_FieldType_SFixed32); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_SFIXED64", kUpb_FieldType_SFixed64); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_SINT32", kUpb_FieldType_SInt32); + lupb_setfieldi(L, "DESCRIPTOR_TYPE_SINT64", kUpb_FieldType_SInt64); + + lupb_setfieldi(L, "SYNTAX_PROTO2", kUpb_Syntax_Proto2); + lupb_setfieldi(L, "SYNTAX_PROTO3", kUpb_Syntax_Proto3); +}
diff --git a/lua/lua_proto_library.bzl b/lua/lua_proto_library.bzl new file mode 100644 index 0000000..579eca1 --- /dev/null +++ b/lua/lua_proto_library.bzl
@@ -0,0 +1,136 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""lua_proto_library(): a rule for building Lua protos.""" + +load("@bazel_skylib//lib:paths.bzl", "paths") + +# Generic support code ######################################################### + +# begin:github_only +_is_google3 = False +# end:github_only + +# begin:google_only +# _is_google3 = True +# end:google_only + +def _get_real_short_path(file): + # For some reason, files from other archives have short paths that look like: + # ../com_google_protobuf/google/protobuf/descriptor.proto + short_path = file.short_path + if short_path.startswith("../"): + second_slash = short_path.index("/", 3) + short_path = short_path[second_slash + 1:] + + # Sometimes it has another few prefixes like: + # _virtual_imports/any_proto/google/protobuf/any.proto + # benchmarks/_virtual_imports/100_msgs_proto/benchmarks/100_msgs.proto + # We want just google/protobuf/any.proto. + virtual_imports = "_virtual_imports/" + if virtual_imports in short_path: + short_path = short_path.split(virtual_imports)[1].split("/", 1)[1] + return short_path + +def _get_real_root(ctx, file): + real_short_path = _get_real_short_path(file) + root = file.path[:-len(real_short_path) - 1] + if not _is_google3 and ctx.rule.attr.strip_import_prefix: + root = paths.join(root, ctx.rule.attr.strip_import_prefix[1:]) + return root + +def _generate_output_file(ctx, src, extension): + package = ctx.label.package + if not _is_google3 and ctx.rule.attr.strip_import_prefix and ctx.rule.attr.strip_import_prefix != "/": + package = package[len(ctx.rule.attr.strip_import_prefix):] + real_short_path = _get_real_short_path(src) + real_short_path = paths.relativize(real_short_path, package) + output_filename = paths.replace_extension(real_short_path, extension) + ret = ctx.actions.declare_file(output_filename) + return ret + +# upb_proto_library / upb_proto_reflection_library shared code ################# + +_LuaFilesInfo = provider( + "A set of lua files generated from .proto files", + fields = ["files"], +) + +def _compile_upb_protos(ctx, proto_info, proto_sources): + files = [_generate_output_file(ctx, name, "_pb.lua") for name in proto_sources] + transitive_sets = proto_info.transitive_descriptor_sets.to_list() + ctx.actions.run( + inputs = depset( + direct = [proto_info.direct_descriptor_set], + transitive = [proto_info.transitive_descriptor_sets], + ), + tools = [ctx.executable._upbc], + outputs = files, + executable = ctx.executable._protoc, + arguments = [ + "--lua_out=" + _get_real_root(ctx, files[0]), + "--plugin=protoc-gen-lua=" + ctx.executable._upbc.path, + "--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets]), + ] + + [_get_real_short_path(file) for file in proto_sources], + progress_message = "Generating Lua protos for :" + ctx.label.name, + ) + return files + +def _lua_proto_rule_impl(ctx): + if len(ctx.attr.deps) != 1: + fail("only one deps dependency allowed.") + dep = ctx.attr.deps[0] + if _LuaFilesInfo not in dep: + fail("proto_library rule must generate _LuaFilesInfo (aspect should have handled this).") + files = dep[_LuaFilesInfo].files + return [ + DefaultInfo( + files = files, + data_runfiles = ctx.runfiles(files = files.to_list()), + ), + ] + +def _lua_proto_library_aspect_impl(target, ctx): + proto_info = target[ProtoInfo] + files = _compile_upb_protos(ctx, proto_info, proto_info.direct_sources) + deps = ctx.rule.attr.deps + transitive = [dep[_LuaFilesInfo].files for dep in deps if _LuaFilesInfo in dep] + return [_LuaFilesInfo(files = depset(direct = files, transitive = transitive))] + +# lua_proto_library() ########################################################## + +_lua_proto_library_aspect = aspect( + attrs = { + "_upbc": attr.label( + executable = True, + cfg = "exec", + default = "//lua:protoc-gen-lua", + ), + "_protoc": attr.label( + executable = True, + cfg = "exec", + default = "//:protoc", + ), + }, + implementation = _lua_proto_library_aspect_impl, + provides = [_LuaFilesInfo], + attr_aspects = ["deps"], + fragments = ["cpp"], +) + +lua_proto_library = rule( + output_to_genfiles = True, + implementation = _lua_proto_rule_impl, + attrs = { + "deps": attr.label_list( + aspects = [_lua_proto_library_aspect], + allow_rules = ["proto_library"], + providers = [ProtoInfo], + ), + }, +)
diff --git a/lua/main.c b/lua/main.c new file mode 100644 index 0000000..7f32209 --- /dev/null +++ b/lua/main.c
@@ -0,0 +1,96 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <lauxlib.h> +#include <lua.h> +#include <lualib.h> +#include <signal.h> + +#include "lua/upb.h" + +lua_State* L; + +static void interrupt(lua_State* L, lua_Debug* ar) { + (void)ar; + lua_sethook(L, NULL, 0, 0); + luaL_error(L, "SIGINT"); +} + +static void sighandler(int i) { + fprintf(stderr, "Signal!\n"); + signal(i, SIG_DFL); + lua_sethook(L, interrupt, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); +} + +const char* init = + "package.preload['lupb'] = ... " + "package.path = '" + "./?.lua;" + "./third_party/lunit/?.lua;" + "external/com_google_protobuf/?.lua;" + "external/com_google_protobuf/src/?.lua;" + "bazel-bin/?.lua;" + "bazel-bin/external/com_google_protobuf/src/?.lua;" + "bazel-bin/external/com_google_protobuf/?.lua;" + "lua/?.lua;" + // These additional paths handle the case where this test is invoked from + // the protobuf repo's Bazel workspace. + "external/?.lua;" + "external/third_party/lunit/?.lua;" + "src/?.lua;" + "bazel-bin/external/?.lua;" + "external/lua/?.lua" + "'"; + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "missing argument with path to .lua file\n"); + return 1; + } + + int ret = 0; + L = luaL_newstate(); + luaL_openlibs(L); + lua_pushcfunction(L, luaopen_lupb); + ret = luaL_loadstring(L, init); + lua_pushcfunction(L, luaopen_lupb); + + signal(SIGINT, sighandler); + ret = ret || lua_pcall(L, 1, LUA_MULTRET, 0) || luaL_dofile(L, argv[1]); + signal(SIGINT, SIG_DFL); + + if (ret) { + fprintf(stderr, "error testing Lua: %s\n", lua_tostring(L, -1)); + ret = 1; + } + + lua_close(L); + return ret; +}
diff --git a/lua/msg.c b/lua/msg.c new file mode 100644 index 0000000..bdec7a2 --- /dev/null +++ b/lua/msg.c
@@ -0,0 +1,1118 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* + * lupb_Message -- Message/Array/Map objects in Lua/C that wrap upb + */ + +#include <float.h> +#include <math.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "lauxlib.h" +#include "lua/upb.h" +#include "upb/collections/map.h" +#include "upb/json/decode.h" +#include "upb/json/encode.h" +#include "upb/message/message.h" +#include "upb/port/def.inc" +#include "upb/reflection/message.h" +#include "upb/text/encode.h" + +/* + * Message/Map/Array objects. These objects form a directed graph: a message + * can contain submessages, arrays, and maps, which can then point to other + * messages. This graph can technically be cyclic, though this is an error and + * a cyclic graph cannot be serialized. So it's better to think of this as a + * tree of objects. + * + * The actual data exists at the upb level (upb_Message, upb_Map, upb_Array), + * independently of Lua. The upb objects contain all the canonical data and + * edges between objects. Lua wrapper objects expose the upb objects to Lua, + * but ultimately they are just wrappers. They pass through all reads and + * writes to the underlying upb objects. + * + * Each upb object lives in a upb arena. We have a Lua object to wrap the upb + * arena, but arenas are never exposed to the user. The Lua arena object just + * serves to own the upb arena and free it at the proper time, once the Lua GC + * has determined that there are no more references to anything that lives in + * that arena. All wrapper objects strongly reference the arena to which they + * belong. + * + * A global object cache stores a mapping of C pointer (upb_Message*, + * upb_Array*, upb_Map*) to a corresponding Lua wrapper. These references are + * weak so that the wrappers can be collected if they are no longer needed. A + * new wrapper object can always be recreated later. + * + * +-----+ + * lupb_Arena |cache|-weak-+ + * | ^ +-----+ | + * | | V + * Lua level | +------------lupb_Message + * ----------------|-----------------|------------------------------------------ + * upb level | | + * | +----V----------------------------------+ + * +->upb_Arena | upb_Message ...(empty arena storage) | + * +---------------------------------------+ + * + * If the user creates a reference between two objects that have different + * arenas, we need to fuse the two arenas together, so that the blocks will + * outlive both arenas. + * + * +-------------------------->(fused)<----------------+ + * | | + * V +-----+ V + * lupb_Arena +-weak-|cache|-weak-+ lupb_Arena + * | ^ | +-----+ | ^ | + * | | V V | | + * Lua level | +------------lupb_Message lupb_Message--+ | + * ----------------|-----------------|----------------------|-----------|------ + * upb level | | | | + * | +----V--------+ +----V--------+ V + * +->upb_Arena | upb_Message | | upb_Message | upb_Arena + * +------|------+ +--^----------+ + * +------------------+ + * Key invariants: + * 1. every wrapper references the arena that contains it. + * 2. every fused arena includes all arenas that own upb objects reachable + * from that arena. In other words, when a wrapper references an arena, + * this is sufficient to ensure that any upb object reachable from that + * wrapper will stay alive. + * + * Additionally, every message object contains a strong reference to the + * corresponding Descriptor object. Likewise, array/map objects reference a + * Descriptor object if they are typed to store message values. + */ + +#define LUPB_ARENA "lupb.arena" +#define LUPB_ARRAY "lupb.array" +#define LUPB_MAP "lupb.map" +#define LUPB_MSG "lupb.msg" + +#define LUPB_ARENA_INDEX 1 +#define LUPB_MSGDEF_INDEX 2 /* For msg, and map/array that store msg */ + +static void lupb_Message_Newmsgwrapper(lua_State* L, int narg, + upb_MessageValue val); +static upb_Message* lupb_msg_check(lua_State* L, int narg); + +static upb_CType lupb_checkfieldtype(lua_State* L, int narg) { + uint32_t n = lupb_checkuint32(L, narg); + bool ok = n >= kUpb_CType_Bool && n <= kUpb_CType_Bytes; + luaL_argcheck(L, ok, narg, "invalid field type"); + return n; +} + +char cache_key; + +/* lupb_cacheinit() + * + * Creates the global cache used by lupb_cacheget() and lupb_cacheset(). + */ +static void lupb_cacheinit(lua_State* L) { + /* Create our object cache. */ + lua_newtable(L); + + /* Cache metatable gives the cache weak values */ + lua_createtable(L, 0, 1); + lua_pushstring(L, "v"); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L, -2); + + /* Set cache in the registry. */ + lua_rawsetp(L, LUA_REGISTRYINDEX, &cache_key); +} + +/* lupb_cacheget() + * + * Pushes cache[key] and returns true if this key is present in the cache. + * Otherwise returns false and leaves nothing on the stack. + */ +static bool lupb_cacheget(lua_State* L, const void* key) { + if (key == NULL) { + lua_pushnil(L); + return true; + } + + lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key); + lua_rawgetp(L, -1, key); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); /* Pop table, nil. */ + return false; + } else { + lua_replace(L, -2); /* Replace cache table. */ + return true; + } +} + +/* lupb_cacheset() + * + * Sets cache[key] = val, where "val" is the value at the top of the stack. + * Does not pop the value. + */ +static void lupb_cacheset(lua_State* L, const void* key) { + lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key); + lua_pushvalue(L, -2); + lua_rawsetp(L, -2, key); + lua_pop(L, 1); /* Pop table. */ +} + +/* 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 wrapper objects refer to this + * object from their userdata to keep the arena-owned data alive. + */ + +typedef struct { + upb_Arena* arena; +} lupb_Arena; + +static upb_Arena* lupb_Arena_check(lua_State* L, int narg) { + lupb_Arena* a = luaL_checkudata(L, narg, LUPB_ARENA); + return a->arena; +} + +upb_Arena* lupb_Arena_pushnew(lua_State* L) { + lupb_Arena* a = lupb_newuserdata(L, sizeof(lupb_Arena), 1, LUPB_ARENA); + a->arena = upb_Arena_New(); + return a->arena; +} + +/** + * lupb_Arena_Fuse() + * + * Merges |from| into |to| so that there is a single arena group that contains + * both, and both arenas will point at this new table. */ +static void lupb_Arena_Fuse(lua_State* L, int to, int from) { + upb_Arena* to_arena = lupb_Arena_check(L, to); + upb_Arena* from_arena = lupb_Arena_check(L, from); + upb_Arena_Fuse(to_arena, from_arena); +} + +static void lupb_Arena_Fuseobjs(lua_State* L, int to, int from) { + lua_getiuservalue(L, to, LUPB_ARENA_INDEX); + lua_getiuservalue(L, from, LUPB_ARENA_INDEX); + lupb_Arena_Fuse(L, lua_absindex(L, -2), lua_absindex(L, -1)); + lua_pop(L, 2); +} + +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_Arenaget() + * + * Returns the arena from the given message, array, or map object. + */ +static upb_Arena* lupb_Arenaget(lua_State* L, int narg) { + upb_Arena* arena; + lua_getiuservalue(L, narg, LUPB_ARENA_INDEX); + arena = lupb_Arena_check(L, -1); + lua_pop(L, 1); + return arena; +} + +/* upb <-> Lua type conversion ************************************************/ + +/* Whether string data should be copied into the containing arena. We can + * avoid a copy if the string data is only needed temporarily (like for a map + * lookup). + */ +typedef enum { + LUPB_COPY, /* Copy string data into the arena. */ + LUPB_REF /* Reference the Lua copy of the string data. */ +} lupb_copy_t; + +/** + * lupb_tomsgval() + * + * Converts the given Lua value |narg| to a upb_MessageValue. + */ +static upb_MessageValue lupb_tomsgval(lua_State* L, upb_CType type, int narg, + int container, lupb_copy_t copy) { + upb_MessageValue ret; + switch (type) { + case kUpb_CType_Int32: + case kUpb_CType_Enum: + ret.int32_val = lupb_checkint32(L, narg); + break; + case kUpb_CType_Int64: + ret.int64_val = lupb_checkint64(L, narg); + break; + case kUpb_CType_UInt32: + ret.uint32_val = lupb_checkuint32(L, narg); + break; + case kUpb_CType_UInt64: + ret.uint64_val = lupb_checkuint64(L, narg); + break; + case kUpb_CType_Double: + ret.double_val = lupb_checkdouble(L, narg); + break; + case kUpb_CType_Float: + ret.float_val = lupb_checkfloat(L, narg); + break; + case kUpb_CType_Bool: + ret.bool_val = lupb_checkbool(L, narg); + break; + case kUpb_CType_String: + case kUpb_CType_Bytes: { + size_t len; + const char* ptr = lupb_checkstring(L, narg, &len); + switch (copy) { + case LUPB_COPY: { + upb_Arena* arena = lupb_Arenaget(L, container); + char* data = upb_Arena_Malloc(arena, len); + memcpy(data, ptr, len); + ret.str_val = upb_StringView_FromDataAndSize(data, len); + break; + } + case LUPB_REF: + ret.str_val = upb_StringView_FromDataAndSize(ptr, len); + break; + } + break; + } + case kUpb_CType_Message: + ret.msg_val = lupb_msg_check(L, narg); + /* Typecheck message. */ + lua_getiuservalue(L, container, LUPB_MSGDEF_INDEX); + lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); + luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch"); + lua_pop(L, 2); + break; + } + return ret; +} + +void lupb_pushmsgval(lua_State* L, int container, upb_CType type, + upb_MessageValue val) { + switch (type) { + case kUpb_CType_Int32: + case kUpb_CType_Enum: + lupb_pushint32(L, val.int32_val); + return; + case kUpb_CType_Int64: + lupb_pushint64(L, val.int64_val); + return; + case kUpb_CType_UInt32: + lupb_pushuint32(L, val.uint32_val); + return; + case kUpb_CType_UInt64: + lupb_pushuint64(L, val.uint64_val); + return; + case kUpb_CType_Double: + lua_pushnumber(L, val.double_val); + return; + case kUpb_CType_Float: + lua_pushnumber(L, val.float_val); + return; + case kUpb_CType_Bool: + lua_pushboolean(L, val.bool_val); + return; + case kUpb_CType_String: + case kUpb_CType_Bytes: + lua_pushlstring(L, val.str_val.data, val.str_val.size); + return; + case kUpb_CType_Message: + assert(container); + if (!lupb_cacheget(L, val.msg_val)) { + lupb_Message_Newmsgwrapper(L, container, val); + } + return; + } + LUPB_UNREACHABLE(); +} + +/* lupb_array *****************************************************************/ + +typedef struct { + upb_Array* arr; + upb_CType type; +} lupb_array; + +static lupb_array* lupb_array_check(lua_State* L, int narg) { + return luaL_checkudata(L, narg, LUPB_ARRAY); +} + +/** + * 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. + */ +static int lupb_array_checkindex(lua_State* L, int narg, uint32_t max) { + uint32_t n = lupb_checkuint32(L, narg); + luaL_argcheck(L, n != 0 && n <= max, narg, "invalid array index"); + return n - 1; /* Lua uses 1-based indexing. */ +} + +/* lupb_array Public API */ + +/* lupb_Array_New(): + * + * Handles: + * Array(upb.TYPE_INT32) + * Array(message_type) + */ +static int lupb_Array_New(lua_State* L) { + int arg_count = lua_gettop(L); + lupb_array* larray; + upb_Arena* arena; + + if (lua_type(L, 1) == LUA_TNUMBER) { + upb_CType type = lupb_checkfieldtype(L, 1); + larray = lupb_newuserdata(L, sizeof(*larray), 1, LUPB_ARRAY); + larray->type = type; + } else { + lupb_MessageDef_check(L, 1); + larray = lupb_newuserdata(L, sizeof(*larray), 2, LUPB_ARRAY); + larray->type = kUpb_CType_Message; + lua_pushvalue(L, 1); + lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); + } + + arena = lupb_Arena_pushnew(L); + lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); + + larray->arr = upb_Array_New(arena, larray->type); + lupb_cacheset(L, larray->arr); + + if (arg_count > 1) { + /* Set initial fields from table. */ + int msg = arg_count + 1; + lua_pushnil(L); + while (lua_next(L, 2) != 0) { + lua_pushvalue(L, -2); /* now stack is key, val, key */ + lua_insert(L, -3); /* now stack is key, key, val */ + lua_settable(L, msg); + } + } + + return 1; +} + +/* lupb_Array_Newindex(): + * + * Handles: + * array[idx] = val + * + * idx can be within the array or one past the end to extend. + */ +static int lupb_Array_Newindex(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 + 1); + upb_MessageValue msgval = lupb_tomsgval(L, larray->type, 3, 1, LUPB_COPY); + + if (n == size) { + upb_Array_Append(larray->arr, msgval, lupb_Arenaget(L, 1)); + } else { + upb_Array_Set(larray->arr, n, msgval); + } + + if (larray->type == kUpb_CType_Message) { + lupb_Arena_Fuseobjs(L, 1, 3); + } + + return 0; /* 1 for chained assignments? */ +} + +/* lupb_array_index(): + * + * Handles: + * array[idx] -> val + * + * idx must be within the array. + */ +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_MessageValue val = upb_Array_Get(larray->arr, n); + + lupb_pushmsgval(L, 1, larray->type, val); + + return 1; +} + +/* lupb_array_len(): + * + * Handles: + * #array -> len + */ +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 *******************************************************************/ + +typedef struct { + upb_Map* map; + upb_CType key_type; + upb_CType value_type; +} lupb_map; + +#define MAP_MSGDEF_INDEX 1 + +static lupb_map* lupb_map_check(lua_State* L, int narg) { + return luaL_checkudata(L, narg, LUPB_MAP); +} + +/* lupb_map Public API */ + +/** + * lupb_Map_New + * + * Handles: + * new_map = upb.Map(key_type, value_type) + * new_map = upb.Map(key_type, value_msgdef) + */ +static int lupb_Map_New(lua_State* L) { + upb_Arena* arena; + lupb_map* lmap; + + if (lua_type(L, 2) == LUA_TNUMBER) { + lmap = lupb_newuserdata(L, sizeof(*lmap), 1, LUPB_MAP); + lmap->value_type = lupb_checkfieldtype(L, 2); + } else { + lupb_MessageDef_check(L, 2); + lmap = lupb_newuserdata(L, sizeof(*lmap), 2, LUPB_MAP); + lmap->value_type = kUpb_CType_Message; + lua_pushvalue(L, 2); + lua_setiuservalue(L, -2, MAP_MSGDEF_INDEX); + } + + arena = lupb_Arena_pushnew(L); + lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); + + lmap->key_type = lupb_checkfieldtype(L, 1); + lmap->map = upb_Map_New(arena, lmap->key_type, lmap->value_type); + lupb_cacheset(L, lmap->map); + + 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_MessageValue key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF); + upb_MessageValue val; + + if (upb_Map_Get(lmap->map, key, &val)) { + lupb_pushmsgval(L, 1, 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_MessageValue key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF); + + if (lua_isnil(L, 3)) { + upb_Map_Delete(map, key, NULL); + } else { + upb_MessageValue val = lupb_tomsgval(L, lmap->value_type, 3, 1, LUPB_COPY); + upb_Map_Set(map, key, val, lupb_Arenaget(L, 1)); + if (lmap->value_type == kUpb_CType_Message) { + lupb_Arena_Fuseobjs(L, 1, 3); + } + } + + return 0; +} + +static int lupb_MapIterator_Next(lua_State* L) { + int map = lua_upvalueindex(2); + size_t* iter = lua_touserdata(L, lua_upvalueindex(1)); + lupb_map* lmap = lupb_map_check(L, map); + + upb_MessageValue key, val; + if (upb_Map_Next(lmap->map, &key, &val, iter)) { + lupb_pushmsgval(L, map, lmap->key_type, key); + lupb_pushmsgval(L, map, lmap->value_type, val); + return 2; + } else { + return 0; + } +} + +/** + * lupb_map_pairs() + * + * Handles: + * pairs(map) + */ +static int lupb_map_pairs(lua_State* L) { + size_t* iter = lua_newuserdata(L, sizeof(*iter)); + lupb_map_check(L, 1); + + *iter = kUpb_Map_Begin; + lua_pushvalue(L, 1); + + /* Upvalues are [iter, lupb_map]. */ + lua_pushcclosure(L, &lupb_MapIterator_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_Message + * *******************************************************************/ + +typedef struct { + upb_Message* msg; +} lupb_Message; + +/* lupb_Message helpers */ + +static upb_Message* lupb_msg_check(lua_State* L, int narg) { + lupb_Message* msg = luaL_checkudata(L, narg, LUPB_MSG); + return msg->msg; +} + +static const upb_MessageDef* lupb_Message_Getmsgdef(lua_State* L, int msg) { + lua_getiuservalue(L, msg, LUPB_MSGDEF_INDEX); + const upb_MessageDef* m = lupb_MessageDef_check(L, -1); + lua_pop(L, 1); + return m; +} + +static const upb_FieldDef* lupb_msg_tofield(lua_State* L, int msg, int field) { + size_t len; + const char* fieldname = luaL_checklstring(L, field, &len); + const upb_MessageDef* m = lupb_Message_Getmsgdef(L, msg); + return upb_MessageDef_FindFieldByNameWithSize(m, fieldname, len); +} + +static const upb_FieldDef* lupb_msg_checkfield(lua_State* L, int msg, + int field) { + const upb_FieldDef* f = lupb_msg_tofield(L, msg, field); + if (f == NULL) { + luaL_error(L, "no such field '%s'", lua_tostring(L, field)); + } + return f; +} + +upb_Message* lupb_msg_pushnew(lua_State* L, int narg) { + const upb_MessageDef* m = lupb_MessageDef_check(L, narg); + lupb_Message* lmsg = lupb_newuserdata(L, sizeof(lupb_Message), 2, LUPB_MSG); + upb_Arena* arena = lupb_Arena_pushnew(L); + + lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); + lua_pushvalue(L, 1); + lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); + + lmsg->msg = upb_Message_New(upb_MessageDef_MiniTable(m), arena); + lupb_cacheset(L, lmsg->msg); + return lmsg->msg; +} + +/** + * lupb_Message_Newmsgwrapper() + * + * Creates a new wrapper for a message, copying the arena and msgdef references + * from |narg| (which should be an array or map). + */ +static void lupb_Message_Newmsgwrapper(lua_State* L, int narg, + upb_MessageValue val) { + lupb_Message* lmsg = lupb_newuserdata(L, sizeof(*lmsg), 2, LUPB_MSG); + lmsg->msg = (upb_Message*)val.msg_val; /* XXX: cast isn't great. */ + lupb_cacheset(L, lmsg->msg); + + /* Copy both arena and msgdef into the wrapper. */ + lua_getiuservalue(L, narg, LUPB_ARENA_INDEX); + lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); + lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); + lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); +} + +/** + * lupb_Message_Newud() + * + * Creates the Lua userdata for a new wrapper object, adding a reference to + * the msgdef if necessary. + */ +static void* lupb_Message_Newud(lua_State* L, int narg, size_t size, + const char* type, const upb_FieldDef* f) { + if (upb_FieldDef_CType(f) == kUpb_CType_Message) { + /* Wrapper needs a reference to the msgdef. */ + void* ud = lupb_newuserdata(L, size, 2, type); + lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); + lupb_MessageDef_pushsubmsgdef(L, f); + lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); + return ud; + } else { + return lupb_newuserdata(L, size, 1, type); + } +} + +/** + * lupb_Message_Newwrapper() + * + * Creates a new Lua wrapper object to wrap the given array, map, or message. + */ +static void lupb_Message_Newwrapper(lua_State* L, int narg, + const upb_FieldDef* f, + upb_MutableMessageValue val) { + if (upb_FieldDef_IsMap(f)) { + const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(f); + const upb_FieldDef* key_f = + upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_KeyFieldNumber); + const upb_FieldDef* val_f = + upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_ValueFieldNumber); + lupb_map* lmap = + lupb_Message_Newud(L, narg, sizeof(*lmap), LUPB_MAP, val_f); + lmap->key_type = upb_FieldDef_CType(key_f); + lmap->value_type = upb_FieldDef_CType(val_f); + lmap->map = val.map; + } else if (upb_FieldDef_IsRepeated(f)) { + lupb_array* larr = + lupb_Message_Newud(L, narg, sizeof(*larr), LUPB_ARRAY, f); + larr->type = upb_FieldDef_CType(f); + larr->arr = val.array; + } else { + lupb_Message* lmsg = + lupb_Message_Newud(L, narg, sizeof(*lmsg), LUPB_MSG, f); + lmsg->msg = val.msg; + } + + /* Copy arena ref to new wrapper. This may be a different arena than the + * underlying data was originally constructed from, but if so both arenas + * must be in the same group. */ + lua_getiuservalue(L, narg, LUPB_ARENA_INDEX); + lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); + + lupb_cacheset(L, val.msg); +} + +/** + * lupb_msg_typechecksubmsg() + * + * Typechecks the given array, map, or msg against this upb_FieldDef. + */ +static void lupb_msg_typechecksubmsg(lua_State* L, int narg, int msgarg, + const upb_FieldDef* f) { + /* Typecheck this map's msgdef against this message field. */ + lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); + lua_getiuservalue(L, msgarg, LUPB_MSGDEF_INDEX); + lupb_MessageDef_pushsubmsgdef(L, f); + luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch"); + lua_pop(L, 2); +} + +/* lupb_Message Public API */ + +/** + * lupb_MessageDef_call + * + * Handles: + * new_msg = MessageClass() + * new_msg = MessageClass{foo = "bar", baz = 3, quux = {foo = 3}} + */ +int lupb_MessageDef_call(lua_State* L) { + int arg_count = lua_gettop(L); + lupb_msg_pushnew(L, 1); + + if (arg_count > 1) { + /* Set initial fields from table. */ + int msg = arg_count + 1; + lua_pushnil(L); + while (lua_next(L, 2) != 0) { + lua_pushvalue(L, -2); /* now stack is key, val, key */ + lua_insert(L, -3); /* now stack is key, key, val */ + lua_settable(L, msg); + } + } + + return 1; +} + +/** + * lupb_msg_index + * + * Handles: + * msg.foo + * msg["foo"] + * msg[field_descriptor] # (for extensions) (TODO) + */ +static int lupb_msg_index(lua_State* L) { + upb_Message* msg = lupb_msg_check(L, 1); + const upb_FieldDef* f = lupb_msg_checkfield(L, 1, 2); + + if (upb_FieldDef_IsRepeated(f) || upb_FieldDef_IsSubMessage(f)) { + /* Wrapped type; get or create wrapper. */ + upb_Arena* arena = upb_FieldDef_IsRepeated(f) ? lupb_Arenaget(L, 1) : NULL; + upb_MutableMessageValue val = upb_Message_Mutable(msg, f, arena); + if (!lupb_cacheget(L, val.msg)) { + lupb_Message_Newwrapper(L, 1, f, val); + } + } else { + /* Value type, just push value and return .*/ + upb_MessageValue val = upb_Message_GetFieldByDef(msg, f); + lupb_pushmsgval(L, 0, upb_FieldDef_CType(f), val); + } + + return 1; +} + +/** + * lupb_Message_Newindex() + * + * Handles: + * msg.foo = bar + * msg["foo"] = bar + * msg[field_descriptor] = bar # (for extensions) (TODO) + */ +static int lupb_Message_Newindex(lua_State* L) { + upb_Message* msg = lupb_msg_check(L, 1); + const upb_FieldDef* f = lupb_msg_checkfield(L, 1, 2); + upb_MessageValue msgval; + bool merge_arenas = true; + + if (upb_FieldDef_IsMap(f)) { + lupb_map* lmap = lupb_map_check(L, 3); + const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(f); + const upb_FieldDef* key_f = + upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_KeyFieldNumber); + const upb_FieldDef* val_f = + upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_ValueFieldNumber); + upb_CType key_type = upb_FieldDef_CType(key_f); + upb_CType value_type = upb_FieldDef_CType(val_f); + luaL_argcheck(L, lmap->key_type == key_type, 3, "key type mismatch"); + luaL_argcheck(L, lmap->value_type == value_type, 3, "value type mismatch"); + if (value_type == kUpb_CType_Message) { + lupb_msg_typechecksubmsg(L, 3, 1, val_f); + } + msgval.map_val = lmap->map; + } else if (upb_FieldDef_IsRepeated(f)) { + lupb_array* larr = lupb_array_check(L, 3); + upb_CType type = upb_FieldDef_CType(f); + luaL_argcheck(L, larr->type == type, 3, "array type mismatch"); + if (type == kUpb_CType_Message) { + lupb_msg_typechecksubmsg(L, 3, 1, f); + } + msgval.array_val = larr->arr; + } else if (upb_FieldDef_IsSubMessage(f)) { + upb_Message* msg = lupb_msg_check(L, 3); + lupb_msg_typechecksubmsg(L, 3, 1, f); + msgval.msg_val = msg; + } else { + msgval = lupb_tomsgval(L, upb_FieldDef_CType(f), 3, 1, LUPB_COPY); + merge_arenas = false; + } + + if (merge_arenas) { + lupb_Arena_Fuseobjs(L, 1, 3); + } + + upb_Message_SetFieldByDef(msg, f, msgval, lupb_Arenaget(L, 1)); + + /* Return the new value for chained assignments. */ + lua_pushvalue(L, 3); + return 1; +} + +/** + * lupb_msg_tostring() + * + * Handles: + * tostring(msg) + * print(msg) + * etc. + */ +static int lupb_msg_tostring(lua_State* L) { + upb_Message* msg = lupb_msg_check(L, 1); + const upb_MessageDef* m; + char buf[1024]; + size_t size; + + lua_getiuservalue(L, 1, LUPB_MSGDEF_INDEX); + m = lupb_MessageDef_check(L, -1); + + size = upb_TextEncode(msg, m, NULL, 0, buf, sizeof(buf)); + + if (size < sizeof(buf)) { + lua_pushlstring(L, buf, size); + } else { + char* ptr = malloc(size + 1); + upb_TextEncode(msg, m, NULL, 0, ptr, size + 1); + lua_pushlstring(L, ptr, size); + free(ptr); + } + + return 1; +} + +static const struct luaL_Reg lupb_msg_mm[] = { + {"__index", lupb_msg_index}, + {"__newindex", lupb_Message_Newindex}, + {"__tostring", lupb_msg_tostring}, + {NULL, NULL}}; + +/* lupb_Message toplevel + * **********************************************************/ + +static int lupb_getoptions(lua_State* L, int narg) { + int options = 0; + if (lua_gettop(L) >= narg) { + size_t len = lua_rawlen(L, narg); + for (size_t i = 1; i <= len; i++) { + lua_rawgeti(L, narg, i); + options |= lupb_checkuint32(L, -1); + lua_pop(L, 1); + } + } + return options; +} + +/** + * lupb_decode() + * + * Handles: + * msg = upb.decode(MessageClass, bin_string) + */ +static int lupb_decode(lua_State* L) { + size_t len; + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + const char* pb = lua_tolstring(L, 2, &len); + const upb_MiniTable* layout = upb_MessageDef_MiniTable(m); + upb_Message* msg = lupb_msg_pushnew(L, 1); + upb_Arena* arena = lupb_Arenaget(L, -1); + char* buf; + + /* Copy input data to arena, message will reference it. */ + buf = upb_Arena_Malloc(arena, len); + memcpy(buf, pb, len); + + upb_DecodeStatus status = upb_Decode(buf, len, msg, layout, NULL, + kUpb_DecodeOption_AliasString, arena); + + if (status != kUpb_DecodeStatus_Ok) { + lua_pushstring(L, "Error decoding protobuf."); + return lua_error(L); + } + + return 1; +} + +/** + * lupb_Encode() + * + * Handles: + * bin_string = upb.encode(msg) + */ +static int lupb_Encode(lua_State* L) { + const upb_Message* msg = lupb_msg_check(L, 1); + const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1); + const upb_MiniTable* layout = upb_MessageDef_MiniTable(m); + int options = lupb_getoptions(L, 2); + upb_Arena* arena = lupb_Arena_pushnew(L); + char* buf; + size_t size; + upb_EncodeStatus status = + upb_Encode(msg, (const void*)layout, options, arena, &buf, &size); + if (status != kUpb_EncodeStatus_Ok) { + lua_pushstring(L, "Error encoding protobuf."); + return lua_error(L); + } + + lua_pushlstring(L, buf, size); + + return 1; +} + +/** + * lupb_jsondecode() + * + * Handles: + * text_string = upb.json_decode(MessageClass, json_str, + * {upb.JSONDEC_IGNOREUNKNOWN}) + */ +static int lupb_jsondecode(lua_State* L) { + size_t len; + const upb_MessageDef* m = lupb_MessageDef_check(L, 1); + const char* json = lua_tolstring(L, 2, &len); + int options = lupb_getoptions(L, 3); + upb_Message* msg; + upb_Arena* arena; + upb_Status status; + + msg = lupb_msg_pushnew(L, 1); + arena = lupb_Arenaget(L, -1); + upb_Status_Clear(&status); + upb_JsonDecode(json, len, msg, m, NULL, options, arena, &status); + lupb_checkstatus(L, &status); + + return 1; +} + +/** + * lupb_jsonencode() + * + * Handles: + * text_string = upb.json_encode(msg, {upb.JSONENC_EMITDEFAULTS}) + */ +static int lupb_jsonencode(lua_State* L) { + upb_Message* msg = lupb_msg_check(L, 1); + const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1); + int options = lupb_getoptions(L, 2); + char buf[1024]; + size_t size; + upb_Status status; + + upb_Status_Clear(&status); + size = upb_JsonEncode(msg, m, NULL, options, buf, sizeof(buf), &status); + lupb_checkstatus(L, &status); + + if (size < sizeof(buf)) { + lua_pushlstring(L, buf, size); + } else { + char* ptr = malloc(size + 1); + upb_JsonEncode(msg, m, NULL, options, ptr, size + 1, &status); + lupb_checkstatus(L, &status); + lua_pushlstring(L, ptr, size); + free(ptr); + } + + return 1; +} + +/** + * lupb_textencode() + * + * Handles: + * text_string = upb.text_encode(msg, {upb.TXTENC_SINGLELINE}) + */ +static int lupb_textencode(lua_State* L) { + upb_Message* msg = lupb_msg_check(L, 1); + const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1); + int options = lupb_getoptions(L, 2); + char buf[1024]; + size_t size; + + size = upb_TextEncode(msg, m, NULL, options, buf, sizeof(buf)); + + if (size < sizeof(buf)) { + lua_pushlstring(L, buf, size); + } else { + char* ptr = malloc(size + 1); + upb_TextEncode(msg, m, NULL, options, ptr, size + 1); + lua_pushlstring(L, ptr, size); + free(ptr); + } + + return 1; +} + +static void lupb_setfieldi(lua_State* L, const char* field, int i) { + lua_pushinteger(L, i); + lua_setfield(L, -2, field); +} + +static const struct luaL_Reg lupb_msg_toplevel_m[] = { + {"Array", lupb_Array_New}, {"Map", lupb_Map_New}, + {"decode", lupb_decode}, {"encode", lupb_Encode}, + {"json_decode", lupb_jsondecode}, {"json_encode", lupb_jsonencode}, + {"text_encode", lupb_textencode}, {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_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_setfieldi(L, "TXTENC_SINGLELINE", UPB_TXTENC_SINGLELINE); + lupb_setfieldi(L, "TXTENC_SKIPUNKNOWN", UPB_TXTENC_SKIPUNKNOWN); + lupb_setfieldi(L, "TXTENC_NOSORT", UPB_TXTENC_NOSORT); + + lupb_setfieldi(L, "ENCODE_DETERMINISTIC", kUpb_EncodeOption_Deterministic); + lupb_setfieldi(L, "ENCODE_SKIPUNKNOWN", kUpb_EncodeOption_SkipUnknown); + + lupb_setfieldi(L, "JSONENC_EMITDEFAULTS", upb_JsonEncode_EmitDefaults); + lupb_setfieldi(L, "JSONENC_PROTONAMES", upb_JsonEncode_UseProtoNames); + + lupb_setfieldi(L, "JSONDEC_IGNOREUNKNOWN", upb_JsonDecode_IgnoreUnknown); + + lupb_cacheinit(L); +}
diff --git a/lua/test.proto b/lua/test.proto new file mode 100644 index 0000000..92bcd1c --- /dev/null +++ b/lua/test.proto
@@ -0,0 +1,98 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +import "google/protobuf/timestamp.proto"; + +package upb_lua_test; + +message MapTest { + map<string, double> map_string_double = 1; +} + +message PackedTest { + repeated bool bool_packed = 1 [packed = true]; + repeated int32 i32_packed = 2 [packed = true]; + repeated int64 i64_packed = 3 [packed = true]; + repeated fixed32 f32_packed = 4 [packed = true]; + repeated fixed64 f64_packed = 5 [packed = true]; +} + +message UnpackedTest { + repeated bool bool_packed = 1 [packed = false]; + repeated int32 i32_packed = 2 [packed = false]; + repeated int64 i64_packed = 3 [packed = false]; + repeated fixed32 f32_packed = 4 [packed = false]; + repeated fixed64 f64_packed = 5 [packed = false]; +} + +message TestLargeFieldNumber { + optional int32 i32 = 456214797; +} + +message TestTimestamp { + optional google.protobuf.Timestamp ts = 1; +} + +message HelloRequest { + optional uint32 id = 1; + optional uint32 random_name_a0 = 2; + optional uint32 random_name_a1 = 3; + optional uint32 random_name_a2 = 4; + optional uint32 random_name_a3 = 5; + optional uint32 random_name_a4 = 6; + optional uint32 random_name_a5 = 7; + optional uint32 random_name_a6 = 8; + optional uint32 random_name_a7 = 9; + optional uint32 random_name_a8 = 10; + optional uint32 random_name_a9 = 11; + optional uint32 random_name_b0 = 12; + optional uint32 random_name_b1 = 13; + optional uint32 random_name_b2 = 14; + optional uint32 random_name_b3 = 15; + optional uint32 random_name_b4 = 16; + optional uint32 random_name_b5 = 17; + optional uint32 random_name_b6 = 18; + optional uint32 random_name_b7 = 19; + optional uint32 random_name_b8 = 20; + optional uint32 random_name_b9 = 21; + optional uint32 random_name_c0 = 22; + optional uint32 random_name_c1 = 23; + optional uint32 random_name_c2 = 24; + optional uint32 random_name_c3 = 25; + optional uint32 random_name_c4 = 26; + optional uint32 random_name_c5 = 27; + optional uint32 random_name_c6 = 28; + optional uint32 random_name_c7 = 29; + optional uint32 random_name_c8 = 30; + optional uint32 random_name_c9 = 31; + optional string version = 32; +}
diff --git a/lua/test_upb.lua b/lua/test_upb.lua new file mode 100644 index 0000000..8ebf82b --- /dev/null +++ b/lua/test_upb.lua
@@ -0,0 +1,852 @@ +--[[-------------------------------------------------------------------------- + +Protocol Buffers - Google's data interchange format +Copyright 2023 Google LLC. All rights reserved. +https://developers.google.com/protocol-buffers/ + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--]]-------------------------------------------------------------------------- + +local upb = require "lupb" +local lunit = require "lunit" +local upb_test = require "lua.test_pb" +local test_messages_proto3 = require "google.protobuf.test_messages_proto3_pb" +local test_messages_proto2 = require "google.protobuf.test_messages_proto2_pb" +local descriptor = require "google.protobuf.descriptor_pb" +local empty = require "google.protobuf.empty_pb" + +if _VERSION >= 'Lua 5.2' then + _ENV = lunit.module("testupb", "seeall") +else + module("testupb", lunit.testcase, package.seeall) +end + +function iter_to_array(iter) + local arr = {} + for v in iter do + arr[#arr + 1] = v + end + return arr +end + +function test_def_readers() + local m = test_messages_proto3.TestAllTypesProto3 + assert_equal("TestAllTypesProto3", m:name()) + assert_equal("protobuf_test_messages.proto3.TestAllTypesProto3", m:full_name()) + + -- field + local f = m:field("optional_int32") + local f2 = m:field(1) + assert_equal(f, f2) + assert_equal(1, f:number()) + assert_equal("optional_int32", f:name()) + assert_equal(upb.LABEL_OPTIONAL, f:label()) + assert_equal(upb.DESCRIPTOR_TYPE_INT32, f:descriptor_type()) + assert_equal(upb.TYPE_INT32, f:type()) + assert_nil(f:containing_oneof()) + assert_equal(m, f:containing_type()) + assert_equal(0, f:default()) + local message_field_count = 0 + for field in m:fields() do + message_field_count = message_field_count + 1 + end + assert_equal(message_field_count, #m) + + local message_oneof_count = 0 + for oneof in m:oneofs() do + message_oneof_count = message_oneof_count + 1 + end + assert_equal(message_oneof_count, m:oneof_count()) + + -- oneof + local o = m:lookup_name("oneof_field") + assert_equal("oneof_field", o:name()) + assert_equal(m, o:containing_type()) + local oneof_field_count = 0 + for field in o:fields() do + oneof_field_count = oneof_field_count + 1 + end + assert_equal(oneof_field_count, #o) + + -- enum + local e = test_messages_proto3['TestAllTypesProto3.NestedEnum'] + assert_true(#e > 3 and #e < 10) + assert_equal(2, e:value("BAZ"):number()) +end + +function test_msg_map() + msg = test_messages_proto3.TestAllTypesProto3() + msg.map_int32_int32[5] = 10 + msg.map_int32_int32[6] = 12 + assert_equal(10, msg.map_int32_int32[5]) + assert_equal(12, msg.map_int32_int32[6]) + + -- Test overwrite. + msg.map_int32_int32[5] = 20 + assert_equal(20, msg.map_int32_int32[5]) + assert_equal(12, msg.map_int32_int32[6]) + msg.map_int32_int32[5] = 10 + + -- Test delete. + msg.map_int32_int32[5] = nil + assert_nil(msg.map_int32_int32[5]) + assert_equal(12, msg.map_int32_int32[6]) + msg.map_int32_int32[5] = 10 + + local serialized = upb.encode(msg) + assert_true(#serialized > 0) + local msg2 = upb.decode(test_messages_proto3.TestAllTypesProto3, serialized) + assert_equal(10, msg2.map_int32_int32[5]) + assert_equal(12, msg2.map_int32_int32[6]) +end + +function test_map_sorting() + function msg_with_int32_entries(start, expand) + local msg = test_messages_proto3.TestAllTypesProto3() + for i=start,start + 8 do + msg.map_int32_int32[i] = i * 2 + end + + if expand then + for i=start+20,200 do + msg.map_int32_int32[i] = i + end + for i=start+20,200 do + msg.map_int32_int32[i] = nil + end + end + return msg + end + + function msg_with_msg_entries(expand) + local msg = test_messages_proto3.TestAllTypesProto3() + -- 8! = 40320 possible orderings makes it overwhelmingly likely that two + -- random orderings will be different. + for i=1,8 do + local submsg = test_messages_proto3.TestAllTypesProto3.NestedMessage() + submsg.corecursive = msg_with_int32_entries(i, expand) + msg.map_string_nested_message[tostring(i)] = submsg + end + + expand = false + if expand then + for i=21,2000 do + local submsg = test_messages_proto3.TestAllTypesProto3.NestedMessage() + submsg.corecursive = msg_with_int32_entries(i, expand) + msg.map_string_nested_message[tostring(i)] = submsg + end + for i=21,2000 do + msg.map_string_nested_message[tostring(i)] = nil + end + end + return msg + end + + -- Create two messages with the same contents but (hopefully) different + -- map table orderings. + local msg = msg_with_msg_entries(false) + local msg2 = msg_with_msg_entries(true) + + local text1 = upb.text_encode(msg) + local text2 = upb.text_encode(msg2) + assert_equal(text1, text2) + + local binary1 = upb.encode(msg, {upb.ENCODE_DETERMINISTIC}) + local binary2 = upb.encode(msg2, {upb.ENCODE_DETERMINISTIC}) + assert_equal(binary1, binary2) + + -- Non-sorted map should compare different. + local text3 = upb.text_encode(msg, {upb.TXTENC_NOSORT}) + assert_not_equal(text1, text3) + + local binary3 = upb.encode(msg) + assert_not_equal(binary1, binary3) +end + +function test_utf8() + local proto2_msg = test_messages_proto2.TestAllTypesProto2() + proto2_msg.optional_string = "\xff" + local serialized = upb.encode(proto2_msg) + + -- Decoding invalid UTF-8 succeeds in proto2. + upb.decode(test_messages_proto2.TestAllTypesProto2, serialized) + + -- Decoding invalid UTF-8 fails in proto2. + assert_error_match("Error decoding protobuf", function() + upb.decode(test_messages_proto3.TestAllTypesProto3, serialized) + end) + + -- TODO: should proto3 accessors also check UTF-8 at set time? +end + +function test_string_double_map() + msg = upb_test.MapTest() + msg.map_string_double["one"] = 1.0 + msg.map_string_double["two point five"] = 2.5 + assert_equal(1, msg.map_string_double["one"]) + assert_equal(2.5, msg.map_string_double["two point five"]) + + -- Test overwrite. + msg.map_string_double["one"] = 2 + assert_equal(2, msg.map_string_double["one"]) + assert_equal(2.5, msg.map_string_double["two point five"]) + msg.map_string_double["one"] = 1.0 + + -- Test delete. + msg.map_string_double["one"] = nil + assert_nil(msg.map_string_double["one"]) + assert_equal(2.5, msg.map_string_double["two point five"]) + msg.map_string_double["one"] = 1 + + local serialized = upb.encode(msg) + assert_true(#serialized > 0) + local msg2 = upb.decode(upb_test.MapTest, serialized) + assert_equal(1, msg2.map_string_double["one"]) + assert_equal(2.5, msg2.map_string_double["two point five"]) +end + +function test_string_double_map() + local function fill_msg(msg) + msg.i32_packed[1] = 100 + msg.i32_packed[2] = 200 + msg.i32_packed[3] = 50000 + + msg.i64_packed[1] = 101 + msg.i64_packed[2] = 201 + msg.i64_packed[3] = 50001 + + msg.f32_packed[1] = 102 + msg.f32_packed[2] = 202 + msg.f32_packed[3] = 50002 + + msg.f64_packed[1] = 103 + msg.f64_packed[2] = 203 + msg.f64_packed[3] = 50003 + end + + local function check_msg(msg) + assert_equal(100, msg.i32_packed[1]) + assert_equal(200, msg.i32_packed[2]) + assert_equal(50000, msg.i32_packed[3]) + assert_equal(3, #msg.i32_packed) + + assert_equal(101, msg.i64_packed[1]) + assert_equal(201, msg.i64_packed[2]) + assert_equal(50001, msg.i64_packed[3]) + assert_equal(3, #msg.i64_packed) + + assert_equal(102, msg.f32_packed[1]) + assert_equal(202, msg.f32_packed[2]) + assert_equal(50002, msg.f32_packed[3]) + assert_equal(3, #msg.f32_packed) + + assert_equal(103, msg.f64_packed[1]) + assert_equal(203, msg.f64_packed[2]) + assert_equal(50003, msg.f64_packed[3]) + assert_equal(3, #msg.f64_packed) + end + + local msg = upb_test.PackedTest() + fill_msg(msg) + check_msg(msg) + + local serialized_packed = upb.encode(msg) + local msg2 = upb.decode(upb_test.PackedTest, serialized_packed) + local msg3 = upb.decode(upb_test.UnpackedTest, serialized_packed) + check_msg(msg2) + check_msg(msg3) + + serialized_unpacked = upb.encode(msg3) + local msg4 = upb.decode(upb_test.PackedTest, serialized_unpacked) + local msg5 = upb.decode(upb_test.PackedTest, serialized_unpacked) + check_msg(msg4) + check_msg(msg5) + +end + +function test_msg_string_map() + msg = test_messages_proto3.TestAllTypesProto3() + msg.map_string_string["foo"] = "bar" + msg.map_string_string["baz"] = "quux" + assert_nil(msg.map_string_string["abc"]) + assert_equal("bar", msg.map_string_string["foo"]) + assert_equal("quux", msg.map_string_string["baz"]) + + -- Test overwrite. + msg.map_string_string["foo"] = "123" + assert_equal("123", msg.map_string_string["foo"]) + assert_equal("quux", msg.map_string_string["baz"]) + msg.map_string_string["foo"] = "bar" + + -- Test delete + msg.map_string_string["foo"] = nil + assert_nil(msg.map_string_string["foo"]) + assert_equal("quux", msg.map_string_string["baz"]) + msg.map_string_string["foo"] = "bar" + + local serialized = upb.encode(msg) + assert_true(#serialized > 0) + local msg2 = upb.decode(test_messages_proto3.TestAllTypesProto3, serialized) + assert_equal("bar", msg2.map_string_string["foo"]) + assert_equal("quux", msg2.map_string_string["baz"]) +end + +function test_msg_array() + msg = test_messages_proto3.TestAllTypesProto3() + + assert_not_nil(msg.repeated_int32) + assert_equal(msg.repeated_int32, msg.repeated_int32) + assert_equal(0, #msg.repeated_int32) + + msg.repeated_int32[1] = 2 + assert_equal(1, #msg.repeated_int32); + assert_equal(2, msg.repeated_int32[1]); + + -- Can't assign a scalar; array is expected. + assert_error_match("lupb.array expected", function() msg.repeated_int32 = 5 end) + + -- Can't assign array of the wrong type. + local function assign_int64() + msg.repeated_int32 = upb.Array(upb.TYPE_INT64) + end + assert_error_match("array type mismatch", assign_int64) + + local arr = upb.Array(upb.TYPE_INT32) + arr[1] = 6 + assert_equal(1, #arr) + msg.repeated_int32 = arr + assert_equal(msg.repeated_int32, msg.repeated_int32) + assert_equal(arr, msg.repeated_int32) + assert_equal(1, #msg.repeated_int32) + assert_equal(6, msg.repeated_int32[1]) + + -- Can't assign other Lua types. + assert_error_match("array expected", function() msg.repeated_int32 = "abc" end) + assert_error_match("array expected", function() msg.repeated_int32 = true end) + assert_error_match("array expected", function() msg.repeated_int32 = false end) + assert_error_match("array expected", function() msg.repeated_int32 = nil end) + assert_error_match("array expected", function() msg.repeated_int32 = {} end) + assert_error_match("array expected", function() msg.repeated_int32 = print end) +end + +function test_array_append() + local arr = upb.Array(upb.TYPE_INT32) + for i=1,200000 do + arr[i] = i + end + for i=1,200000 do + assert_equal(i, arr[i]) + end +end + +function test_msg_submsg() + --msg = test_messages_proto3.TestAllTypesProto3() + msg = test_messages_proto3['TestAllTypesProto3']() + + assert_nil(msg.optional_nested_message) + + -- Can't assign message of the wrong type. + local function assign_int64() + msg.optional_nested_message = test_messages_proto3.TestAllTypesProto3() + end + assert_error_match("message type mismatch", assign_int64) + + local nested = test_messages_proto3['TestAllTypesProto3.NestedMessage']() + msg.optional_nested_message = nested + assert_equal(nested, msg.optional_nested_message) + + -- Can't assign other Lua types. + assert_error_match("msg expected", function() msg.optional_nested_message = "abc" end) + assert_error_match("msg expected", function() msg.optional_nested_message = true end) + assert_error_match("msg expected", function() msg.optional_nested_message = false end) + assert_error_match("msg expected", function() msg.optional_nested_message = nil end) + assert_error_match("msg expected", function() msg.optional_nested_message = {} end) + assert_error_match("msg expected", function() msg.optional_nested_message = print end) +end + +-- Lua 5.1 and 5.2 have slightly different semantics for how a finalizer +-- can be defined in Lua. +if _VERSION >= 'Lua 5.2' then + function defer(fn) + setmetatable({}, { __gc = fn }) + end +else + function defer(fn) + getmetatable(newproxy(true)).__gc = fn + end +end + +function test_finalizer() + -- Tests that we correctly handle a call into an already-finalized object. + -- Collectible objects are finalized in the opposite order of creation. + do + local t = {} + defer(function() + assert_error_match("called into dead object", function() + -- Generic def call. + t[1]:lookup_msg("abc") + end) + end) + t = { + upb.DefPool(), + } + end + collectgarbage() +end + +-- in-range of 64-bit types but not exactly representable as double +local bad64 = 2^68 - 1 + +local numeric_types = { + [upb.TYPE_UINT32] = { + valid_val = 2^32 - 1, + too_big = 2^32, + too_small = -1, + other_bad = 5.1 + }, + [upb.TYPE_UINT64] = { + valid_val = 2^63, + too_big = 2^64, + too_small = -1, + other_bad = bad64 + }, + [upb.TYPE_INT32] = { + valid_val = 2^31 - 1, + too_big = 2^31, + too_small = -2^31 - 1, + other_bad = 5.1 + }, + -- Enums don't exist at a language level in Lua, so we just represent enum + -- values as int32s. + [upb.TYPE_ENUM] = { + valid_val = 2^31 - 1, + too_big = 2^31, + too_small = -2^31 - 1, + other_bad = 5.1 + }, + [upb.TYPE_INT64] = { + valid_val = 2^62, + too_big = 2^63, + too_small = -2^64, + other_bad = bad64 + }, + [upb.TYPE_FLOAT] = { + valid_val = 340282306073709652508363335590014353408 + }, + [upb.TYPE_DOUBLE] = { + valid_val = 10^101 + }, +} + +function test_utf8() + local invalid_utf8 = "\xff" + local proto2_msg = test_messages_proto2.TestAllTypesProto2{ + optional_string = invalid_utf8, + } + + -- As proto2, invalid UTF-8 parses and serializes fine. + local serialized = upb.encode(proto2_msg) + local proto2_msg2 = upb.decode(test_messages_proto2.TestAllTypesProto2, serialized) + + -- Decoding as proto3 fails. + assert_error(function() + upb.decode(test_messages_proto3.TestAllTypesProto3, serialized) + end) +end + +function test_msg_primitives() + local msg = test_messages_proto3.TestAllTypesProto3{ + optional_int32 = 10, + optional_uint32 = 20, + optional_int64 = 30, + optional_uint64 = 40, + optional_double = 50, + optional_float = 60, + optional_sint32 = 70, + optional_sint64 = 80, + optional_fixed32 = 90, + optional_fixed64 = 100, + optional_sfixed32 = 110, + optional_sfixed64 = 120, + optional_bool = true, + optional_string = "abc", + optional_nested_message = test_messages_proto3['TestAllTypesProto3.NestedMessage']{a = 123}, + } + + -- Attempts to access non-existent fields fail. + assert_error_match("no such field", function() msg.no_such = 1 end) + + assert_equal(10, msg.optional_int32) + assert_equal(20, msg.optional_uint32) + assert_equal(30, msg.optional_int64) + assert_equal(40, msg.optional_uint64) + assert_equal(50, msg.optional_double) + assert_equal(60, msg.optional_float) + assert_equal(70, msg.optional_sint32) + assert_equal(80, msg.optional_sint64) + assert_equal(90, msg.optional_fixed32) + assert_equal(100, msg.optional_fixed64) + assert_equal(110, msg.optional_sfixed32) + assert_equal(120, msg.optional_sfixed64) + assert_equal(true, msg.optional_bool) + assert_equal("abc", msg.optional_string) + assert_equal(123, msg.optional_nested_message.a) +end + + +function test_string_array() + local function test_for_string_type(upb_type) + local array = upb.Array(upb_type) + assert_equal(0, #array) + + -- 0 is never a valid index in Lua. + assert_error_match("array index", function() return array[0] end) + -- Past the end of the array. + assert_error_match("array index", function() return array[1] end) + + array[1] = "foo" + assert_equal("foo", array[1]) + assert_equal(1, #array) + -- Past the end of the array. + assert_error_match("array index", function() return array[2] end) + + local array2 = upb.Array(upb_type) + assert_equal(0, #array2) + + array[2] = "bar" + assert_equal("foo", array[1]) + assert_equal("bar", array[2]) + assert_equal(2, #array) + -- Past the end of the array. + assert_error_match("array index", function() return array[3] end) + + -- Can't assign other Lua types. + assert_error_match("Expected string", function() array[3] = 123 end) + assert_error_match("Expected string", function() array[3] = true end) + assert_error_match("Expected string", function() array[3] = false end) + assert_error_match("Expected string", function() array[3] = nil end) + assert_error_match("Expected string", function() array[3] = {} end) + assert_error_match("Expected string", function() array[3] = print end) + assert_error_match("Expected string", function() array[3] = array end) + end + + test_for_string_type(upb.TYPE_STRING) + test_for_string_type(upb.TYPE_BYTES) +end + +function test_numeric_array() + local function test_for_numeric_type(upb_type) + local array = upb.Array(upb_type) + local vals = numeric_types[upb_type] + assert_equal(0, #array) + + -- 0 is never a valid index in Lua. + assert_error_match("array index", function() return array[0] end) + -- Past the end of the array. + assert_error_match("array index", function() return array[1] end) + + array[1] = vals.valid_val + assert_equal(vals.valid_val, array[1]) + assert_equal(1, #array) + assert_equal(vals.valid_val, array[1]) + -- Past the end of the array. + assert_error_match("array index", function() return array[2] end) + + array[2] = 10 + assert_equal(vals.valid_val, array[1]) + assert_equal(10, array[2]) + assert_equal(2, #array) + -- Past the end of the array. + assert_error_match("array index", function() return array[3] end) + + -- Values that are out of range. + local errmsg = "not an integer or out of range" + if vals.too_small then + assert_error_match(errmsg, function() array[3] = vals.too_small end) + end + if vals.too_big then + assert_error_match(errmsg, function() array[3] = vals.too_big end) + end + if vals.other_bad then + assert_error_match(errmsg, function() array[3] = vals.other_bad end) + end + + -- Can't assign other Lua types. + errmsg = "bad argument #3" + assert_error_match(errmsg, function() array[3] = "abc" end) + assert_error_match(errmsg, function() array[3] = true end) + assert_error_match(errmsg, function() array[3] = false end) + assert_error_match(errmsg, function() array[3] = nil end) + assert_error_match(errmsg, function() array[3] = {} end) + assert_error_match(errmsg, function() array[3] = print end) + assert_error_match(errmsg, function() array[3] = array end) + end + + for k in pairs(numeric_types) do + test_for_numeric_type(k) + end +end + +function test_numeric_map() + local function test_for_numeric_types(key_type, val_type) + local map = upb.Map(key_type, val_type) + local key_vals = numeric_types[key_type] + local val_vals = numeric_types[val_type] + + assert_equal(0, #map) + + -- Unset keys return nil + assert_nil(map[key_vals.valid_val]) + + map[key_vals.valid_val] = val_vals.valid_val + assert_equal(1, #map) + assert_equal(val_vals.valid_val, map[key_vals.valid_val]) + + i = 0 + for k, v in pairs(map) do + assert_equal(key_vals.valid_val, k) + assert_equal(val_vals.valid_val, v) + end + + -- Out of range key/val + local errmsg = "not an integer or out of range" + if key_vals.too_small then + assert_error_match(errmsg, function() map[key_vals.too_small] = 1 end) + end + if key_vals.too_big then + assert_error_match(errmsg, function() map[key_vals.too_big] = 1 end) + end + if key_vals.other_bad then + assert_error_match(errmsg, function() map[key_vals.other_bad] = 1 end) + end + + if val_vals.too_small then + assert_error_match(errmsg, function() map[1] = val_vals.too_small end) + end + if val_vals.too_big then + assert_error_match(errmsg, function() map[1] = val_vals.too_big end) + end + if val_vals.other_bad then + assert_error_match(errmsg, function() map[1] = val_vals.other_bad end) + end + end + + for k in pairs(numeric_types) do + for v in pairs(numeric_types) do + test_for_numeric_types(k, v) + end + end +end + +function test_unknown() + local bytes = string.rep("\x38\x00", 1000) + for i=1,1000 do + local msg = upb.decode(test_messages_proto3.TestAllTypesProto3, bytes) + end +end + +function test_foo() + local defpool = upb.DefPool() + local filename = "external/com_google_protobuf/src/google/protobuf/descriptor_proto-descriptor-set.proto.bin" + local alternate_filename = "src/google/protobuf/descriptor_proto-descriptor-set.proto.bin" + local file = io.open(filename, "rb") or io.open("bazel-bin/" .. filename, "rb") or io.open(alternate_filename, "rb") + assert_not_nil(file) + local descriptor = file:read("*a") + assert_true(#descriptor > 0) + defpool:add_set(descriptor) + local FileDescriptorSet = defpool:lookup_msg("google.protobuf.FileDescriptorSet") + assert_not_nil(FileDescriptorSet) + set = FileDescriptorSet() + assert_equal(#set.file, 0) + assert_error_match("lupb.array expected", function () set.file = 1 end) + + set = upb.decode(FileDescriptorSet, descriptor) + + -- Test that we can at least call this without crashing. + set_textformat = tostring(set) + + -- print(set_textformat) + assert_equal(#set.file, 1) + assert_equal(set.file[1].name, "google/protobuf/descriptor.proto") +end + +function test_descriptor() + local defpool = upb.DefPool() + local file_proto = descriptor.FileDescriptorProto { + name = "test.proto", + message_type = upb.Array(descriptor.DescriptorProto, { + descriptor.DescriptorProto{ + name = "ABC", + }, + }) + } + local file = defpool:add_file(upb.encode(file_proto)) + assert_equal(file:defpool(), defpool) +end + +function test_descriptor_error() + local defpool = upb.DefPool() + local file = descriptor.FileDescriptorProto() + file.name = "test.proto" + file.message_type[1] = descriptor.DescriptorProto{ + name = "ABC" + } + file.message_type[2] = descriptor.DescriptorProto{ + name = "BC." + } + assert_error(function () defpool:add_file(upb.encode(file)) end) + assert_nil(defpool:lookup_msg("ABC")) +end + +function test_duplicate_enumval() + local defpool = upb.DefPool() + local file_proto = descriptor.FileDescriptorProto { + name = "test.proto", + message_type = upb.Array(descriptor.DescriptorProto, { + descriptor.DescriptorProto{ + name = "ABC", + }, + }), + enum_type = upb.Array(descriptor.EnumDescriptorProto, { + descriptor.EnumDescriptorProto{ + name = "MyEnum", + value = upb.Array(descriptor.EnumValueDescriptorProto, { + descriptor.EnumValueDescriptorProto{ + name = "ABC", + number = 1, + } + }), + }, + }) + } + assert_error(function () defpool:add_file(upb.encode(file_proto)) end) +end + +function test_duplicate_filename_error() + local defpool = upb.DefPool() + local file = descriptor.FileDescriptorProto() + file.name = "test.proto" + defpool:add_file(upb.encode(file)) + -- Second add with the same filename fails. + assert_error(function () defpool:add_file(upb.encode(file)) end) +end + +function test_encode_skipunknown() + -- Test that upb.ENCODE_SKIPUNKNOWN does not encode unknown fields. + local msg = test_messages_proto3.TestAllTypesProto3{ + optional_int32 = 10, + optional_uint32 = 20, + optional_int64 = 30, + } + -- SKIPUNKNOWN here tests that it does *not* affect regular fields. + local serialized = upb.encode(msg, {upb.ENCODE_SKIPUNKNOWN}) + assert_true(#serialized > 0) + local empty_with_unknown = upb.decode(empty.Empty, serialized) + assert_true(#upb.encode(empty_with_unknown) > 0) + -- Verify that unknown fields are not serialized. + assert_true(#upb.encode(empty_with_unknown, {upb.ENCODE_SKIPUNKNOWN}) == 0) +end + +function test_json_emit_defaults() + local msg = test_messages_proto3.TestAllTypesProto3() + local json = upb.json_encode(msg, {upb.JSONENC_EMITDEFAULTS}) +end + +function test_json_locale() + local msg = test_messages_proto3.TestAllTypesProto3() + msg.optional_double = 1.1 + local original_locale = os.setlocale(nil) + os.setlocale("C") + local json = upb.json_encode(msg) + os.setlocale("de_DE.utf8") + assert_equal(json, upb.json_encode(msg)) + os.setlocale(original_locale) -- Restore. +end + +function test_encode_depth_limit() + local msg = test_messages_proto3.TestAllTypesProto3() + msg.recursive_message = msg + assert_error(function() upb.encode(msg) end) +end + +function test_large_field_number() + local msg = upb_test.TestLargeFieldNumber() + msg.i32 = 5 + local serialized = upb.encode(msg) + local msg2 = upb.decode(upb_test.TestLargeFieldNumber, serialized) + assert_equal(msg.i32, msg2.i32) +end + +function test_timestamp_minutes() + local msg = upb.json_decode(upb_test.TestTimestamp, '{"ts": "2000-01-01T00:00:00-06:59"}') + assert_equal(msg.ts.seconds, 946684800 + ((6 * 60) + 59) * 60) +end + +function test_gc() + local top = test_messages_proto3.TestAllTypesProto3() + local n = 100 + local m + + for i=1,n do + local inner = test_messages_proto3.TestAllTypesProto3() + m = inner + for j=1,n do + local tmp = m + m = test_messages_proto3.TestAllTypesProto3() + -- This will cause the arenas to fuse. But we stop referring to the child, + -- so the Lua object is eligible for collection (and therefore its original + -- arena can be collected too). Only the fusing will keep the C mem alivd. + m.recursive_message = tmp + + end + top.recursive_message = m + end + + collectgarbage() + + for i=1,n do + -- Verify we can touch all the messages again and without accessing freed + -- memory. + m = m.recursive_message + assert_not_nil(m) + end +end + +function test_b9440() + local m = upb_test.HelloRequest() + m.id = 8 + assert_equal(8, m.id) + m.version = "1" + assert_equal(8, m.id) +end + +local stats = lunit.main() + +if stats.failed > 0 or stats.errors > 0 then + error("One or more errors in test suite") +end
diff --git a/lua/upb.c b/lua/upb.c new file mode 100644 index 0000000..4500fb4 --- /dev/null +++ b/lua/upb.c
@@ -0,0 +1,261 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* + * require("lua") -- A Lua extension for upb. + * + * Exposes only the core library + * (sub-libraries are exposed in other extensions). + * + * 64-bit woes: Lua can only represent numbers of type lua_Number (which is + * double unless the user specifically overrides this). Doubles can represent + * the entire range of 64-bit integers, but lose precision once the integers are + * greater than 2^53. + * + * Lua 5.3 is adding support for integers, which will allow for 64-bit + * integers (which can be interpreted as signed or unsigned). + * + * LuaJIT supports 64-bit signed and unsigned boxed representations + * through its "cdata" mechanism, but this is not portable to regular Lua. + * + * Hopefully Lua 5.3 will come soon enough that we can either use Lua 5.3 + * integer support or LuaJIT 64-bit cdata for users that need the entire + * domain of [u]int64 values. + */ + +#include "lua/upb.h" + +#include <float.h> +#include <math.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "lauxlib.h" +#include "upb/message/message.h" + +/* Lua compatibility code *****************************************************/ + +/* Shims for upcoming Lua 5.3 functionality. */ +static bool lua_isinteger(lua_State* L, int argn) { + LUPB_UNUSED(L); + LUPB_UNUSED(argn); + return false; +} + +/* Utility functions **********************************************************/ + +void lupb_checkstatus(lua_State* L, upb_Status* s) { + if (!upb_Status_IsOk(s)) { + lua_pushstring(L, upb_Status_ErrorMessage(s)); + lua_error(L); + } +} + +/* Pushes a new userdata with the given metatable. */ +void* lupb_newuserdata(lua_State* L, size_t size, int n, const char* type) { +#if LUA_VERSION_NUM >= 504 + void* ret = lua_newuserdatauv(L, size, n); +#else + void* ret = lua_newuserdata(L, size); + lua_createtable(L, 0, n); + lua_setuservalue(L, -2); +#endif + + /* Set metatable. */ + luaL_getmetatable(L, type); + assert(!lua_isnil(L, -1)); /* Should have been created by luaopen_upb. */ + lua_setmetatable(L, -2); + + return ret; +} + +#if LUA_VERSION_NUM < 504 +int lua_setiuservalue(lua_State* L, int index, int n) { + lua_getuservalue(L, index); + lua_insert(L, -2); + lua_rawseti(L, -2, n); + lua_pop(L, 1); + return 1; +} + +int lua_getiuservalue(lua_State* L, int index, int n) { + lua_getuservalue(L, index); + lua_rawgeti(L, -1, n); + lua_replace(L, -2); + return 1; +} +#endif + +/* We use this function as the __index metamethod when a type has both methods + * and an __index metamethod. */ +int lupb_indexmm(lua_State* L) { + /* Look up in __index table (which is a closure param). */ + lua_pushvalue(L, 2); + lua_rawget(L, lua_upvalueindex(1)); + if (!lua_isnil(L, -1)) { + return 1; + } + + /* Not found, chain to user __index metamethod. */ + lua_pushvalue(L, lua_upvalueindex(2)); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_call(L, 2, 1); + return 1; +} + +void lupb_register_type(lua_State* L, const char* name, const luaL_Reg* m, + const luaL_Reg* mm) { + luaL_newmetatable(L, name); + + if (mm) { + lupb_setfuncs(L, mm); + } + + if (m) { + lua_createtable(L, 0, 0); /* __index table */ + lupb_setfuncs(L, m); + + /* Methods go in the mt's __index slot. If the user also specified an + * __index metamethod, use our custom lupb_indexmm() that can check both. */ + lua_getfield(L, -2, "__index"); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + } else { + lua_pushcclosure(L, &lupb_indexmm, 2); + } + lua_setfield(L, -2, "__index"); + } + + lua_pop(L, 1); /* The mt. */ +} + +/* Scalar type mapping ********************************************************/ + +/* Functions that convert scalar/primitive values (numbers, strings, bool) + * between Lua and C/upb. Handles type/range checking. */ + +bool lupb_checkbool(lua_State* L, int narg) { + if (!lua_isboolean(L, narg)) { + luaL_error(L, "must be true or false"); + } + return lua_toboolean(L, narg); +} + +/* Unlike luaL_checkstring(), this does not allow implicit conversion to + * string. */ +const char* lupb_checkstring(lua_State* L, int narg, size_t* len) { + if (lua_type(L, narg) != LUA_TSTRING) { + luaL_error(L, "Expected string"); + } + + return lua_tolstring(L, narg, len); +} + +/* Unlike luaL_checkinteger, these do not implicitly convert from string or + * round an existing double value. We allow floating-point input, but only if + * the actual value is integral. */ +#define INTCHECK(type, ctype, min, max) \ + ctype lupb_check##type(lua_State* L, int narg) { \ + double n; \ + if (lua_isinteger(L, narg)) { \ + return lua_tointeger(L, narg); \ + } \ + \ + /* Prevent implicit conversion from string. */ \ + luaL_checktype(L, narg, LUA_TNUMBER); \ + n = lua_tonumber(L, narg); \ + \ + /* Check this double has no fractional part and remains in bounds. \ + * Consider INT64_MIN and INT64_MAX: \ + * 1. INT64_MIN -(2^63) is a power of 2, so this converts to a double. \ + * 2. INT64_MAX (2^63 - 1) is not a power of 2, and conversion of \ + * out-of-range integer values to a double can lead to undefined behavior. \ + * On some compilers, this conversion can return 0, but it also can return \ + * the max value. To deal with this, we can first divide by 2 to prevent \ + * the overflow, multiply it back, and add 1 to find the true limit. */ \ + double i; \ + double max_value = (((double)max / 2) * 2) + 1; \ + if ((modf(n, &i) != 0.0) || n < min || n >= max_value) { \ + luaL_error(L, "number %f was not an integer or out of range for " #type, \ + n); \ + } \ + return (ctype)n; \ + } \ + void lupb_push##type(lua_State* L, ctype val) { \ + /* TODO: push integer for Lua >= 5.3, 64-bit cdata for LuaJIT. */ \ + /* This is lossy for some [u]int64 values, which isn't great, but */ \ + /* crashing when we encounter these values seems worse. */ \ + lua_pushnumber(L, val); \ + } + +INTCHECK(int64, int64_t, INT64_MIN, INT64_MAX) +INTCHECK(int32, int32_t, INT32_MIN, INT32_MAX) +INTCHECK(uint64, uint64_t, 0, UINT64_MAX) +INTCHECK(uint32, uint32_t, 0, UINT32_MAX) + +double lupb_checkdouble(lua_State* L, int narg) { + /* If we were being really hard-nosed here, we'd check whether the input was + * an integer that has no precise double representation. But doubles aren't + * generally expected to be exact like integers are, and worse this could + * cause data-dependent runtime errors: one run of the program could work fine + * because the integer calculations happened to be exactly representable in + * double, while the next could crash because of subtly different input. */ + + luaL_checktype(L, narg, LUA_TNUMBER); /* lua_tonumber() auto-converts. */ + return lua_tonumber(L, narg); +} + +float lupb_checkfloat(lua_State* L, int narg) { + /* We don't worry about checking whether the input can be exactly converted to + * float -- see above. */ + + luaL_checktype(L, narg, LUA_TNUMBER); /* lua_tonumber() auto-converts. */ + return lua_tonumber(L, narg); +} + +void lupb_pushdouble(lua_State* L, double d) { lua_pushnumber(L, d); } + +void lupb_pushfloat(lua_State* L, float d) { lua_pushnumber(L, d); } + +/* Library entry point ********************************************************/ + +int luaopen_lupb(lua_State* L) { +#if LUA_VERSION_NUM == 501 + const struct luaL_Reg funcs[] = {{NULL, NULL}}; + luaL_register(L, "upb_c", funcs); +#else + lua_createtable(L, 0, 8); +#endif + lupb_def_registertypes(L); + lupb_msg_registertypes(L); + return 1; /* Return package table. */ +}
diff --git a/lua/upb.h b/lua/upb.h new file mode 100644 index 0000000..46ec911 --- /dev/null +++ b/lua/upb.h
@@ -0,0 +1,135 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* + * Shared definitions for upb Lua modules. + */ + +#ifndef UPB_LUA_UPB_H_ +#define UPB_LUA_UPB_H_ + +#include "lauxlib.h" +#include "upb/message/message.h" +#include "upb/reflection/def.h" +#include "upb/reflection/message.h" + +/* Lua changes its API in incompatible ways in every minor release. + * This is some shim code to paper over the differences. */ + +#if LUA_VERSION_NUM == 501 +#define lua_rawlen lua_objlen +#define lua_setuservalue(L, idx) lua_setfenv(L, idx) +#define lua_getuservalue(L, idx) lua_getfenv(L, idx) +#define lupb_setfuncs(L, l) luaL_register(L, NULL, l) +#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 504 +#define lupb_setfuncs(L, l) luaL_setfuncs(L, l, 0) +#else +#error Only Lua 5.1-5.4 are supported +#endif + +/* Create a new userdata with the given type and |n| uservals, which are popped + * from the stack to initialize the userdata. */ +void* lupb_newuserdata(lua_State* L, size_t size, int n, const char* type); + +#if LUA_VERSION_NUM < 504 +/* Polyfills for this Lua 5.4 function. Pushes userval |n| for the userdata at + * |index|. */ +int lua_setiuservalue(lua_State* L, int index, int n); +int lua_getiuservalue(lua_State* L, int index, int n); +#endif + +/* Registers a type with the given name, methods, and metamethods. */ +void lupb_register_type(lua_State* L, const char* name, const luaL_Reg* m, + const luaL_Reg* mm); + +/* Checks the given upb_Status and throws a Lua error if it is not ok. */ +void lupb_checkstatus(lua_State* L, upb_Status* s); + +int luaopen_lupb(lua_State* L); + +/* C <-> Lua value conversions. ***********************************************/ + +/* Custom check/push functions. Unlike the Lua equivalents, they are pinned to + * specific C types (instead of lua_Number, etc), and do not allow any implicit + * conversion or data loss. */ +int64_t lupb_checkint64(lua_State* L, int narg); +int32_t lupb_checkint32(lua_State* L, int narg); +uint64_t lupb_checkuint64(lua_State* L, int narg); +uint32_t lupb_checkuint32(lua_State* L, int narg); +double lupb_checkdouble(lua_State* L, int narg); +float lupb_checkfloat(lua_State* L, int narg); +bool lupb_checkbool(lua_State* L, int narg); +const char* lupb_checkstring(lua_State* L, int narg, size_t* len); +const char* lupb_checkname(lua_State* L, int narg); + +void lupb_pushint64(lua_State* L, int64_t val); +void lupb_pushint32(lua_State* L, int32_t val); +void lupb_pushuint64(lua_State* L, uint64_t val); +void lupb_pushuint32(lua_State* L, uint32_t val); + +/** From def.c. ***************************************************************/ + +const upb_MessageDef* lupb_MessageDef_check(lua_State* L, int narg); +const upb_EnumDef* lupb_EnumDef_check(lua_State* L, int narg); +const upb_FieldDef* lupb_FieldDef_check(lua_State* L, int narg); +upb_DefPool* lupb_DefPool_check(lua_State* L, int narg); +void lupb_MessageDef_pushsubmsgdef(lua_State* L, const upb_FieldDef* f); + +void lupb_def_registertypes(lua_State* L); + +/** From msg.c. ***************************************************************/ + +void lupb_pushmsgval(lua_State* L, int container, upb_CType type, + upb_MessageValue val); +int lupb_MessageDef_call(lua_State* L); +upb_Arena* lupb_Arena_pushnew(lua_State* L); + +void lupb_msg_registertypes(lua_State* L); + +#define lupb_assert(L, predicate) \ + if (!(predicate)) \ + luaL_error(L, "internal error: %s, %s:%d ", #predicate, __FILE__, __LINE__); + +#define LUPB_UNUSED(var) (void)var + +#if defined(__GNUC__) || defined(__clang__) +#define LUPB_UNREACHABLE() \ + do { \ + assert(0); \ + __builtin_unreachable(); \ + } while (0) +#else +#define LUPB_UNREACHABLE() \ + do { \ + assert(0); \ + } while (0) +#endif + +#endif /* UPB_LUA_UPB_H_ */
diff --git a/lua/upb.lua b/lua/upb.lua new file mode 100644 index 0000000..3533309 --- /dev/null +++ b/lua/upb.lua
@@ -0,0 +1,58 @@ +--[[-------------------------------------------------------------------------- + + Copyright (c) 2009-2021, Google LLC + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Google LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--]]-------------------------------------------------------------------------- + +local upb = require("lupb") + +upb.generated_pool = upb.DefPool() + +local module_metatable = { + __index = function(t, k) + local package = t._filedef:package() + if package then + k = package .. "." .. k + end + local pool = upb.generated_pool + local def = pool:lookup_msg(k) or pool:lookup_enum(k) + local v = nil + if def and def:file():name() == t._filedef:name() then + v = def + t[k] = v + end + return v + end +} + +function upb._generated_module(desc_string) + local file = upb.generated_pool:add_file(desc_string) + local module = {_filedef = file} + setmetatable(module, module_metatable) + return module +end + +return upb
diff --git a/lua/upbc.cc b/lua/upbc.cc new file mode 100644 index 0000000..4a274f2 --- /dev/null +++ b/lua/upbc.cc
@@ -0,0 +1,139 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "google/protobuf/descriptor.pb.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "google/protobuf/compiler/code_generator.h" +#include "google/protobuf/compiler/plugin.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/io/printer.h" + +namespace protoc = ::google::protobuf::compiler; +namespace protobuf = ::google::protobuf; + +class LuaGenerator : public protoc::CodeGenerator { + bool Generate(const protobuf::FileDescriptor* file, + const std::string& parameter, protoc::GeneratorContext* context, + std::string* error) const override; +}; + +static std::string StripExtension(absl::string_view fname) { + size_t lastdot = fname.find_last_of('.'); + if (lastdot == std::string::npos) { + return std::string(fname); + } + return std::string(fname.substr(0, lastdot)); +} + +static std::string Filename(const protobuf::FileDescriptor* file) { + return StripExtension(file->name()) + "_pb.lua"; +} + +static std::string ModuleName(const protobuf::FileDescriptor* file) { + std::string ret = StripExtension(file->name()) + "_pb"; + return absl::StrReplaceAll(ret, {{"/", "."}}); +} + +static void PrintHexDigit(char digit, protobuf::io::Printer* printer) { + char text; + if (digit < 10) { + text = '0' + digit; + } else { + text = 'A' + (digit - 10); + } + printer->WriteRaw(&text, 1); +} + +static void PrintString(int max_cols, absl::string_view* str, + protobuf::io::Printer* printer) { + printer->Print("\'"); + while (max_cols > 0 && !str->empty()) { + char ch = (*str)[0]; + if (ch == '\\') { + printer->PrintRaw("\\\\"); + max_cols--; + } else if (ch == '\'') { + printer->PrintRaw("\\'"); + max_cols--; + } else if (isprint(ch)) { + printer->WriteRaw(&ch, 1); + max_cols--; + } else { + unsigned char byte = ch; + printer->PrintRaw("\\x"); + PrintHexDigit(byte >> 4, printer); + PrintHexDigit(byte & 15, printer); + max_cols -= 4; + } + str->remove_prefix(1); + } + printer->Print("\'"); +} + +bool LuaGenerator::Generate(const protobuf::FileDescriptor* file, + const std::string& /* parameter */, + protoc::GeneratorContext* context, + std::string* /* error */) const { + std::string filename = Filename(file); + protobuf::io::ZeroCopyOutputStream* out = context->Open(filename); + protobuf::io::Printer printer(out, '$'); + + for (int i = 0; i < file->dependency_count(); i++) { + const protobuf::FileDescriptor* dep = file->dependency(i); + printer.Print("require('$name$')\n", "name", ModuleName(dep)); + } + + printer.Print("local upb = require('upb')\n"); + + protobuf::FileDescriptorProto file_proto; + file->CopyTo(&file_proto); + std::string file_data; + file_proto.SerializeToString(&file_data); + + printer.Print("local descriptor = table.concat({\n"); + absl::string_view data(file_data); + while (!data.empty()) { + printer.Print(" "); + PrintString(72, &data, &printer); + printer.Print(",\n"); + } + printer.Print("})\n"); + + printer.Print("return upb._generated_module(descriptor)\n"); + + return true; +} + +int main(int argc, char** argv) { + LuaGenerator generator; + return google::protobuf::compiler::PluginMain(argc, argv, &generator); +}