├── .gitignore ├── .gitmodules ├── LICENSE ├── NOTICE ├── README.md ├── lua_protobuf ├── __init__.py └── generator.py ├── protoc-gen-lua └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | lua_protobuf/*.pyc 2 | 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wiki"] 2 | path = wiki 3 | url = git@github.com:indygreg/lua-protobuf.wiki.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | lua-protobuf 2 | Copyright (c) 2011, Gregory Szorc 3 | All rights reserved. 4 | 5 | Future contributors will be listed here. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua-protobuf provides a Lua interface to Google's [Protocol Buffers](http://code.google.com/apis/protocolbuffers/). 2 | 3 | # Producing Code 4 | 5 | lua-protobuf provides a plugin for the _protoc_ protocol buffer compiler (it ships with protocol buffers). This plugin tells _protoc_ to produce a set of C++ output files, which define the Lua interface to protocol buffers using the Lua C API. 6 | 7 | First, obtain a copy of lua-protobuf: 8 | 9 | $ git clone git@github.com:indygreg/lua-protobuf.git 10 | $ cd lua-protobuf 11 | 12 | Next, install lua-protobuf: 13 | 14 | $ python setup.py install 15 | 16 | Yes, lua-protobuf is written in Python (for now at least). 17 | 18 | Finally, launch protoc and tell it to produce Lua output: 19 | 20 | $ protoc -I/path/to/your/proto/files --lua_out=/output/path file1.proto file2.proto 21 | 22 | You simply need to add _--lua_out_ to the arguments to _protoc_ to get it to produce the Lua output files. 23 | 24 | Under the hood, _protoc_ is looking for the program _protoc-gen-lua_ somewhere in your $PATH. You can modify $PATH in lieux of installing the package, if you desire. 25 | 26 | ## Missing plugin_pb2 Python Module 27 | 28 | The protocol buffers Python installer does not install a file required by protoc-gen-lua at this time. The missing file is the Python interface to the compiler plugin *plugin.proto*. The protoc-gen-lua Python script may fail when importing the *google.protobuf.compiler.plugin_pb2* module. 29 | 30 | A thread on the protocol buffers mailing list [discusses the issue](http://groups.google.com/group/protobuf/browse_thread/thread/e58c33f20c27f4a9). 31 | 32 | At this time, the issue can be worked around by manually installing the missing file. 33 | 34 | Assuming you have a working protoc compiler on your system and have the existing Python protocol buffers package installed, from the protocol buffers source code directory, run the following: 35 | 36 | $ protoc -Isrc --python_out=/path/to/output/directory src/google/protobuf/compiler/plugin.proto 37 | 38 | The command should produce no output if successful. Additionally, the file */path/to/output/directory/google/protobuf/compiler/plugin_pb2.py* should have been created. 39 | 40 | Next, find the location of the installed protocol buffer Python package. If you have *locate*, try finding it via *`locate descriptor_pb2.py`*. A common location is something like */usr/lib/python2.6/site-packages/protobuf-2.3.0-py2.6.egg/google/protobuf/*. Navigate to this directory and: 41 | 42 | $ mkdir compiler 43 | $ touch compiler/__init__.py 44 | $ cp /path/to/plugin_pb2.py compiler/plugin_pb2.py 45 | 46 | In common English: 47 | 48 | 1. Create a new directory, from the install root, *google/protobuf/compiler* 49 | 2. Create an empty file, *\_\_init\_\_.py* in this directory. This tells Python that the directory contains Python modules. 50 | 3. Copy the *plugin_pb2.py* file produced by protoc to this new directory. 51 | 52 | Depending on the installed location of protocol buffers, these actions may require superuser or administrator privileges. 53 | 54 | # Compiling Produced Files 55 | 56 | You should be able to compile the produced .h and .cc files like you would for protocol buffer output files. If you have an existing Makefile, project, etc, just add the produced .h and .cc files to it. 57 | 58 | For linking, you'll need to include whatever library contains Lua. On *NIX toolchains, this typically corresponds to the linker flag _-llua_ or _-llua5.1_. 59 | 60 | The produced C++ code contains _extern "C" { }_ blocks around all code that utilizes Lua API function calls to avoid C++ name mangling. 61 | 62 | ## Windows 63 | 64 | Windows requires an identifier for symbols to be exported from shared libraries. If compiling the lua-protobuf output to a shared library, you'll need to use a preprocessor define: 65 | 66 | #define LUA_PROTOBUF_EXPORT __declspec(dllexport) 67 | -------------------------------------------------------------------------------- /lua_protobuf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indygreg/lua-protobuf/753db70df5fd224a1a4b10fc63215461f329c67e/lua_protobuf/__init__.py -------------------------------------------------------------------------------- /lua_protobuf/generator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Gregory Szorc 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from google.protobuf.descriptor import FieldDescriptor 16 | import re 17 | 18 | RE_BARE_BEGIN_BRACKET = re.compile(r'^\s*{\s*$') 19 | RE_BEGIN_BRACKET = re.compile(r'{\s*$') 20 | RE_END_BRACKET = re.compile(r'^\s*};?\s*$') 21 | 22 | FIELD_LABEL_MAP = { 23 | FieldDescriptor.LABEL_OPTIONAL: 'optional', 24 | FieldDescriptor.LABEL_REQUIRED: 'required', 25 | FieldDescriptor.LABEL_REPEATED: 'repeated' 26 | } 27 | 28 | FIELD_TYPE_MAP = { 29 | FieldDescriptor.TYPE_DOUBLE: 'double', 30 | FieldDescriptor.TYPE_FLOAT: 'float', 31 | FieldDescriptor.TYPE_INT64: 'int64', 32 | FieldDescriptor.TYPE_UINT64: 'uint64', 33 | FieldDescriptor.TYPE_INT32: 'int32', 34 | FieldDescriptor.TYPE_FIXED64: 'fixed64', 35 | FieldDescriptor.TYPE_FIXED32: 'fixed32', 36 | FieldDescriptor.TYPE_BOOL: 'bool', 37 | FieldDescriptor.TYPE_STRING: 'string', 38 | FieldDescriptor.TYPE_GROUP: 'group', 39 | FieldDescriptor.TYPE_MESSAGE: 'message', 40 | FieldDescriptor.TYPE_BYTES: 'bytes', 41 | FieldDescriptor.TYPE_UINT32: 'uint32', 42 | FieldDescriptor.TYPE_ENUM: 'enum', 43 | FieldDescriptor.TYPE_SFIXED32: 'sfixed32', 44 | FieldDescriptor.TYPE_SFIXED64: 'sfixed64', 45 | FieldDescriptor.TYPE_SINT32: 'sint32', 46 | FieldDescriptor.TYPE_SINT64: 'sint64', 47 | } 48 | 49 | def lua_protobuf_header(): 50 | '''Returns common header included by all produced files''' 51 | return ''' 52 | #ifndef LUA_PROTOBUF_H 53 | #define LUA_PROTOBUF_H 54 | 55 | #include 56 | 57 | #ifdef __cplusplus 58 | extern "C" { 59 | #endif 60 | 61 | #include 62 | 63 | #ifdef WINDOWS 64 | #define LUA_PROTOBUF_EXPORT __declspec(dllexport) 65 | #else 66 | #define LUA_PROTOBUF_EXPORT 67 | #endif 68 | 69 | // type for callback function that is executed before Lua performs garbage 70 | // collection on a message instance. 71 | // if called function returns 1, Lua will free the memory backing the object 72 | // if returns 0, Lua will not free the memory 73 | typedef int (*lua_protobuf_gc_callback)(::google::protobuf::Message *msg, void *userdata); 74 | 75 | // __index and __newindex functions for enum tables 76 | LUA_PROTOBUF_EXPORT int lua_protobuf_enum_index(lua_State *L); 77 | LUA_PROTOBUF_EXPORT int lua_protobuf_enum_newindex(lua_State *L); 78 | 79 | // GC callback function that always returns true 80 | LUA_PROTOBUF_EXPORT int lua_protobuf_gc_always_free(::google::protobuf::Message *msg, void *userdata); 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif 87 | 88 | ''' 89 | 90 | def lua_protobuf_source(): 91 | '''Returns source for common code''' 92 | return ''' 93 | 94 | #include "lua-protobuf.h" 95 | 96 | #ifdef __cplusplus 97 | extern "C" { 98 | #endif 99 | 100 | #include 101 | 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | 106 | int lua_protobuf_enum_index(lua_State *L) 107 | { 108 | return luaL_error(L, "attempting to access undefined enumeration value: %s", lua_tostring(L, 2)); 109 | } 110 | 111 | int lua_protobuf_enum_newindex(lua_State *L) 112 | { 113 | return luaL_error(L, "cannot modify enumeration tables"); 114 | } 115 | 116 | int lua_protobuf_gc_always_free(::google::protobuf::Message *msg, void *ud) 117 | { 118 | return 1; 119 | } 120 | 121 | ''' 122 | 123 | def c_header_header(filename, package): 124 | return [ 125 | '// Generated by the lua-protobuf compiler.', 126 | '// You shouldn\'t be editing this file manually', 127 | '//', 128 | '// source proto file: %s' % filename, 129 | '', 130 | '#ifndef LUA_PROTOBUF_%s_H' % package.replace('.', '_'), 131 | '#define LUA_PROTOBUF_%s_H' % package.replace('.', '_'), 132 | '', 133 | '#include "lua-protobuf.h"', 134 | '#include <%s.pb.h>' % package.replace('.', '/'), 135 | '', 136 | '#ifdef __cplusplus', 137 | 'extern "C" {', 138 | '#endif', 139 | '', 140 | '#include ', 141 | '', 142 | '// register all messages in this package to a Lua state', 143 | 'LUA_PROTOBUF_EXPORT int %sopen(lua_State *L);' % package_function_prefix(package), 144 | '', 145 | ] 146 | 147 | def source_header(filename, package): 148 | '''Returns lines that begin a source file''' 149 | return [ 150 | '// Generated by the lua-protobuf compiler', 151 | '// You shouldn\'t edit this file manually', 152 | '//', 153 | '// source proto file: %s' % filename, 154 | '', 155 | '#include "%s.pb-lua.h"' % package.replace('.', '/'), 156 | '', 157 | '#ifdef __cplusplus', 158 | 'extern "C" { // make sure functions treated with C naming', 159 | '#endif', 160 | '', 161 | '#include ', 162 | '', 163 | '#ifdef __cplusplus', 164 | '}', 165 | '#endif', 166 | '', 167 | '#include ', 168 | '', 169 | '// this represents Lua udata for a protocol buffer message', 170 | '// we record where a message came from so we can GC it properly', 171 | 'typedef struct msg_udata { // confuse over-simplified pretty-printer', 172 | ' ::google::protobuf::Message * msg;', 173 | ' bool lua_owns;', 174 | ' lua_protobuf_gc_callback gc_callback;', 175 | ' void * callback_data;', 176 | '} msg_udata;', 177 | '', 178 | ] 179 | 180 | def package_function_prefix(package): 181 | return 'lua_protobuf_%s_' % package.replace('.', '_') 182 | 183 | def message_function_prefix(package, message): 184 | return '%s%s_' % (package_function_prefix(package), message) 185 | 186 | def message_open_function_name(package, message): 187 | '''Returns function name that registers the Lua library for a message type''' 188 | 189 | return '%sopen' % message_function_prefix(package, message) 190 | 191 | def cpp_class(package, message = None): 192 | '''Returns the fully qualified class name for a message type''' 193 | 194 | if not message: 195 | return package.replace('.', '::') 196 | 197 | return '::%s::%s' % ( package.replace('.', '::'), message ) 198 | 199 | def field_function_name(package, message, prefix, field): 200 | '''Obtain the function name of a field accessor/mutator function''' 201 | 202 | return '%s%s_%s' % ( message_function_prefix(package, message), prefix, field ) 203 | 204 | def field_function_start(package, message, prefix, field): 205 | '''Obtain the start of function for a field accessor function''' 206 | return [ 207 | 'int %s(lua_State *L)' % field_function_name(package, message, prefix, field), 208 | '{', 209 | ] 210 | 211 | def lua_libname(package, message): 212 | '''Returns the Lua library name for a specific message''' 213 | 214 | return 'protobuf.%s.%s' % (package, message) 215 | 216 | def metatable(package, message): 217 | '''Returns Lua metatable for protocol buffer message type''' 218 | return 'protobuf_.%s.%s' % (package, message) 219 | 220 | def obtain_message_from_udata(package, message=None, index=1, varname='m'): 221 | '''Statement that obtains a message from userdata''' 222 | 223 | c = cpp_class(package, message) 224 | return [ 225 | 'msg_udata * %sud = (msg_udata *)%s;' % ( varname, check_udata(package, message, index) ), 226 | '%s *%s = (%s *)%sud->msg;' % ( c, varname, c, varname ), 227 | ] 228 | 229 | def check_udata(package, message, index=1): 230 | '''Validates a udata is instance of protocol buffer message 231 | 232 | By default, it validates udata at top of the stack 233 | ''' 234 | 235 | return 'luaL_checkudata(L, %d, "%s")' % ( index, metatable(package, message) ) 236 | 237 | def has_body(package, message, field): 238 | '''Returns the function body for a has_ function''' 239 | 240 | lines = [] 241 | lines.extend(obtain_message_from_udata(package, message)) 242 | lines.append('lua_pushboolean(L, m->has_%s());' % field) 243 | lines.append('return 1;') 244 | 245 | return lines 246 | 247 | def clear_body(package, message, field): 248 | '''Returns the function body for a clear_ function''' 249 | lines = [] 250 | lines.extend(obtain_message_from_udata(package, message)) 251 | lines.append('m->clear_%s();' % field) 252 | lines.append('return 0;') 253 | 254 | return lines 255 | 256 | def size_body(package, message, field): 257 | '''Returns the function body for a size_ function''' 258 | lines = [] 259 | lines.extend(obtain_message_from_udata(package, message)) 260 | lines.append('int size = m->%s_size();' % field) 261 | lines.append('lua_pushinteger(L, size);') 262 | lines.append('return 1;') 263 | 264 | return lines 265 | 266 | def add_body(package, message, field, type_name): 267 | '''Returns the function body for the add_ function for repeated embedded messages''' 268 | lines = [] 269 | lines.extend(obtain_message_from_udata(package, message)) 270 | lines.extend([ 271 | '%s *msg_new = m->add_%s();' % ( cpp_class(type_name), field ), 272 | 273 | # since the message is allocated out of the containing message, Lua 274 | # does not need to do GC 275 | 'lua_protobuf%s_pushreference(L, msg_new, NULL, NULL);' % type_name.replace('.', '_'), 276 | 'return 1;', 277 | ]) 278 | 279 | return lines 280 | 281 | def field_get(package, message, field_descriptor): 282 | '''Returns function definition for a get_ function''' 283 | 284 | name = field_descriptor.name 285 | type = field_descriptor.type 286 | type_name = field_descriptor.type_name 287 | label = field_descriptor.label 288 | repeated = label == FieldDescriptor.LABEL_REPEATED 289 | 290 | lines = [] 291 | lines.extend(field_function_start(package, message, 'get', name)) 292 | lines.extend(obtain_message_from_udata(package, message)) 293 | 294 | # the logic is significantly different depending on if the field is 295 | # singular or repeated. 296 | # for repeated, we have an argument which points to the numeric index to 297 | # retrieve. in true Lua convention, we index starting from 1, which is 298 | # different from protocol buffers, which indexes from 0 299 | 300 | if repeated: 301 | lines.extend([ 302 | 'if (lua_gettop(L) != 2) {', 303 | 'return luaL_error(L, "missing required numeric argument");', 304 | '}', 305 | 'lua_Integer index = luaL_checkinteger(L, 2);', 306 | 'if (index < 1 || index > m->%s_size()) {' % name, 307 | # TODO is returning nil the more Lua way? 308 | 'return luaL_error(L, "index must be between 1 and current size: %%d", m->%s_size());' % name, 309 | '}', 310 | ]) 311 | 312 | # TODO float and double types are not equivalent. don't treat them as such 313 | # TODO figure out how to support 64 bit integers properly 314 | 315 | if repeated: 316 | if type in [ FieldDescriptor.TYPE_STRING, FieldDescriptor.TYPE_BYTES ]: 317 | lines.extend([ 318 | 'string s = m->%s(index - 1);' % name, 319 | 'lua_pushlstring(L, s.c_str(), s.size());', 320 | ]) 321 | elif type == FieldDescriptor.TYPE_BOOL: 322 | lines.append('lua_pushboolean(L, m->%s(index-1));' % name) 323 | 324 | elif type in [FieldDescriptor.TYPE_INT32, FieldDescriptor.TYPE_UINT32, 325 | FieldDescriptor.TYPE_FIXED32, FieldDescriptor.TYPE_SFIXED32, FieldDescriptor.TYPE_SINT32]: 326 | 327 | lines.append('lua_pushinteger(L, m->%s(index-1));' % name) 328 | 329 | elif type in [ FieldDescriptor.TYPE_INT64, FieldDescriptor.TYPE_UINT64, 330 | FieldDescriptor.TYPE_FIXED64, FieldDescriptor.TYPE_SFIXED64, FieldDescriptor.TYPE_SINT64]: 331 | lines.append('lua_pushinteger(L, m->%s(index-1));' % name) 332 | 333 | elif type == FieldDescriptor.TYPE_FLOAT or type == FieldDescriptor.TYPE_DOUBLE: 334 | lines.append('lua_pushnumber(L, m->%s(index-1));' % name) 335 | 336 | elif type == FieldDescriptor.TYPE_ENUM: 337 | lines.append('lua_pushnumber(L, m->%s(index-1));' % name) 338 | 339 | elif type == FieldDescriptor.TYPE_MESSAGE: 340 | lines.extend([ 341 | '%s * got_msg = m->mutable_%s(index-1);' % ( type_name.replace('.', '::'), name ), 342 | 'lua_protobuf%s_pushreference(L, got_msg, NULL, NULL);' % type_name.replace('.', '_'), 343 | ]) 344 | 345 | else: 346 | lines.append('return luaL_error(L, "lua-protobuf does not support this field type");') 347 | else: 348 | # for scalar fields, we push nil if the value is not defined 349 | # this is the Lua way 350 | if type == FieldDescriptor.TYPE_STRING or type == FieldDescriptor.TYPE_BYTES: 351 | lines.append('string s = m->%s();' % name) 352 | lines.append('m->has_%s() ? lua_pushlstring(L, s.c_str(), s.size()) : lua_pushnil(L);' % name) 353 | 354 | elif type == FieldDescriptor.TYPE_BOOL: 355 | lines.append('m->has_%s() ? lua_pushboolean(L, m->%s()) : lua_pushnil(L);' % ( name, name )) 356 | 357 | elif type in [FieldDescriptor.TYPE_INT32, FieldDescriptor.TYPE_UINT32, 358 | FieldDescriptor.TYPE_FIXED32, FieldDescriptor.TYPE_SFIXED32, FieldDescriptor.TYPE_SINT32]: 359 | lines.append('m->has_%s() ? lua_pushinteger(L, m->%s()) : lua_pushnil(L);' % ( name, name )) 360 | 361 | elif type in [ FieldDescriptor.TYPE_INT64, FieldDescriptor.TYPE_UINT64, 362 | FieldDescriptor.TYPE_FIXED64, FieldDescriptor.TYPE_SFIXED64, FieldDescriptor.TYPE_SINT64]: 363 | lines.append('m->has_%s() ? lua_pushinteger(L, m->%s()) : lua_pushnil(L);' % ( name, name )) 364 | 365 | elif type == FieldDescriptor.TYPE_FLOAT or type == FieldDescriptor.TYPE_DOUBLE: 366 | lines.append('m->has_%s() ? lua_pushnumber(L, m->%s()) : lua_pushnil(L);' % ( name, name )) 367 | 368 | elif type == FieldDescriptor.TYPE_ENUM: 369 | lines.append('m->has_%s() ? lua_pushinteger(L, m->%s()) : lua_pushnil(L);' % ( name, name )) 370 | 371 | elif type == FieldDescriptor.TYPE_MESSAGE: 372 | lines.extend([ 373 | 'if (!m->has_%s()) {' % name, 374 | 'lua_pushnil(L);', 375 | '}', 376 | 377 | # we push the message as userdata 378 | # since the message is allocated out of the parent message, we 379 | # don't need to do garbage collection 380 | '%s * got_msg = m->mutable_%s();' % ( type_name.replace('.', '::'), name ), 381 | 'lua_protobuf%s_pushreference(L, got_msg, NULL, NULL);' % type_name.replace('.', '_'), 382 | ]) 383 | 384 | else: 385 | # not supported yet :( 386 | lines.append('return luaL_error(L, "lua-protobuf does not support this field type");') 387 | 388 | lines.append('return 1;') 389 | lines.append('}\n') 390 | 391 | return lines 392 | 393 | def field_set_assignment(field, args): 394 | return [ 395 | 'if (index == current_size + 1) {', 396 | 'm->add_%s(%s);' % ( field, args ), 397 | '}', 398 | 'else {', 399 | 'm->set_%s(index-1, %s);' % ( field, args ), 400 | '}', 401 | ] 402 | 403 | def field_set(package, message, field_descriptor): 404 | '''Returns function definition for a set_ function''' 405 | 406 | name = field_descriptor.name 407 | type = field_descriptor.type 408 | type_name = field_descriptor.type_name 409 | label = field_descriptor.label 410 | repeated = label == FieldDescriptor.LABEL_REPEATED 411 | 412 | lines = [] 413 | lines.extend(field_function_start(package, message, 'set', name)) 414 | lines.extend(obtain_message_from_udata(package, message, 1)) 415 | 416 | # we do things differently depending on if this is a singular or repeated field 417 | # for singular fields, the new value is the first argument 418 | # for repeated fields, the index is arg1 and the value is arg2 419 | if repeated: 420 | lines.extend([ 421 | 'if (lua_gettop(L) != 3) {', 422 | ' return luaL_error(L, "required 2 arguments not passed to function");', 423 | '}', 424 | 'lua_Integer index = luaL_checkinteger(L, 2);', 425 | 'int current_size = m->%s_size();' % name, 426 | 'if (index < 1 || index > current_size + 1) {', 427 | 'return luaL_error(L, "index must be between 1 and %d", current_size + 1);', 428 | '}', 429 | 430 | # we don't support the automagic nil clears value... yet 431 | 'if (lua_isnil(L, 3)) {', 432 | 'return luaL_error(L, "cannot assign nil to repeated fields (yet)");', 433 | '}', 434 | ]) 435 | 436 | # TODO proper 64 bit handling 437 | 438 | # now move on to the assignment 439 | if repeated: 440 | if type in [ FieldDescriptor.TYPE_STRING, FieldDescriptor.TYPE_BYTES ]: 441 | lines.extend([ 442 | 'size_t length = 0;', 443 | 'const char *s = luaL_checklstring(L, 3, &length);', 444 | ]) 445 | lines.extend(field_set_assignment(name, 's, length')) 446 | 447 | elif type == FieldDescriptor.TYPE_BOOL: 448 | lines.append('bool b = lua_toboolean(L, 3);') 449 | lines.extend(field_set_assignment(name, 'b')) 450 | 451 | elif type in [ FieldDescriptor.TYPE_DOUBLE, FieldDescriptor.TYPE_FLOAT ]: 452 | lines.append('double d = lua_tonumber(L, 3);') 453 | lines.extend(field_set_assignment(name, 'd')) 454 | 455 | elif type in [ FieldDescriptor.TYPE_INT32, FieldDescriptor.TYPE_FIXED32, 456 | FieldDescriptor.TYPE_UINT32, FieldDescriptor.TYPE_SFIXED32, FieldDescriptor.TYPE_SINT32 ]: 457 | 458 | lines.append('lua_Integer i = lua_tointeger(L, 3);') 459 | lines.extend(field_set_assignment(name, 'i')) 460 | 461 | elif type in [ FieldDescriptor.TYPE_INT64, FieldDescriptor.TYPE_UINT64, 462 | FieldDescriptor.TYPE_FIXED64, FieldDescriptor.TYPE_SFIXED64, FieldDescriptor.TYPE_SINT64]: 463 | 464 | lines.append('lua_Integer i = lua_tointeger(L, 3);') 465 | lines.extend(field_set_assignment(name, 'i')) 466 | 467 | elif type == FieldDescriptor.TYPE_ENUM: 468 | lines.append('lua_Integer i = lua_tointeger(L, 3);') 469 | lines.extend(field_set_assignment(name, '(%s)i' % type_name.replace('.', '::'))) 470 | 471 | elif type == FieldDescriptor.TYPE_MESSAGE: 472 | lines.append('return luaL_error(L, "to manipulate embedded messages, fetch the embedded message and modify it");') 473 | 474 | else: 475 | lines.append('return luaL_error(L, "field type not yet supported");') 476 | 477 | lines.append('return 0;') 478 | else: 479 | # if they call set() with nil, we interpret as a clear 480 | # this is the Lua way, after all 481 | lines.extend([ 482 | 'if (lua_isnil(L, 2)) {', 483 | 'm->clear_%s();' % name, 484 | 'return 0;', 485 | '}', 486 | '', 487 | ]) 488 | 489 | if type in [ FieldDescriptor.TYPE_STRING, FieldDescriptor.TYPE_BYTES ]: 490 | lines.extend([ 491 | 'if (!lua_isstring(L, 2)) return luaL_error(L, "passed value is not a string");', 492 | 'size_t len;', 493 | 'const char *s = lua_tolstring(L, 2, &len);', 494 | 'if (!s) {', 495 | 'luaL_error(L, "could not obtain string on stack. weird");', 496 | '}', 497 | 'm->set_%s(s, len);' % name, 498 | 'return 0;', 499 | ]) 500 | 501 | elif type in [ FieldDescriptor.TYPE_DOUBLE, FieldDescriptor.TYPE_FLOAT ]: 502 | lines.extend([ 503 | 'if (!lua_isnumber(L, 2)) return luaL_error(L, "passed value cannot be converted to a number");', 504 | 'lua_Number n = lua_tonumber(L, 2);', 505 | 'm->set_%s(n);' % name, 506 | 'return 0;', 507 | ]) 508 | 509 | elif type in [ FieldDescriptor.TYPE_INT32, FieldDescriptor.TYPE_FIXED32, 510 | FieldDescriptor.TYPE_UINT32, FieldDescriptor.TYPE_SFIXED32, FieldDescriptor.TYPE_SINT32 ]: 511 | 512 | lines.extend([ 513 | 'lua_Integer v = luaL_checkinteger(L, 2);', 514 | 'm->set_%s(v);' % name, 515 | 'return 0;', 516 | ]) 517 | 518 | elif type in [ FieldDescriptor.TYPE_INT64, FieldDescriptor.TYPE_UINT64, 519 | FieldDescriptor.TYPE_FIXED64, FieldDescriptor.TYPE_SFIXED64, FieldDescriptor.TYPE_SINT64]: 520 | 521 | lines.extend([ 522 | 'lua_Integer i = luaL_checkinteger(L, 2);', 523 | 'm->set_%s(i);' % name, 524 | 'return 0;', 525 | ]) 526 | 527 | elif type == FieldDescriptor.TYPE_BOOL: 528 | lines.extend([ 529 | 'bool b = lua_toboolean(L, 2);', 530 | 'm->set_%s(b);' % name, 531 | 'return 0;', 532 | ]) 533 | 534 | elif type == FieldDescriptor.TYPE_ENUM: 535 | lines.extend([ 536 | 'lua_Integer i = luaL_checkinteger(L, 2);', 537 | 'm->set_%s((%s)i);' % ( name, type_name.replace('.', '::') ), 538 | 'return 0;', 539 | ]) 540 | 541 | elif type == FieldDescriptor.TYPE_MESSAGE: 542 | lines.append('return luaL_error(L, "to manipulate embedded messages, obtain the embedded message and manipulate it");') 543 | 544 | else: 545 | lines.append('return luaL_error(L, "field type is not yet supported");') 546 | 547 | lines.append('}\n') 548 | 549 | return lines 550 | 551 | def new_message(package, message): 552 | '''Returns function definition for creating a new protocol buffer message''' 553 | 554 | lines = [] 555 | 556 | lines.append('int %snew(lua_State *L)' % message_function_prefix(package, message)) 557 | lines.append('{') 558 | 559 | c = cpp_class(package, message) 560 | lines.append('msg_udata * ud = (msg_udata *)lua_newuserdata(L, sizeof(msg_udata));') 561 | 562 | lines.append('ud->lua_owns = true;') 563 | lines.append('ud->msg = new %s();' % c) 564 | lines.append('ud->gc_callback = NULL;') 565 | lines.append('ud->callback_data = NULL;') 566 | 567 | lines.append('luaL_getmetatable(L, "%s");' % metatable(package, message)) 568 | lines.append('lua_setmetatable(L, -2);') 569 | lines.append('return 1;') 570 | 571 | lines.append('}\n') 572 | 573 | return lines 574 | 575 | def message_pushcopy_function(package, message): 576 | '''Returns function definition for pushing a copy of a message to the stack''' 577 | 578 | return [ 579 | 'bool %spushcopy(lua_State *L, const %s &from)' % ( message_function_prefix(package, message), cpp_class(package, message) ), 580 | '{', 581 | 'msg_udata * ud = (msg_udata *)lua_newuserdata(L, sizeof(msg_udata));', 582 | 'ud->lua_owns = true;', 583 | 'ud->msg = new %s(from);' % cpp_class(package, message), 584 | 'ud->gc_callback = NULL;', 585 | 'ud->callback_data = NULL;', 586 | 'luaL_getmetatable(L, "%s");' % metatable(package, message), 587 | 'lua_setmetatable(L, -2);', 588 | 'return true;', 589 | '}', 590 | ] 591 | 592 | def message_pushreference_function(package, message): 593 | '''Returns function definition for pushing a reference of a message on the stack''' 594 | 595 | return [ 596 | 'bool %spushreference(lua_State *L, %s *msg, lua_protobuf_gc_callback f, void *data)' % ( message_function_prefix(package, message), cpp_class(package, message) ), 597 | '{', 598 | 'msg_udata * ud = (msg_udata *)lua_newuserdata(L, sizeof(msg_udata));', 599 | 'ud->lua_owns = false;', 600 | 'ud->msg = msg;', 601 | 'ud->gc_callback = f;', 602 | 'ud->callback_data = data;', 603 | 'luaL_getmetatable(L, "%s");' % metatable(package, message), 604 | 'lua_setmetatable(L, -2);', 605 | 'return true;', 606 | '}', 607 | ] 608 | 609 | def parsefromstring_message_function(package, message): 610 | '''Returns function definition for parsing a message from a serialized string''' 611 | 612 | lines = [] 613 | 614 | lines.append('int %sparsefromstring(lua_State *L)' % message_function_prefix(package, message)) 615 | c = cpp_class(package, message) 616 | 617 | lines.extend([ 618 | '{', 619 | 'if (lua_gettop(L) != 1) {', 620 | 'return luaL_error(L, "parsefromstring() requires a string argument. none given");', 621 | '}', 622 | 623 | 'size_t len;', 624 | 'const char *s = luaL_checklstring(L, -1, &len);', 625 | '%s * msg = new %s();' % ( c, c ), 626 | 'if (!msg->ParseFromArray((const void *)s, len)) {', 627 | 'return luaL_error(L, "error deserializing message");', 628 | '}', 629 | 630 | 'msg_udata * ud = (msg_udata *)lua_newuserdata(L, sizeof(msg_udata));', 631 | 'ud->lua_owns = true;', 632 | 'ud->msg = msg;', 633 | 'ud->gc_callback = NULL;', 634 | 'ud->callback_data = NULL;', 635 | 'luaL_getmetatable(L, "%s");' % metatable(package, message), 636 | 'lua_setmetatable(L, -2);', 637 | 638 | 'return 1;', 639 | '}', 640 | ]) 641 | 642 | return lines 643 | 644 | def gc_message_function(package, message): 645 | '''Returns function definition for garbage collecting a message''' 646 | 647 | lines = [ 648 | 'int %sgc(lua_State *L)' % message_function_prefix(package, message), 649 | '{', 650 | ] 651 | lines.extend(obtain_message_from_udata(package, message, 1)) 652 | # if Lua "owns" the message, we delete it 653 | # else, we delete only if a callback exists and it says it is OK 654 | lines.extend([ 655 | 'if (mud->lua_owns) {', 656 | 'delete mud->msg;', 657 | 'mud->msg = NULL;', 658 | 'return 0;', 659 | '}', 660 | 'if (mud->gc_callback && mud->gc_callback(m, mud->callback_data)) {', 661 | 'delete mud->msg;', 662 | 'mud->msg = NULL;', 663 | 'return 0;', 664 | '}', 665 | 'return 0;', 666 | '}', 667 | ]) 668 | 669 | return lines 670 | 671 | def clear_message_function(package, message): 672 | '''Returns the function definition for clearing a message''' 673 | 674 | lines = [ 675 | 'int %sclear(lua_State *L)' % message_function_prefix(package, message), 676 | '{' 677 | ] 678 | lines.extend(obtain_message_from_udata(package, message, 1)) 679 | lines.extend([ 680 | 'm->Clear();', 681 | 'return 0;', 682 | '}', 683 | ]) 684 | 685 | return lines 686 | 687 | def serialized_message_function(package, message): 688 | '''Returns the function definition for serializing a message''' 689 | 690 | lines = [ 691 | 'int %sserialized(lua_State *L)' % message_function_prefix(package, message), 692 | '{' 693 | ] 694 | lines.extend(obtain_message_from_udata(package, message, 1)) 695 | lines.extend([ 696 | 'string s;', 697 | 'if (!m->SerializeToString(&s)) {', 698 | 'return luaL_error(L, "error serializing message");', 699 | '}', 700 | 'lua_pushlstring(L, s.c_str(), s.length());', 701 | 'return 1;', 702 | '}', 703 | ]) 704 | 705 | return lines 706 | 707 | def message_function_array(package, message): 708 | '''Defines functions for Lua object type 709 | 710 | These are defined on the Lua metatable for the message type. 711 | These are basically constructors and static methods in Lua land. 712 | ''' 713 | return [ 714 | 'static const struct luaL_Reg %s_functions [] = {' % message, 715 | '{"new", %snew},' % message_function_prefix(package, message), 716 | '{"parsefromstring", %sparsefromstring},' % message_function_prefix(package, message), 717 | '{NULL, NULL}', 718 | '};\n', 719 | ] 720 | 721 | def message_method_array(package, descriptor): 722 | '''Defines functions for Lua object instances 723 | 724 | These are functions available to each instance of a message. 725 | They take the object userdata as the first parameter. 726 | ''' 727 | 728 | message = descriptor.name 729 | fp = message_function_prefix(package, message) 730 | 731 | lines = [] 732 | lines.append('static const struct luaL_Reg %s_methods [] = {' % message) 733 | lines.append('{"serialized", %sserialized},' % fp) 734 | lines.append('{"clear", %sclear},' % fp) 735 | lines.append('{"__gc", %sgc},' % message_function_prefix(package, message)) 736 | 737 | for fd in descriptor.field: 738 | name = fd.name 739 | label = fd.label 740 | type = fd.type 741 | 742 | lines.append('{"clear_%s", %s},' % ( name, field_function_name(package, message, 'clear', name) )) 743 | lines.append('{"get_%s", %s},' % ( name, field_function_name(package, message, 'get', name) )) 744 | lines.append('{"set_%s", %s},' % ( name, field_function_name(package, message, 'set', name) )) 745 | 746 | if label in [ FieldDescriptor.LABEL_REQUIRED, FieldDescriptor.LABEL_OPTIONAL ]: 747 | lines.append('{"has_%s", %s},' % ( name, field_function_name(package, message, 'has', name) )) 748 | 749 | if label == FieldDescriptor.LABEL_REPEATED: 750 | lines.append('{"size_%s", %s},' % ( name, field_function_name(package, message, 'size', name) )) 751 | 752 | if type == FieldDescriptor.TYPE_MESSAGE: 753 | lines.append('{"add_%s", %s},' % ( name, field_function_name(package, message, 'add', name) )) 754 | 755 | lines.append('{NULL, NULL},') 756 | lines.append('};\n') 757 | 758 | return lines 759 | 760 | def message_open_function(package, descriptor): 761 | '''Function definition for opening/registering a message type''' 762 | 763 | message = descriptor.name 764 | 765 | lines = [ 766 | 'int %s(lua_State *L)' % message_open_function_name(package, message), 767 | '{', 768 | 'luaL_newmetatable(L, "%s");' % metatable(package, message), 769 | 'lua_pushvalue(L, -1);', 770 | 'lua_setfield(L, -2, "__index");', 771 | 'luaL_register(L, NULL, %s_methods);' % message, 772 | 'luaL_register(L, "%s", %s_functions);' % (lua_libname(package, message), message), 773 | ] 774 | 775 | for enum_descriptor in descriptor.enum_type: 776 | lines.extend(enum_source(enum_descriptor)) 777 | 778 | lines.extend([ 779 | # this is wrong if we are calling through normal Lua module load means 780 | 'lua_pop(L, 1);', 781 | 'return 1;', 782 | '}', 783 | '\n', 784 | ]) 785 | 786 | return lines 787 | 788 | def message_header(package, message_descriptor): 789 | '''Returns the lines for a header definition of a message''' 790 | 791 | message_name = message_descriptor.name 792 | 793 | lines = [] 794 | lines.append('// Message %s' % message_name) 795 | 796 | function_prefix = 'lua_protobuf_' + package.replace('.', '_') + '_' 797 | c = cpp_class(package, message_name) 798 | 799 | lines.extend([ 800 | '// registers the message type with Lua', 801 | 'LUA_PROTOBUF_EXPORT int %s(lua_State *L);\n' % message_open_function_name(package, message_name), 802 | '', 803 | '// push a copy of the message to the Lua stack', 804 | '// caller is free to use original message however she wants, but changes will not', 805 | '// be reflected in Lua and vice-verse', 806 | 'LUA_PROTOBUF_EXPORT bool %s%s_pushcopy(lua_State *L, const %s &msg);' % ( function_prefix, message_name, c), 807 | '', 808 | '// push a reference of the message to the Lua stack', 809 | '// the 3rd and 4th arguments define a callback that can be invoked just before Lua', 810 | '// garbage collects the message. If the 3rd argument is NULL, Lua will *NOT* free', 811 | '// memory. If the second argument points to a function, that function is called when', 812 | '// Lua garbage collects the object. The function is sent a pointer to the message being', 813 | '// collected and the 4th argument to this function. If the function returns true,', 814 | '// Lua will free the memory. If false (0), Lua will not free the memory.', 815 | 'LUA_PROTOBUF_EXPORT bool %s%s_pushreference(lua_State *L, %s *msg, lua_protobuf_gc_callback callback, void *data);' % ( function_prefix, message_name, c ), 816 | '', 817 | '', 818 | '// The following functions are called by Lua. Many people will not need them,', 819 | '// but they are exported for those that do.', 820 | '', 821 | '', 822 | '// constructor called from Lua', 823 | 'LUA_PROTOBUF_EXPORT int %s%s_new(lua_State *L);' % ( function_prefix, message_name ), 824 | '', 825 | '// obtain instance from a serialized string', 826 | 'LUA_PROTOBUF_EXPORT int %s%s_parsefromstring(lua_State *L);' % ( function_prefix, message_name ), 827 | '', 828 | '// garbage collects message instance in Lua', 829 | 'LUA_PROTOBUF_EXPORT int %s%s_gc(lua_State *L);' % ( function_prefix, message_name ), 830 | '', 831 | '// obtain serialized representation of instance', 832 | 'LUA_PROTOBUF_EXPORT int %s%s_serialized(lua_State *L);' % ( function_prefix, message_name ), 833 | '', 834 | '// clear all fields in the message', 835 | 'LUA_PROTOBUF_EXPORT int %s%s_clear(lua_State *L);' % ( function_prefix, message_name ), 836 | '', 837 | ]) 838 | 839 | # each field defined in the message 840 | for field_descriptor in message_descriptor.field: 841 | field_name = field_descriptor.name 842 | field_number = field_descriptor.number 843 | field_label = field_descriptor.label 844 | field_type = field_descriptor.type 845 | field_default = field_descriptor.default_value 846 | 847 | if field_label not in FIELD_LABEL_MAP.keys(): 848 | raise Exception('unknown field label constant: %s' % field_label) 849 | 850 | field_label_s = FIELD_LABEL_MAP[field_label] 851 | 852 | if field_type not in FIELD_TYPE_MAP.keys(): 853 | raise Exception('unknown field type: %s' % field_type) 854 | 855 | field_type_s = FIELD_TYPE_MAP[field_type] 856 | 857 | lines.append('// %s %s %s = %d' % (field_label_s, field_type_s, field_name, field_number)) 858 | lines.append('LUA_PROTOBUF_EXPORT int %s%s_clear_%s(lua_State *L);' % (function_prefix, message_name, field_name)) 859 | lines.append('LUA_PROTOBUF_EXPORT int %s%s_get_%s(lua_State *L);' % (function_prefix, message_name, field_name)) 860 | 861 | # TODO I think we can get rid of this for message types 862 | lines.append('LUA_PROTOBUF_EXPORT int %s%s_set_%s(lua_State *L);' % (function_prefix, message_name, field_name)) 863 | 864 | if field_label in [ FieldDescriptor.LABEL_REQUIRED, FieldDescriptor.LABEL_OPTIONAL ]: 865 | lines.append('LUA_PROTOBUF_EXPORT int %s%s_has_%s(lua_State *L);' % (function_prefix, message_name, field_name)) 866 | 867 | if field_label == FieldDescriptor.LABEL_REPEATED: 868 | lines.append('LUA_PROTOBUF_EXPORT int %s%s_size_%s(lua_State *L);' % (function_prefix, message_name, field_name)) 869 | 870 | if field_type == FieldDescriptor.TYPE_MESSAGE: 871 | lines.append('LUA_PROTOBUF_EXPORT int %s%s_add_%s(lua_State *L);' % ( function_prefix, message_name, field_name)) 872 | 873 | lines.append('') 874 | 875 | lines.append('// end of message %s\n' % message_name) 876 | 877 | return lines 878 | 879 | 880 | def message_source(package, message_descriptor): 881 | '''Returns lines of source code for an individual message type''' 882 | lines = [] 883 | 884 | message = message_descriptor.name 885 | 886 | lines.extend(message_function_array(package, message)) 887 | lines.extend(message_method_array(package, message_descriptor)) 888 | lines.extend(message_open_function(package, message_descriptor)) 889 | lines.extend(message_pushcopy_function(package, message)) 890 | lines.extend(message_pushreference_function(package, message)) 891 | lines.extend(new_message(package, message)) 892 | lines.extend(parsefromstring_message_function(package, message)) 893 | lines.extend(gc_message_function(package, message)) 894 | lines.extend(clear_message_function(package, message)) 895 | lines.extend(serialized_message_function(package, message)) 896 | 897 | for descriptor in message_descriptor.field: 898 | name = descriptor.name 899 | 900 | # clear() is in all label types 901 | lines.extend(field_function_start(package, message, 'clear', name)) 902 | lines.extend(clear_body(package, message, name)) 903 | lines.append('}\n') 904 | 905 | lines.extend(field_get(package, message, descriptor)) 906 | lines.extend(field_set(package, message, descriptor)) 907 | 908 | if descriptor.label in [FieldDescriptor.LABEL_OPTIONAL, FieldDescriptor.LABEL_REQUIRED]: 909 | # has_() 910 | lines.extend(field_function_start(package, message, 'has', name)) 911 | lines.extend(has_body(package, message, name)) 912 | lines.append('}\n') 913 | 914 | if descriptor.label == FieldDescriptor.LABEL_REPEATED: 915 | # size_() 916 | lines.extend(field_function_start(package, message, 'size', name)) 917 | lines.extend(size_body(package, message, name)) 918 | lines.append('}\n') 919 | 920 | if descriptor.type == FieldDescriptor.TYPE_MESSAGE: 921 | lines.extend(field_function_start(package, message, 'add', name)) 922 | lines.extend(add_body(package, message, name, descriptor.type_name)) 923 | lines.append('}\n') 924 | 925 | 926 | return lines 927 | 928 | def enum_source(descriptor): 929 | '''Returns source code defining an enumeration type''' 930 | 931 | # this function assumes the module/table the enum should be assigned to 932 | # is at the top of the stack when it is called 933 | 934 | name = descriptor.name 935 | 936 | # enums are a little funky 937 | # at the core, there is a table whose keys are the enum string names and 938 | # values corresponding to the respective integer values. this table also 939 | # has a metatable with __index to throw errors when unknown enumerations 940 | # are accessed 941 | # 942 | # this table is then wrapped in a proxy table. the proxy table is empty 943 | # but has a metatable with __index and __newindex set. __index is the 944 | # table that actually contains the values. __newindex is a function that 945 | # always throws an error. 946 | # 947 | # we need the proxy table so we can intercept all requests for writes. 948 | # __newindex is only called for new keys, so we need an empty table so 949 | # all writes are sent to __newindex 950 | lines = [ 951 | '// %s enum' % name, 952 | 'lua_newtable(L); // proxy table', 953 | 'lua_newtable(L); // main table', 954 | ] 955 | 956 | # assign enumerations to the table 957 | for value in descriptor.value: 958 | k = value.name 959 | v = value.number 960 | lines.extend([ 961 | 'lua_pushnumber(L, %d);' % v, 962 | 'lua_setfield(L, -2, "%s");' % k 963 | ]) 964 | 965 | # assign the metatable 966 | lines.extend([ 967 | '// define metatable on main table', 968 | 'lua_newtable(L);', 969 | 'lua_pushcfunction(L, lua_protobuf_enum_index);', 970 | 'lua_setfield(L, -2, "__index");', 971 | 'lua_setmetatable(L, -2);', 972 | '', 973 | 974 | '// define metatable on proxy table', 975 | 'lua_newtable(L);', 976 | # proxy meta: -1; main: -2; proxy: -3 977 | 'lua_pushvalue(L, -2);', 978 | 'lua_setfield(L, -2, "__index");', 979 | 'lua_pushcfunction(L, lua_protobuf_enum_newindex);', 980 | 'lua_setfield(L, -2, "__newindex");', 981 | 'lua_remove(L, -2);', 982 | 'lua_setmetatable(L, -2);', 983 | 984 | # proxy at top of stack now 985 | # assign to appropriate module 986 | 'lua_setfield(L, -2, "%s");' % name, 987 | '// end %s enum' % name 988 | ]) 989 | 990 | return lines 991 | 992 | def file_header(file_descriptor): 993 | 994 | filename = file_descriptor.name 995 | package = file_descriptor.package 996 | 997 | lines = [] 998 | 999 | lines.extend(c_header_header(filename, package)) 1000 | 1001 | for descriptor in file_descriptor.message_type: 1002 | lines.extend(message_header(package, descriptor)) 1003 | 1004 | lines.append('#ifdef __cplusplus') 1005 | lines.append('}') 1006 | lines.append('#endif') 1007 | lines.append('') 1008 | lines.append('#endif') 1009 | 1010 | return '\n'.join(lines) 1011 | 1012 | def file_source(file_descriptor): 1013 | '''Obtains the source code for a FileDescriptor instance''' 1014 | 1015 | filename = file_descriptor.name 1016 | package = file_descriptor.package 1017 | 1018 | lines = [] 1019 | lines.extend(source_header(filename, package)) 1020 | lines.append('using ::std::string;\n') 1021 | 1022 | lines.extend([ 1023 | 'int %sopen(lua_State *L)' % package_function_prefix(package), 1024 | '{', 1025 | ]) 1026 | 1027 | # we populate enumerations as tables inside the protobuf global 1028 | # variable/module 1029 | # this is a little tricky, because we need to ensure all the parent tables 1030 | # are present 1031 | # i.e. protobuf.package.foo.enum => protobuf['package']['foo']['enum'] 1032 | # we interate over all the tables and create missing ones, as necessary 1033 | 1034 | # we cheat here and use the undocumented/internal luaL_findtable function 1035 | # we probably shouldn't rely on an "internal" API, so 1036 | # TODO don't use internal API call 1037 | lines.extend([ 1038 | 'const char *table = luaL_findtable(L, LUA_GLOBALSINDEX, "protobuf.%s", 1);' % package, 1039 | 'if (table) {', 1040 | 'return luaL_error(L, "could not create parent Lua tables");', 1041 | '}', 1042 | 'if (!lua_istable(L, -1)) {', 1043 | 'lua_newtable(L);', 1044 | 'lua_setfield(L, -2, "%s");' % package, 1045 | '}', 1046 | ]) 1047 | 1048 | for descriptor in file_descriptor.enum_type: 1049 | lines.extend(enum_source(descriptor)) 1050 | 1051 | lines.extend([ 1052 | # don't need main table on stack any more 1053 | 'lua_pop(L, 1);', 1054 | 1055 | # and we register this package as a module, complete with enumerations 1056 | 'luaL_Reg funcs [] = { { NULL, NULL } };', 1057 | 'luaL_register(L, "protobuf.%s", funcs);' % package, 1058 | ]) 1059 | 1060 | for descriptor in file_descriptor.message_type: 1061 | lines.append('%s(L);' % message_open_function_name(package, descriptor.name)) 1062 | 1063 | lines.append('return 1;') 1064 | lines.append('}') 1065 | lines.append('\n') 1066 | 1067 | for descriptor in file_descriptor.message_type: 1068 | lines.extend(message_source(package, descriptor)) 1069 | 1070 | # perform some hacky pretty-printing 1071 | formatted = [] 1072 | indent = 0 1073 | for line in lines: 1074 | if RE_BARE_BEGIN_BRACKET.search(line): 1075 | formatted.append((' ' * indent) + line) 1076 | indent += 4 1077 | elif RE_BEGIN_BRACKET.search(line): 1078 | formatted.append((' ' * indent) + line) 1079 | indent += 4 1080 | elif RE_END_BRACKET.search(line): 1081 | if indent >= 4: 1082 | indent -= 4 1083 | formatted.append((' ' * indent) + line) 1084 | else: 1085 | formatted.append((' ' * indent) + line) 1086 | 1087 | return '\n'.join(formatted) 1088 | 1089 | -------------------------------------------------------------------------------- /protoc-gen-lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2010 Gregory Szorc 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This is a compiler plugin for protoc 18 | # see http://code.google.com/apis/protocolbuffers/docs/reference/other.html 19 | # 20 | # It produces C++ code that defines Lua userdata types for protocol buffer 21 | # messages. This C++ code makes calls to the C++ code generated by Google's 22 | # C++ generator that ships with Protocol Buffers. 23 | # 24 | # Like the C++ generator, we put all messages from the same file into the 25 | # same output file. 26 | # 27 | # Yes, it is currently written in Python. That's how bootstrapping works, 28 | # people. 29 | 30 | from lua_protobuf.generator import file_source, file_header, lua_protobuf_header, lua_protobuf_source 31 | from google.protobuf.descriptor import FieldDescriptor 32 | from google.protobuf.compiler.plugin_pb2 import CodeGeneratorRequest, CodeGeneratorResponse 33 | from sys import stdin, stdout, stderr 34 | 35 | # Python 3 should use binary buffer object 36 | try: 37 | stdin = stdin.buffer 38 | except AttributeError: 39 | pass 40 | try: 41 | stdout = stdout.buffer 42 | except AttributeError: 43 | pass 44 | 45 | serialized = stdin.read() 46 | request = CodeGeneratorRequest() 47 | request.ParseFromString(serialized) 48 | 49 | response = CodeGeneratorResponse() 50 | 51 | # each input file to the compiler 52 | for i in range(0, len(request.proto_file)): 53 | file_descriptor = request.proto_file[i] 54 | filename = file_descriptor.name 55 | package = file_descriptor.package 56 | 57 | # for now, we require package, which is bad 58 | # TODO fix this 59 | if not package: 60 | response.error = 'file seen without package. lua-protobuf currently requires a package on every proto file: %s' % filename 61 | break 62 | 63 | define_value = package.replace('.', '_').upper() 64 | cpp_header = '%s.pb.h' % package.replace('.', '/') 65 | cpp_namespace = '::%s' % package.replace('.', '::') 66 | 67 | f = response.file.add() 68 | f.name = '%s.pb-lua.h' % package.replace('.', '/') 69 | f.content = file_header(file_descriptor) 70 | 71 | f = response.file.add() 72 | f.name = '%s.pb-lua.cc' % package.replace('.', '/') 73 | f.content = file_source(file_descriptor) 74 | 75 | f = response.file.add() 76 | f.name = 'lua-protobuf.h' 77 | f.content = lua_protobuf_header() 78 | 79 | f = response.file.add() 80 | f.name = 'lua-protobuf.cc' 81 | f.content = lua_protobuf_source() 82 | 83 | stdout.write(response.SerializeToString()) 84 | exit(0) 85 | 86 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 Gregory Szorc 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from setuptools import setup, find_packages 16 | 17 | setup( 18 | name = 'lua-protobuf', 19 | version = '0.0.1', 20 | packages = [ 'lua_protobuf' ], 21 | scripts = ['protoc-gen-lua'], 22 | install_requires = [ 'protobuf>=2.3.0' ], 23 | author = 'Gregory Szorc', 24 | author_email = 'gregory.szorc@gmail.com', 25 | description = 'Lua protocol buffer code generator', 26 | license = 'Apache 2.0', 27 | url = 'http://github.com/indygreg/lua-protobuf' 28 | ) 29 | --------------------------------------------------------------------------------