Adding lua-protobuf

This commit is contained in:
Nicolas Pixel Noble 2022-12-18 22:46:11 -08:00
parent 71dd4a474f
commit f506f642f5
21 changed files with 9547 additions and 1 deletions

View file

@ -23,8 +23,8 @@ find a link to the vendored version itself.
- [ELFIO](https://github.com/serge1/ELFIO)
- [expected](https://github.com/TartanLlama/expected)
- [ffi-reflect](https://github.com/corsix/ffi-reflect) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/ffi-reflect))
- [FFmpeg](https://ffmpeg.org)
- [flags](https://github.com/sailormoon/flags) ([vendored](https://github.com/grumpycoders/pcsx-redux/blob/main/third_party/flags.h))
- [ffmpeg](https://ffmpeg.org)
- [fmt](https://github.com/fmtlib/fmt)
- [FreeType](https://gitlab.freedesktop.org/freetype/freetype.git)
- [gl3w](https://github.com/skaslev/gl3w) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/gl3w/GL))
@ -41,6 +41,7 @@ find a link to the vendored version itself.
- [json](https://github.com/nlohmann/json) ([vendored](https://github.com/grumpycoders/pcsx-redux/blob/main/third_party/json.hpp))
- [libcester](https://github.com/exoticlibraries/libcester)
- [libuv](https://github.com/libuv/libuv)
- [lua-protobuf](https://github.com/starwing/lua-protobuf) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/lua-protobuf))
- [LuaJIT](https://github.com/LuaJIT/LuaJIT) ([vendored](https://github.com/grumpycoders/LuaJIT/tree/vendored-clib-virtual))
- [luv](https://github.com/luvit/luv)
- [magic_enum](https://github.com/Neargye/magic_enum)

View file

@ -123,6 +123,7 @@ SRCS += third_party/imgui/misc/freetype/imgui_freetype.cpp
SRCS += third_party/imgui_lua_bindings/imgui_lua_bindings.cpp
SRCS += third_party/imgui_md/imgui_md.cpp
SRCS += third_party/imgui_memory_editor/imgui_memory_editor.cpp
SRCS += third_party/lua-protobuf/pb.c
SRCS += third_party/luv/src/luv.c
SRCS += third_party/md4c/src/md4c.c
SRCS += third_party/multipart-parser-c/multipart_parser.c

View file

@ -23,4 +23,5 @@
-Ithird_party/xbyak/xbyak
-DIMGUI_IMPL_OPENGL_LOADER_GL3W
-DIMGUI_ENABLE_FREETYPE
-DLUAJIT_ENABLE_LUA52COMPAT
-DZEP_FEATURE_CPP_FILE_SYSTEM

View file

@ -19,16 +19,58 @@
#include "lua/extra.h"
#include "lua-protobuf/pb.h"
#include "lua/luawrapper.h"
void PCSX::LuaFFI::open_extra(Lua L) {
L.getfieldtable("_LOADED", LUA_REGISTRYINDEX);
luaopen_pb(L.getState());
L.setfield("pb", -3);
L.pop();
luaopen_pb_io(L.getState());
L.setfield("pb.io");
luaopen_pb_conv(L.getState());
L.setfield("pb.conv");
luaopen_pb_slice(L.getState());
L.setfield("pb.slice");
luaopen_pb_buffer(L.getState());
L.setfield("pb.buffer");
luaopen_pb_unsafe(L.getState());
L.setfield("pb.unsafe");
static int lualoader = 1;
static const char* pprint = (
#include "pprint.lua/pprint.lua"
);
static const char* reflectFFI = (
#include "ffi-reflect/reflect.lua"
);
static const char* protobufLexer = (
#include "lua-protobuf/lexer.lua"
);
static const char* protobufTopLevel = (
#include "lua-protobuf/toplevel.lua"
);
static const char* descriptorPB = (
#include "lua-protobuf/descriptor.pb.lua"
);
static const char* protoc = (
#include "lua-protobuf/protoc.lua"
);
L.load(pprint, "internal:pprinter.lua/pprint.lua");
L.load(reflectFFI, "internal:ffi-reflect/reflect.lua");
L.load(protobufLexer, "internal:lua-protobuf/lexer.lua");
L.setfield("pb.Lexer");
L.load(protobufTopLevel, "internal:lua-protobuf/toplevel.lua");
L.setfield("pb.TopLevel");
L.load(descriptorPB, "internal:lua-protobuf/descriptor.pb.lua");
L.setfield("pb.Descriptor");
L.load(protoc, "internal:lua-protobuf/protoc.lua");
L.setfield("protoc");
L.pop();
}

24
third_party/lua-protobuf/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
*.dSYM
*.dll
*.dll.*
*.exp
*.gcda
*.gcno
*.gcov
*.iobj
*.ipdb
*.lib
*.natvis
*.obj
*.out
*.pb
*.pdb
*.rock
*.so
*.o
*.gc*
google
lua*/
out*
test*
test*.lua

45
third_party/lua-protobuf/.travis.yml vendored Normal file
View file

@ -0,0 +1,45 @@
language: c
os: linux
dist: xenial
env:
global:
- ROCKSPEC=rockspecs/lua-protobuf-scm-1.rockspec
jobs:
- LUA="lua 5.1"
- LUA="lua 5.2"
- LUA="lua 5.3"
- LUA="lua 5.4"
- LUA="luajit 2.0"
- LUA="luajit 2.1"
branches:
only:
- master
- lua54
before_install:
- pip install --user urllib3[secure] cpp-coveralls
- curl -O https://raw.githubusercontent.com/luarocks/hererocks/master/hererocks.py
- python hererocks.py env --$LUA -rlatest
- source env/bin/activate
install:
# - sudo luarocks make $ROCKSPEC CFLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" LIBFLAG="-shared --coverage"
- luarocks make $ROCKSPEC CFLAGS="-O3 -fPIC -Wall -Wextra --coverage" LIBFLAG="-shared --coverage"
script:
- lua test.lua
# - lunit.sh test.lua
after_success:
- coveralls
# - coveralls -b .. -r .. --dump c.report.json
# - luacov-coveralls -j c.report.json -v
notifications:
email:
on_success: change
on_failure: always
# vim: ft=yaml nu et sw=2 fdc=2 fdm=syntax

21
third_party/lua-protobuf/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Xavier Wang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

517
third_party/lua-protobuf/README.md vendored Normal file
View file

@ -0,0 +1,517 @@
# Google protobuf support for Lua
[![Build Status](https://img.shields.io/github/workflow/status/starwing/lua-protobuf/CI)](https://github.com/starwing/lua-protobuf/actions?query=branch%3Amaster)[![Coverage Status](https://img.shields.io/coveralls/github/starwing/lua-protobuf)](https://coveralls.io/github/starwing/lua-protobuf?branch=master)
English | [中文](https://github.com/starwing/lua-protobuf/blob/master/README.zh.md)
---
This project offers a C module for Lua (5.1, 5.2, 5.3, 5.4 and LuaJIT) manipulating Google's protobuf protocol, both for version 2 and 3 syntax and semantics. It splits to the lower-level and the high-level parts for different goals.
For converting between binary protobuf data with Lua tables, using `pb.load()` loads the compiled protobuf schema content (`*.pb` file) generated by Google protobuf's compiler named `protoc` and call `pb.encode()`/`pb.decode()`.
Or use these modules to manipulate the raw wire format in lower-level way:
- `pb.slice`: a wire format decoding module.
- `pb.buffer`: a buffer implement that use to encode basic types into protobuf's wire format. It can be used to support streaming decode protobuf data.
- `pb.conv`: a module converting integers in the protobuf wire format.
- `pb.io`: a module access `stdin/stdout` or other files in binary mode.
If you don't want to depend Google's protobuf compiler, `protoc.lua` is a pure Lua module translating text-based protobuf schema content into the `*.pb` binary format.
## Install
To install, you could just use `luarocks`:
```shell
luarocks install lua-protobuf
```
If you want to build it from source, just clone the repo and use luarocks:
```shell
git clone https://github.com/starwing/lua-protobuf
luarocks make rockspecs/lua-protobuf-scm-1.rockspec
```
If you don't have luarocks, use `hererocks` to install Lua and luarocks:
```shell
pip install hererocks
git clone https://github.com/starwing/lua-protobuf
hererocks -j 2.0 -rlatest .
bin/luarocks make lua-protobuf/rockspecs/lua-protobuf-scm-1.rockspec CFLAGS="-fPIC -Wall -Wextra" LIBFLAGS="-shared"
cp protoc.lua pb.so ..
```
Or you can build it by hand, it only has a pure Lua module `protoc.lua` and a pair of C source: `pb.h` and `pb.c`. *Notice* that in order to build the `pb` C module, you need Lua header file and/or libary file installed. replace `$LUA_HEADERS` and `$LUA_LIBS` below to real install locations.
To build it on macOS, use your favor compiler:
```shell
gcc -O2 -shared -undefined dynamic_lookup -I "$LUA_HEADERS" pb.c -o pb.so
```
On Linux, use the nearly same command:
```shell
gcc -O2 -shared -fPIC -I "$LUA_HEADERS" pb.c -o pb.so
```
On Windows, you could use MinGW or MSVC, create a `*.sln` project or build it on the command line (notice the `Lua_BUILD_AS_DLL` flag):
```shell
cl /O2 /LD /Fepb.dll /I "$LUA_HEADERS" /DLUA_BUILD_AS_DLL pb.c "$LUA_LIBS"
```
## Example
```lua
local pb = require "pb"
local protoc = require "protoc"
-- load schema from text (just for demo, use protoc.new() in real world)
assert(protoc:load [[
message Phone {
optional string name = 1;
optional int64 phonenumber = 2;
}
message Person {
optional string name = 1;
optional int32 age = 2;
optional string address = 3;
repeated Phone contacts = 4;
} ]])
-- lua table data
local data = {
name = "ilse",
age = 18,
contacts = {
{ name = "alice", phonenumber = 12312341234 },
{ name = "bob", phonenumber = 45645674567 }
}
}
-- encode lua table data into binary format in lua string and return
local bytes = assert(pb.encode("Person", data))
print(pb.tohex(bytes))
-- and decode the binary data back into lua table
local data2 = assert(pb.decode("Person", bytes))
print(require "serpent".block(data2))
```
## Use case
[![零境交错](https://img.tapimg.com/market/images/e59627dc9039ff22ba7d000b5c9fe7f6.jpg?imageView2/2/h/560/q/40/format/jpg/interlace/1/ignore-error/1)](http://djwk.qq.com)
## Usage
### `protoc` Module
| Function | Returns | Descriptions |
| ------------------- | ------------- | ---------------------------------------------------- |
| `protoc.new()` | Proroc object | create a new compiler instance |
| `protoc.reload()` | true | reload all google standard messages into `pb` module |
| `p:parse(string)` | table | transform schema to `DescriptorProto` table |
| `p:compile(string)` | string | transform schema to binary *.pb format data |
| `p:load(string)` | true | load schema into `pb` module |
| `p.loaded` | table | contains all parsed `DescriptorProto` table |
| `p.unknown_import` | see below | handle schema import error |
| `p.unknown_type` | see below | handle unknown type in schema |
| `p.include_imports` | bool | auto load imported proto |
To parse a text schema content, create a compiler instance first:
```lua
local p = protoc.new()
```
Then, set some options to the compiler, e.g. the unknown handlers:
```lua
-- set some hooks
p.unknown_import = function(self, module_name) ... end
p.unknown_type = function(self, type_name) ... end
-- ... and options
p.include_imports = true
```
The `unknown_import` and `unknown_type` handle could be `true`, string or a function. Seting it to `true` means all *non-exist* modules and types are given a default value without triggering an error; A string means a Lua pattern that indicates whether an unknown module or type should raise an error, e.g.
```lua
p.unknown_type = "Foo.*"
```
means all types prefixed by `Foo` will be treat as existing type and do not trigger errors.
If these are functions, the unknown type and module name will be passed to functions. For module handler, it should return a `DescriptorProto` Table produced by `p:load()` functions, for type handler, it should return a type name and type, such as `message` or `enum`, e.g.
```lua
function p:unknown_import(name)
-- if can not find "foo.proto", load "my_foo.proto" instead
return p:parsefile("my_"..name)
end
function p:unknown_type(name)
-- if cannot find "Type", treat it as ".MyType" and is a message type return ".My"..name, "message"
end
```
After setting options, use `load()` or `compile()` or `parse()` function to get result.
### `pb` Module
`pb` module has high-level routines to manipulate protobuf messages.
In below table of functions, we have several types that have special means:
- `type`: a string that indicates the protobuf message type, `".Foo"` means the type in a proto definition that has not `package` statement declared. `"foo.Foo"` means the type in a proto definition that declared `package foo;`
- `data`: could be string, `pb.Slice` value or `pb.Buffer` value.
- `iterator`: a function that can use in Lua `for in` statement, e.g.
```lua
for name in pb.types() do
print(name)
end
```
**NOTICE**: Only `pb.load()` returns error on failure, *do check* the result it returns. Other routines raise a error when failure for convenience.
| Function | Returns | Description |
| ------------------------------ | --------------- | ------------------------------------------------------- |
| `pb.clear()` | None | clear all types |
| `pb.clear(type)` | None | delete specific type |
| `pb.load(data)` | boolean,integer | load a binary schema data into `pb` module |
| `pb.encode(type, table)` | string | encode a message table into binary form |
| `pb.encode(type, table, b)` | buffer | encode a message table into binary form to buffer |
| `pb.decode(type, data)` | table | decode a binary message into Lua table |
| `pb.decode(type, data, table)` | table | decode a binary message into a given Lua table |
| `pb.pack(fmt, ...)` | string | same as `buffer.pack()` but return string |
| `pb.unpack(data, fmt, ...)` | values... | same as `slice.unpack()` but accept data |
| `pb.types()` | iterator | iterate all types in `pb` module |
| `pb.type(type)` | see below | return informations for specific type |
| `pb.fields(type)` | iterator | iterate all fields in a message |
| `pb.field(type, string)` | see below | return informations for specific field of type |
| `pb.typefmt(type)` | String | transform type name of field into pack/unpack formatter |
| `pb.enum(type, string)` | number | get the value of a enum by name |
| `pb.enum(type, number)` | string | get the name of a enum by value |
| `pb.defaults(type[, table])` | table | get the default table of type |
| `pb.hook(type[, function])` | function | get or set hook functions |
| `pb.option(string)` | string | set options to decoder/encoder |
| `pb.state()` | `pb.State` | retrieve current pb state |
| `pb.state(newstate \| nil)` | `pb.State` | set new pb state and retrieve the old one |
#### Schema loading
`pb.load()` accepts the schema binary data and returns a boolean indicates the result of loading, success or failure, and a offset reading in schema so far that is useful to figure out the reason of failure.
#### Type mapping
| Protobuf Types | Lua Types |
| -------------------------------------------------- | ------------------------------------------------------------ |
| `double`, `float` | `number` |
| `int32`, `uint32`, `fixed32`, `sfixed32`, `sint32` | `number` or `integer` in Lua 5.3+ |
| `int64`, `uint64`, `fixed64`, `sfixed64`, `sint64` | `number` or `"#"` prefixed `string` or `integer` in Lua 5.3+ |
| `bool` | `boolean` |
| `string`, `bytes` | `string` |
| `message` | `table` |
| `enum` | `string` or `number` |
#### Type Information
Using `pb.(type|field)[s]()` functions retrieve type information for loaded messages.
`pb.type()` returns multiple informations for specified type:
- name : the full qualifier name of type, e.g. ".package.TypeName"
- basename: the type name without package prefix, e.g. "TypeName"
- `"map"` | `"enum"` | `"message"`: whether the type is a map_entry type, enum type or message type.
`pb.types()` returns a iterators, behavior like call `pb.type()` on every types of all messages.
```lua
print(pb.type "MyType")
-- list all types that loaded into pb
for name, basename, type in pb.types() do
print(name, basename, type)
end
```
`pb.field()` returns information of the specified field for one type:
- name: the name of the field
- number: number of field in the schema
- type: field type
- default value: if no default value, nil
- `"packed"`|`"repeated"`| `"optional"`: label of the field, optional or repeated, required is not supported
- [oneof_name, oneof_index]: if this is a `oneof` field, this is the `oneof` name and index
And `pb.fields()` iterates all fields in a message:
```lua
print(pb.field("MyType", "the_first_field"))
-- notice that you needn't receive all return values from iterator
for name, number, type in pb.fields "MyType" do
print(name, number, type)
end
```
`pb.enum()` maps from enum name and value:
```lua
protoc:load [[
enum Color { Red = 1; Green = 2; Blue = 3 }
]]
print(pb.enum("Color", "Red")) --> 1
print(pb.enum("Color", 2)) --> "Green"
```
#### Default Values
Using `pb.defaults()` to get a table with all default values from a message. this table will be used as the metatable of the corresponding decoded message table when setting `use_default_metatable` option.
```lua
check_load [[
message TestDefault {
optional int32 defaulted_int = 10 [ default = 777 ];
optional bool defaulted_bool = 11 [ default = true ];
optional string defaulted_str = 12 [ default = "foo" ];
optional float defaulted_num = 13 [ default = 0.125 ];
} ]]
print(require "serpent".block(pb.defaults "TestDefault"))
-- output:
-- {
-- defaulted_bool = true,
-- defaulted_int = 777,
-- defaulted_num = 0.125,
-- defaulted_str = "foo"
-- } --[[table: 0x7f8c1e52b050]]
```
#### Hooks
If set `pb.option "enable_hooks"`, the hook function will be enabled. you could use `pb.hook()` and `pb.encode_hook` to set or get a decode or encode hook function, respectively: call it with type name directly get current setted hook; call it with two arguments to set a hook; and call it with `nil` as the second argument to remove the hook. in all case, the original one will be returned.
After the hook function setted and hook enabled, the decode function will be called *after* a message get decoded and encode functions will be called *before* the message is encoded. So you could get all values in the table passed to hook function. That's the only argument of hook.
If you need type name in hook functions, use this helper:
```lua
local function make_hook(name, func)
return pb.hook(name, function(t)
return func(name, t)
end)
end
```
#### Options
Setting options to change the behavior of other routines.
These options are supported currently:
| Option | Description |
| ----------------------- | ------------------------------------------------------------ |
| `enum_as_name` | set value to enum name when decode a enum **(default)** |
| `enum_as_value` | set value to enum value when decode a enum |
| `int64_as_number` | set value to integer when it fit into uint32, otherwise return a number **(default)** |
| `int64_as_string` | same as above, but return a string instead |
| `int64_as_hexstring` | same as above, but return a hexadigit string instead |
| `auto_default_values` | act as `use_default_values` for proto3 and act as `no_default_values` for the others **(default)** |
| `no_default_values` | do not default values for decoded message table |
| `use_default_values` | set default values by copy values from default table before decode |
| `use_default_metatable` | set default values by set table from `pb.default()` as the metatable |
| `enable_hooks` | `pb.decode` will call `pb.hooks()` hook functions |
| `disable_hooks` | `pb.decode` do not call hooks **(default)** |
| `encode_default_values` | default values also encode |
| `no_encode_default_values` | do not encode default values **(default)** |
| `decode_default_array` | work with `no_default_values`,decode null to empty table for array |
| `no_decode_default_array` | work with `no_default_values`,decode null to nil for array **(default)** |
| `encode_order` | guarantees the same message will be encoded into the same result with the same schema and the same data (but the order itself is not specified) |
| `no_encode_order` | do not have guarantees about encode orders **(default)** |
| `decode_default_message` | decode null message to default message table |
| `no_decode_default_message` | decode null message to null **(default)** |
*Note*: The string returned by `int64_as_string` or `int64_as_hexstring` will prefix a `'#'` character. Because Lua may convert between string with number, prefix a `'#'` makes Lua return the string as-is.
all routines in all module accepts `'#'` prefix `string`/`hex string` as arguments regardless of the option setting.
#### Multiple State
`pb` module support multiple states. A state is a database that contains all type information of registered messages. You can retrieve current state by `pb.state()`, or set new state by `pb.state(newstate)`.
Use `pb.state(nil)` to discard current state, but not to set a new one (the following routines call that use the state will create a new default state automatedly). Use `pb.state()` to retrieve current state without setting a new one. e.g.
```lua
local old = pb.state(nil)
-- if you use protoc.lua, call protoc.reload() here.
assert(pb.load(...))
-- do someting ...
pb.state(old)
```
Notice that if you use `protoc.lua` module, it will register some message to the state, so you should call `proto.reload()` after setting a new state.
### `pb.io` Module
`pb.io` module reads binary data from a file or `stdin`/`stdout`, `pb.io.read()` reads binary data from a file, or `stdin` if no file name given as the first parameter.
`pb.io.write()` and `pb.io.dump()` are same as Lua's `io.write()` except they write binary data. the former writes data to `stdout`, and the latter writes data to a file specified by the first parameter as the file name.
All these functions return a true value when success, and return `nil, errmsg` when an error occurs.
| Function | Returns | Description |
| ---------------------- | ------- | ----------------------------------- |
| `io.read()` | string | read all binary data from `stdin` |
| `io.read(string)` | string | read all binary data from file name |
| `io.write(...)` | true | write binary data to `stdout` |
| `io.dump(string, ...)` | string | write binary data to file name |
### `pb.conv` Module
`pb.conv` provide functions to convert between numbers.
| Encode Function | Decode Function |
| ---------------------- | ---------------------- |
| `conv.encode_int32()` | `conv.decode_int32()` |
| `conv.encode_uint32()` | `conv.decode_uint32()` |
| `conv.encode_sint32()` | `conv.decode_sint32()` |
| `conv.encode_sint64()` | `conv.decode_sint64()` |
| `conv.encode_float()` | `conv.decode_float()` |
| `conv.encode_double()` | `conv.decode_double()` |
### `pb.slice` Module
Slice object parse binary protobuf data in a low-level way. Use `slice.new()` to create a slice object, with the optional offset `i` and `j` to access a subpart of the original data (named a *view*).
As protobuf usually nest sub message with in a range of slice, a slice object has a stack itself to support this. Calling `s:enter(i, j)` saves current position and enters next level with the optional offset `i` and `j` just as `slice.new()`. calling `s:leave()` restore the prior view. `s:level()` returns the current level, and `s:level(n)` returns the current position, the start and the end position information of the `n`th level. calling `s:enter()` without parameter will read a length delimited type value from the slice and enter the view in reading value. Using `#a` to get the count of bytes remains in current view.
```lua
local s = slice.new("<data here>")
local tag = s:unpack "v"
if tag%8 == 2 then -- tag has a type of string/bytes? maybe it's a sub-message.
s:enter() -- read following bytes value, and enter the view of bytes value.
-- do something with bytes value, e.g. reads a lot of fixed32 integers from bytes.
local t = {}
while #s > 0 do
t[#t+1] = s:unpack "d"
end
s:leave() -- after done, leave bytes value and ready to read next value.
end
```
To read values from slice, use `slice.unpack()`, it use a format string to control how to read into a slice as below table (same format character are also used in `buffer.pack()`). Notice that you can use `pb.typefmt()` to convert between format and protobuf type names (returned from `pb.field()`).
| Format | Description |
| ------ | ------------------------------------------------------------ |
| v | variable Int value |
| d | 4 bytes fixed32 value |
| q | 8 bytes fixed64 value |
| s | length delimited value, usually a `string`, `bytes` or `message` in protobuf. |
| c | receive a extra number parameter `count` after the format, and reads `count` bytes in slice. |
| b | variable int value as a Lua `boolean` value. |
| f | 4 bytes `fixed32` value as floating point `number` value. |
| F | 8 bytes `fixed64` value as floating point `number` value. |
| i | variable int value as signed int value, i.e. `int32` |
| j | variable int value as zig-zad encoded signed int value, i.e.`sint32` |
| u | variable int value as unsigned int value, i.e. `uint32` |
| x | 4 bytes fixed32 value as unsigned fixed32 value, i.e.`fixed32` |
| y | 4 bytes fixed32 value as signed fixed32 value, i.e. `sfixed32` |
| I | variable int value as signed int value, i.e.`int64` |
| J | variable int value as zig-zad encoded signed int value, i.e. `sint64` |
| U | variable int value and treat it as `uint64` |
| X | 8 bytes fixed64 value as unsigned fixed64 value, i.e. `fixed64` |
| Y | 8 bytes fixed64 value as signed fixed64 value, i.e. `sfixed64` |
And extra format can be used to control the read cursor in one `slice.unpack()` process:
| Format | Description |
| ------ | ------------------------------------------------------------ |
| @ | returns current cursor position in the slice, related with the beginning of the current view. |
| * | set the current cursor position to the extra parameter after format string. |
| + | set the relate cursor position, i.e. add the extra parameter to the current position. |
e.g. If you want to read a `varint` value twice, you can write it as:
```lua
local v1, v2 = s:unpack("v*v", 1)
-- v: reads a `varint` value
-- *: receive the second parameter 1 and set it to the current cursor position, i.e. restore the cursor to the head of the view
-- v: reads the first `varint` value again
```
All routines in `pb.slice` module:
| Function | Returns | Description |
| ------------------------- | ------------ | ------------------------------------------------------------ |
| `slice.new(data[,i[,j]])` | Slice object | create a new slice object |
| `s:delete()` | none | same as `s:reset()`, free it's content |
| `tostring(s)` | string | return the string repr of the object |
| `#s` | number | returns the count of bytes can read in current view |
| `s:result([i[, j]])` | String | return the remaining bytes in current view |
| `s:reset([...])` | self | reset object to another data |
| `s:level()` | number | returns the count of stored state |
| `s:level(number)` | p, i, j | returns the informations of the `n`th stored state |
| `s:enter()` | self | reads a bytes value, and enter it's view |
| `s:enter(i[, j])` | self | enter a view start at `i` and ends at `j`, includes |
| `s:leave([number])` | self, n | leave the number count of level (default 1) and return current level |
| `s:unpack(fmt, ...)` | values... | reads values of current view from slice |
### `pb.buffer` Module
Buffer module used to construct a protobuf data format stream in a low-level way. It's just a bytes data buffer. using `buffer.pack()` to append values to the buffer, and `buffer.result()` to get the encoded raw data, or `buffer.tohex()` to get the human-readable hex digit value of data.
`buffer.pack()` use the same format syntax with `slice.unpack()`, and support `'()'` format means the inner value will be encoded as a length delimited value, i.e. a message value encoded format.
parenthesis can be nested.
e.g.
```lua
b:pack("(vvv)", 1, 2, 3) -- get a bytes value that contains three varint value.
```
`buffer.pack()` also support '#' format, it means prepends a length into the buffer.
e.g.
```lua
b:pack("#", 5) -- prepends a varint length #b-5+1 at offset 5
```
All routines in `pb.buffer` module:
| Function | Returns | Description |
| ------------------- | ------------- | ------------------------------------------------------------ |
| `buffer.new([...])` | Buffer object | create a new buffer object, extra args will passed to `b:reset()` |
| `b:delete()` | none | same as `b:reset()`, free it's content |
| `tostring(b)` | string | returns the string repr of the object |
| `#b` | number | returns the encoded count of bytes in buffer |
| `b:reset()` | self | reset to a empty buffer |
| `b:reset([...])` | self | resets the buffer and set its content as the concat of it's args |
| `b:tohex([i[, j]])` | string | return the string of hexadigit represent of the data, `i` and `j` are ranges in encoded data, includes. Omit it means the whole range |
| `b:result([i[,j]])` | string | return the raw data, `i` and `j` are ranges in encoded data, includes,. Omit it means the whole range |
| `b:pack(fmt, ...)` | self | encode the values passed to `b:pack()`, use `fmt` to indicate how to encode value |

545
third_party/lua-protobuf/README.zh.md vendored Normal file
View file

@ -0,0 +1,545 @@
# 在Lua中操作Google protobuf格式数据
[![Build Status](https://img.shields.io/github/workflow/status/starwing/lua-protobuf/CI)](https://github.com/starwing/lua-protobuf/actions?query=branch%3Amaster)[![Coverage Status](https://img.shields.io/coveralls/github/starwing/lua-protobuf)](https://coveralls.io/github/starwing/lua-protobuf?branch=master)
[English](https://github.com/starwing/lua-protobuf/blob/master/README.md) | 中文
---
Urho3d集成说明https://note.youdao.com/ynoteshare1/index.html?id=20d06649fab669371140256abd7a362b&type=note
Unreal SLua集成https://github.com/zengdelang/slua-unreal-pb
Unreal UnLua集成https://github.com/hxhb/unlua-pb
ToLua集成说明[链接](http://changxianjie.gitee.io/unitypartner/2019/10/01/tolua中使用protobuf3—集成lua-protobuf/)
xlua集成[链接](https://github.com/91Act/build_xlua_with_libs)
QQ群485016061 [![lua-protobuf1交流群](https://pub.idqqimg.com/wpa/images/group.png)](https://shang.qq.com/wpa/qunwpa?idkey=d7e2973604a723c4f77d0a837df39be26e15be2c2ec29d5ebfdb64f94e74e6ae)
本项目提供在 Lua 全版本5.1+、LuaJIT下的protobuf 2/3 版本支持。提供了高级的消息编码/解码接口以及底层的protobuf wireformat 二进制数据操作接口。
使用高级接口,你需要通过 `pb.load()` 接口导入 protobuf 的消息描述文件schema文件.proto后缀名的二进制版本本质上是schema文件通过官方的 `FileDescriptorSet` 编码得到的二进制pb消息导入的信息被存储在称为“state”的内存数据库中供消息编码/解码使用。你也可以通过`pb`模块提供的一系列接口来读取这个数据库里的内容。
要使用底层接口,你需要使用下面几个库提供的功能:
- `pb.slice`读取二进制的wireformat信息。
- `pb.buffer`写入二进制wireformat信息。
- `pb.conv`在Lua的数字类型和protobuf提供的一众数字数据类型之间转换。
- `pb.io`:用于将`pb`模块用于工具当中使用:通过标准输入输出流读取写入二进制消息。
另外为了得到schema文件的二进制版本一般后缀名是.pb的文件你需要官方protobuf项目提供的schema编译器二进制`protoc.exe`工具如果在你的平台下获得这个工具太麻烦或者你希望能有一个小体积的protobuf编译工具你可以使用项目自带的另一个独立的纯 Lua库`protoc.lua`文件。该文件提供了纯Lua实现的schema文件编译器并集成了通过调用`pb.load()`载入编译结果的方便接口。但是要注意因为这个库是纯Lua实现的它编译的速度会非常慢如果你的schema文件太大或者编译的时候遇到了性能瓶颈。还是推荐你通过`protoc.exe`或者在开发时利用`pb`库自己写脚本将schema编译成.pb文件供生产环境使用。
## 安装
**注意**`lua-prootbuf`毕竟是个纯C的Lua库而Lua库的编译安装是**有门槛**的。如果遇到了问题,建议询问**有Lua的C模块使用经验**的人或者参阅《Lua程序设计》里的相关内容预先学习相关知识。
另外Lua的C模块是通用的任何使用Lua的环境下都可以使用然而XLua等C#环境通常有自己的一套规则,需要一些额外的集成操作。请确认**自己对这些环境集成C模块足够了解**,或者**咨询足够了解的人**获得帮助。这里只提供通用C模块的安装方法。
最简单的安装方法是使用Lua生态的包管理器`luarocks`进行安装(注意,这样**依然需要你的电脑上装有C编译器**,如果安装失败,你应该首先检查你的`luarocks`能否正常工作能否正常安装其他Lua C模块
```shell
luarocks install lua-protobuf
```
你也可以使用`luarocks`从源码安装(这样装的版本会更新一些):
```shell
git clone https://github.com/starwing/lua-protobuf
luarocks make rockspecs/lua-protobuf-scm-1.rockspec
```
如果你没有/不会配置`luarocks`有一个Python写的方便脚本可以在你的电脑上安装`luarocks`——还是需要你有能正常运行的C编译器——当然也需要你有Python。
```shell
pip install hererocks
git clone https://github.com/starwing/lua-protobuf
hererocks -j 2.0 -rlatest .
bin/luarocks make lua-protobuf/rockspecs/lua-protobuf-scm-1.rockspec CFLAGS="-fPIC -Wall -Wextra" LIBFLAGS="-shared"
cp protoc.lua pb.so ..
```
如果你想尝试Hard模式的话自己手动编译也是可行的。
这是macOS的编译命令行
```shell
gcc -O2 -shared -undefined dynamic_lookup pb.c -o pb.so
```
Linux的
```shell
gcc -O2 -shared -fPIC pb.c -o pb.so
```
Windows的注意那个Lua_BUILD_AS_DLL在Windows上必须带这个预处理宏编译
```shell
cl /O2 /LD /Fepb.dll /I Lua53\include /DLUA_BUILD_AS_DLL pb.c Lua53\lib\lua53.lib
```
## 样例
```lua
local pb = require "pb"
local protoc = require "protoc"
-- 直接载入schema (这么写只是方便, 生产环境推荐使用 protoc.new() 接口)
assert(protoc:load [[
message Phone {
optional string name = 1;
optional int64 phonenumber = 2;
}
message Person {
optional string name = 1;
optional int32 age = 2;
optional string address = 3;
repeated Phone contacts = 4;
} ]])
-- lua 表数据
local data = {
name = "ilse",
age = 18,
contacts = {
{ name = "alice", phonenumber = 12312341234 },
{ name = "bob", phonenumber = 45645674567 }
}
}
-- 将Lua表编码为二进制数据
local bytes = assert(pb.encode("Person", data))
print(pb.tohex(bytes))
-- 再解码回Lua表
local data2 = assert(pb.decode("Person", bytes))
print(require "serpent".block(data2))
```
## 使用案例
[![零境交错](https://img.tapimg.com/market/images/e59627dc9039ff22ba7d000b5c9fe7f6.jpg?imageView2/2/h/560/q/40/format/jpg/interlace/1/ignore-error/1)](http://djwk.qq.com)
## 接口文档
请注意接口文档有些是`.`有些是`:``.`代表这是静态函数,直接调用即可,`:`代表这是一个方法,需要在一个对象上调用。
### `protoc` 模块
| 接口 | 返回 | 描述 |
| --------------- | ------- | --------------------------------------------- |
| `protoc.new()` | 编译器对象 | 创建一个新的编译器对象 |
| `protoc.reload()` | `true` | 重新载入谷歌标准的schema信息编译需要用到 |
| `p:parse(string[, filename])` | table | 将文本schema信息转换成 `DescriptorProto` 消息的Lua表 |
| `p:compile(string[, filename])` | string | 将文本schema信息转换成二进制.pb文件数据 |
| `p:load(string[, filename])` | `true` | 将文本schema信息转换后调用`pb.load()`载入内存数据库 |
| `p.loaded` | table | 一个包含了所有已载入的 `DescriptorProto` 表的缓存表 |
| `p.unknown_import` | 详情见下 | 处理schema里`import`语句找不到文件的回调 |
| `p.unknown_type` | 详情见下 | 处理schema里未知类型的回调 |
| `p.include_imports` | bool | 编译结果中包含所有`import`的文件 |
要编译一个文本的schema信息首先生成一个编译器实例。一个编译器实例会记住你用它编译的每一个scehma文件从而能够正确处理schema之间的import关系
```lua
local p = protoc.new()
```
生成了编译器实例之后,可以给编译器实例设置一些选项或者回调:
```lua
-- 设置回调……
p.unknown_import = function(self, module_name) ... end
p.unknown_type = function(self, type_name) ... end
-- ……和选项
p.include_imports = true
```
`unknown_import`和`unknown_type`可以被设置成多种类型的值:如果被设置成`true`,则所有不存在的模块或者消息类型会自动生成一个默认值(空模块/空消息)而不会报错。`pb.load()`会自动处理空消息的合并因此这样载入信息也不会出错。如果设置一个字符串值那么这个字符串是一个Lua的正则表达式满足正则表达式的模块或者消息类型会被设置成默认值而不满足的则会报错
```lua
p.unknown_type = "Foo.*"
```
上面的选项意味着所有`Foo`包里的消息会被当作好像已经载入了,即使没找到也不会报错。
如果这些回调被设置成一个函数,这个函数会在找不到模块/消息的时候被调用。调用的时候会传入找不到的模块/消息的名字,你需要返回一个`DescriptorProto`数据表(对模块而言),或者一个类型的名字和这个类型的实际分类,比如说`message`或者`enum`,如下所示:
```lua
function p:unknown_import(name)
-- 如果找不到 "foo.proto" 文件而调用了这个函数,那就自己手动载入 "my_foo.proto" 文件并返回信息
return p:parsefile("my_"..name)
end
function p:unknown_type(name)
-- 如果找不到 "Type" 那就把它当 "MyType" 消息编译好了,注意前面那个“.”,那是包名。
return ".My"..name, "message"
end
```
设置好这些选项或者回调以后,使用`load()`或`compile()`或`parse()`来按照你的需求得到想要的结果。这些函数都需要直接传入scehma的文本内容作为参数可以可选地多传入当前schema对应的文件名用于方便schema之间的`import`指令找到对应的文件,但是除非设置了`include_imports`,否则`import`指令即使找到了对应的文件也不会编译/加载对应文件,只是会检查类型引入是否正确。即使没有设置`include_imports`也可以手动按照拓扑顺序依次load对应文件从而加载所有schema。
### `pb` 模块
`pb`模块提供了编码/解码信息的高级接口、内存schema数据库的载入和读取接口以及其他的一些方便的工具函数。
下面的表格里给出了`pb`模块里所有的函数,注意表格中的中“返回”一栏中的一些特殊返回值,这些返回值有一些约定俗成的含义:
- `type`:这个返回值代表返回的是一个代表类型的字符串。`".Foo"`代表没有包名的proto文件里的Foo消息而`"foo.Foo"`代表写了`package foo;`包名的proto文件里的Foo消息。
- `data`:一个字符串,或者`pb.Slice`对象或者`pb.Buffer`对象,反正是个能表示二进制数据的东西
- `iterator`返回一个能在for in语句里使用的迭代器对象比如说
```lua
for name in pb.types() do
print(name)
end
```
**注意**:只有`pb.load()`通过返回值返回是否出错,你要用`assert(pb.load(...))`去调用这个函数!其他的函数会直接扔一个错误异常,不需要你对返回值调用`assert()`函数。
| 接口 | 返回 | 描述 |
| --------------- | ------- | --------------------------------------------- |
| `pb.clear()` | None | 清除所有类型 |
| `pb.clear(type)` | None | 清除特定类型 |
| `pb.load(data)` | boolean,integer | 将一个二进制schema信息载入内存数据库 |
| `pb.encode(type, table)` | string | 将table按照type消息类型进行编码 |
| `pb.encode(type, table, b)` | buffer | 同上但是编码进额外提供的buffer对象里并返回 |
| `pb.decode(type, data)` | table | 将二进制data按照type消息类型解码为一个表 |
| `pb.decode(type, data, table)` | table | 同上,但是解码到你提供的表里 |
| `pb.pack(fmt, ...)` | string | 同 `buffer.pack()` ,但直接用字符串返回二进制数据 |
| `pb.unpack(data, fmt, ...)` | values... | 同 `slice.unpack()` 但是接受任何二进制类型数据 |
| `pb.types()` | iterator | 遍历内存数据库里所有的消息类型,返回具体信息 |
| `pb.type(type)` | 详情见下 | 返回内存数据库特定消息类型的具体信息 |
| `pb.fields(type)` | iterator | 遍历特定消息里所有的域,返回具体信息 |
| `pb.field(type, string)` | 详情见下 | 返回特定消息里特定域的具体信息 |
| `pb.field(type, number)` | 详情见下 | 返回特定消息里特定域的具体信息 |
| `pb.typefmt(type)` | String | 得到 protobuf 数据类型名对应的 pack/unpack 的格式字符串 |
| `pb.enum(type, string)` | number | 提供特定枚举里的名字,返回枚举数字 |
| `pb.enum(type, number)` | string | 提供特定枚举里的数字,返回枚举名字 |
| `pb.defaults(type[, boolean])` | table | 获得或设置特定消息类型的默认表 |
| `pb.hook(type[, function])` | function | 获得或设置特定消息类型的解码钩子 |
| `pb.option(string)` | string | 设置编码或解码的具体选项 |
| `pb.state()` | `pb.State` | 返回当前的内存数据库 |
| `pb.state(newstate \| nil)` | `pb.State` | 设置或删除当前的内存数据库,返回旧的内存数据库 |
#### 内存数据库载入 Schema 信息
`pb.load()` 接受一个二进制的schema数据并将其载入到内存数据库中。如果载入成功则返回`true`,否则返回`false`,无论成功与否,都会返回读取的二进制数据的字节数。如果载入失败,你可以检查在这个字节位置周围是否有数据错误的情况,比如被`NUL`字符截断等等的问题。
二进制流中是什么样的schema就会载入什么样的schema。通常只能载入一个文件。如果需要同时载入多个文件比如包括import后的文件或者多个不相干文件可以通过在使用`protoc.exe`或者`protoc.lua`编译二进制schema的时候编译多个文件或者使用`include_imports`在二进制数据中包含多个文件的内容实现。注意根据protobuf的特性直接将多个schema二进制数据连接在一起载入也是可行的。
#### 类型映射
| Protobuf 类型 | Lua 类型 |
| ------------------------------------------------- | ----------------------------------------------------------- |
| `double`, `float` | `number` |
| `int32`, `uint32`, `fixed32`, `sfixed32`, `sint32` | `number``integer` Lua 5.3+ |
| `int64`, `uint64`, `fixed64`, `sfixed64`, `sint64` | `number``"#"` 打头的 `string``integer` Lua 5.3+ |
| `bool` | `boolean` |
| `string`, `bytes` | `string` |
| `message` | `table` |
| `enum` | `string``number` |
#### 内存数据库信息获取
可以使用`pb.type()`、`pb.types()`、`pb.field()`、`pb.fields()`系列函数获取内存数据库内的消息类型信息。
内存数据库存储了可以编码/解码的所有的消息类型信息,如果内存数据库中无法查询到对应信息,则编码/解码可能失败。使用*限定后的消息类型*名字就可以获取对应的类型信息。比如`foo` 包里的`Foo`消息的*限定消息类型名字*是`".foo.Foo"`,如果没有包名,则直接在消息名前面加`"."`,比如`".Foo"`就是没有包名的`Foo`消息的限定名称。
通过调用`pb.type()`,你可以获得下面的信息:
- name即限定的消息类型名称如`".package.TypeName"`
- basename即去除了包名的消息类型名称如`"TypeName"`
- type`"map"` | `"enum"` | `"message"`,消息的实际类型——`MapEntry`类型,或者枚举,或者消息。
`pb.types()`返回了一个迭代器,就好像对内存数据库里存储的每个类型调用 `pb.type()`一样:
```lua
-- 打印出 MyType 消息的详细信息
print(pb.type "MyType")
-- 列出内存数据库里存储的所有消息类型的信息
for name, basename, type in pb.types() do
print(name, basename, type)
end
```
`pb.field()` 返回了一个特定的消息里的一个域的详细信息:
- name: 域名
- number: schema中该域的对应数字序号
- type: 域类型
- default value: 域的默认值,没有的话是`nil`
- `"packed"`|`"repeated"`| `"optional"`:域的标签,注意并不支持`required`,会被当作`optional`
- oneof_name域所属的oneof块的名字可选
- , oneof_index域所属的oneof块的索引可选
`pb.fields()` 会返回一个好像对消息类型里的每个域调用`pb.field()`一样的迭代器对象:
```lua
-- 打印 MyType 消息类型里 the_first_field 域的详细信息
print(pb.field("MyType", "the_first_field"))
-- 遍历 MyType 消息类型里所有的域,注意你并不需要写全所有的详细信息(后面的信息会被忽略掉)
for name, number, type in pb.fields "MyType" do
print(name, number, type) -- 只需要打印这三个
end
```
`pb.enum()` 转换枚举的名字和值:
```lua
protoc:load [[
enum Color { Red = 1; Green = 2; Blue = 3 }
]]
print(pb.enum("Color", "Red")) --> 1
print(pb.enum("Color", 2)) --> "Green"
```
其实枚举本身就是一种特殊的“消息类型”,在内存数据库里,枚举和消息其实没什么区别,因此使用`pb.field()`也能做到枚举的名字和值之间的转换,`pb.enum()`这个名字更多的只是语义上的区别,另外因为只返回一个值,可能会相对快一些。
#### 默认值
你可以调用`pb.defaults()` 函数得到对应一个消息类型的一张Lua表这张表存储了该消息类型所有域的默认值。
`pb.defaults()`函数的第一个参数是指定的消息类型名称如果可选的第二个参数为true那么该缓存的默认表会被从内存数据库中清除。
其实通过`pb.decode("Type")`本身就能得到一张填充了默认值的Lua表。这个函数的目的是它提供的表会被内存数据库记住如果你设置了`use_default_metatable`这个选项,那么这个默认值表就会成为对应类型被解码时被自动设置的原表——也就是说,可以支持解码一个空表,但是你能通过元表取得所有域的默认值,示例如下:
```lua
check_load [[
message TestDefault {
optional int32 defaulted_int = 10 [ default = 777 ];
optional bool defaulted_bool = 11 [ default = true ];
optional string defaulted_str = 12 [ default = "foo" ];
optional float defaulted_num = 13 [ default = 0.125 ];
} ]]
print(require "serpent".block(pb.defaults "TestDefault"))
-- output:
-- {
-- defaulted_bool = true,
-- defaulted_int = 777,
-- defaulted_num = 0.125,
-- defaulted_str = "foo"
-- } --[[table: 0x7f8c1e52b050]]
```
#### 钩子
如果通过`pb.option "enable_hooks"`启用了钩子功能,那么你可以通过`pb.hook()`函数为指定的消息类型设置一个解码钩子。一个钩子是一个会在该消息类型所有的域都被读取完毕之后调用的一个函数。你可以在这个时候对这个已经读取完毕的消息表做任何事。比如设置上一节提到的元表。
`pb.hook()`的第一个参数是指定的消息类型名称,第二个参数就是钩子函数了。如果第二个参数是`nil`,则这个类型的钩子函数会被清除;任何情况下,`pb.hook()`函数都会返回之前设置过的钩子函数(或者`nil`)。
钩子函数只有一个参数,即当前已经处理完的消息表。如果你同时还需要消息类型名称,那么可以使用以下工具函数:
```lua
local function make_hook(name, func)
return pb.hook(name, function(t)
return func(name, t)
end)
end
```
#### 选项
你可以通过调用`pb.option()`函数设置选项来改变编码/解码时的行为。
目前支持的选项如下:
| 选项 | 描述 |
| --------------------- | ----------------------------------------------------- |
| `enum_as_name` | 解码枚举的时候,设置值为枚举名 **(默认)** |
| `enum_as_value` | 解码枚举的时候,设置值为枚举值数字 |
| `int64_as_number` | 如果值的大小小于uint32允许的最大值则存储整数否则存储Lua浮点数类型$\le$ Lua 5.2可能会导致不精确或者64位整数类型$\ge$ Lua 5.3这个版本开始才支持64位整数类型 **(默认)** |
| `int64_as_string` | 同上,但返回一个前缀`"#"`的字符串以避免精度损失 |
| `int64_as_hexstring` | 同上但返回一个16进制的字符串 |
| `auto_default_values` | 对于 proto3采取 `use_default_values` 的设置;对于其他 protobuf 格式,则采取 `no_default_values` 的设置 **(默认)** |
| `no_default_values` | 忽略默认值设置 |
| `use_default_values` | 将默认值表复制到解码目标表中来 |
| `use_default_metatable` | 将默认值表作为解码目标表的元表使用 |
| `enable_hooks` | `pb.decode` 启用钩子功能 |
| `disable_hooks` | `pb.decode` 禁用钩子功能 **(默认)** |
| `encode_default_values` | 默认值也参与编码 |
| `no_encode_default_values` | 默认值不参与编码 **(默认)** |
| `decode_default_array` | 配合`no_default_values`选项,对于数组,将空值解码为空表 |
| `no_decode_default_array` | 配合`no_default_values`选项对于数组将空值解码为nil **(默认)** |
| `encode_order` | 保证对相同的schema和data`pb.encode`编码出的结果一致。注意这个选项会损失效率 |
| `no_encode_order` | 不保证对相同输入,`pb.encode`编码出的结果一致。**(默认)** |
| `decode_default_message` | 将空子消息解析成默认值表 |
| `no_decode_default_message` | 将空子消息解析成 `nil` **(default)** |
*注意* `int64_as_string``int64_as_hexstring` 返回的字符串会带一个 `'#'` 字符前缀因为Lua会自动把数字表示的字符串当作数字使用从而导致精度损失。带一个前缀会让Lua认为这个字符串并不是数字从而避免了Lua的自动转换。
本模块中所有接受数字参数的函数都支持使用带`'#'`前缀的字符串用于表示数字,无论是否开启了相关的选项都是如此。如果需要表格中提供的数字,也同样支持使用前缀字符串指定。
#### 多内存数据库
`pb` 模块支持同时存在多个内存数据库,但是你每次只能使用其中的一个。内存数据库仅仅存储所有的类型。默认值表、选项等等不受影响。你可以通过`pb.state()`函数来获得/设置内存数据库。
如果要新建一个内存数据库,调用 `pb.state(nil)` 函数清除当前内存数据库(会在返回值中返回),如果在调用`pb.load()`函数载入消息的类型的时候发现当前没有内存数据库,那么载入器会自动创建一个新的内存数据库。从而支持多个内存数据库同时存在。你可以同样使用`pb.state()` 函数来切换这多个内存数据库。
下面的示例能让你在独立的内存数据库中完成某些操作:
```lua
local old = pb.state(nil) -- 清空当前内存数据库
-- 如果要使用 protoc.lua, 这里还需要调用 protoc.reload() 函数
assert(pb.load(...)) -- 载入新的消息类型信息
-- 开始编码/解码 ...
pb.state(old) -- 恢复旧的内存数据库(并丢弃刚才新建的那个)
```
需要注意的是 `protoc.Lua` 模块会注册一些Google标准消息类型到内存数据库中因此一定要记得在创建新的内存数据库之后调用 `proto.reload()` 函数恢复这些信息。
### `pb.io` 模块
`pb.io` 模块从文件或者 `stdin`/`stdout`中读取或者写入二进制数据。提供这个模块的目的是在Windows下Lua没有二进制读写标准输入输出的能力。然而要实现一个官方的`protoc`插件则必须能够读写二进制的标准输入输出流。因为官方的`protoc`找到插件以后会用插件启动新进程然后把读取编译好的proto文件的内容用二进制的`FileDescriptorSet`消息的格式发给新进程的`stdin`。所以提供了这个插件才可以用纯Lua写官方的插件。
`pb.io.read(filename)` 负责从提供的文件名指定的文件里读取二进制数据,如果不提供文件名,那么就直接从 `stdin` 读取。
`pb.io.write()``pb.io.dump()` 和Lua标准库里的 `io.write()` 是一样的,只是会写二进制数据。前者写`stdout`,而后者写到第一个参数提供的文件名所指定的文件中。
这些函数执行成功的时候都会返回`true`,执行失败的时候会返回 `nil, errmsg`,所以调用的时候记得用`assert()`包住以捕获错误。
| 接口 | 返回 | 描述 |
| --------------- | ------- | --------------------------------------------- |
| `io.read()` | string | 从 `stdin`读取所有二进制数据 |
| `io.read(string)` | string | 从文件中读取所有二进制数据 |
| `io.write(...)` | true | 将二进制数据写入 `stdout` |
| `io.dump(string, ...)` | string | write binary data to file name |
### `pb.conv` 模块
`pb.conv` 能够在Lua和protobuf提供的各种数字类型之间进行转换。如果你要使用底层接口那么这个模块就会很有用。
| Encode Function | Decode Function |
| ---------------------- | ---------------------- |
| `conv.encode_int32()` | `conv.decode_int32()` |
| `conv.encode_uint32()` | `conv.decode_uint32()` |
| `conv.encode_sint32()` | `conv.decode_sint32()` |
| `conv.encode_sint64()` | `conv.decode_sint64()` |
| `conv.encode_float()` | `conv.decode_float()` |
| `conv.encode_double()` | `conv.decode_double()` |
### `pb.slice` 模块
“Slice”是一种类似于“视图”的对象它代表某个二进制数据的一部分。使用`slice.new()`可以创建一个slice视图它会自动关联你传给new函数的那个对象并且在它之上获取一个指针用于读取二进制的底层wireformat信息。
slice对象最重要的方法是`slice:unpack()`,它的第一个参数是一个格式字符串,每个格式字符代表需要解码的一个类型。具体的格式字符下面会用表格的形式给出,这些格式字符也可以使用`pb.typefmt()`函数从protobuf的基础类型的名字转换而来。请注意`pb.buffer`模块的重要方法`buffer:pack()`使用的是同一套格式字符:
| 格式字符 | 描述 |
| ------ | ------------------------------------------------------------ |
| v | 基础类型变长的整数类型1到10个字节`varint` |
| d | 基础类型4 字节数字类型 |
| q | 基础类型8 字节数字类型 |
| s | 基础类型,带长度数据通常是 `string`, `bytes` 或者 `message` 类型的数据 |
| c | fmt之后额外接受一个数字参数 `count`,直接读取接下来的 `count` 个字节 |
| b | 布尔类型:`bool` |
| f | 4 字节浮点数类型:`float` |
| F | 8 字节浮点数类型:`double` |
| i | `varint`表示的32位有符号整数`int32` |
| j | `varint`表示的zig-zad 编码的有符号32位整数`sint32` |
| u | `varint`表示的32位无符号整数`uint32` |
| x | 4 字节的无符号32位整数`fixed32` |
| y | 4 字节的有符号32位整数`sfixed32` |
| I | `varint`表示的64位有符号整数`int64` |
| J | `varint`表示的zig-zad 编码的有符号64位整数`sint64` |
| U | `varint`表示的64位无符号整数`uint64` |
| X | 4 字节的无符号32位整数`fixed32` |
| Y | 4 字节的有符号32位整数`sfixed32` |
slice对象内部会维护“当前读到哪儿”的位置信息。每当使用unpack读取的时候会自动指向下一个待读取的偏移可以使用`#slice` 方法得知“还剩下多少字节的数据没有读”。下面的表给出了操控“当前位置”的“格式字符”——注意,这些格式字符只能用在`unpack`函数里,`pack`是不给用的:
| 格式字符 | 描述 |
| ------ | ------------------------------------------------------------ |
| @ | 返回1开始的当前读取位置偏移以当前视图开始为1 |
| * | fmt参数后提供一个额外参数直接设置偏移为这个参数的值 |
| + | fmt参数后提供一个额外参数设置偏移为加上这个参数以后的值 |
下面是一个“将一个 `varint` 类型的值读取两次”的例子:
```lua
local v1, v2 = s:unpack("v*v", 1)
-- v: 读取一个 varint
-- *: 接受下一个参数这里是1将其设置为当前位置现在读取位置回到一开始了
-- v: 现在,把上一个 varint 再读一遍
```
除了读取位置以外slice还支持“进入”和“退出”视图也就是视图栈的功能。比如说你的协议里是一个消息A套一个消息B再套一个消息C你用`slice:new()`可以得到整个消息A的视图那么在发现消息B出现的时候你可以通过`s:enter()`先读取一个带长度数据这个数据读取后被跳过然后将视图缩小到这个带长度数据内部也就是消息B的内容。同理可以处理消息C。当处理完之后调用`s:leave()`可以回到原视图了,注意在读取带长度数据的时候这个数据就被跳过了,这时回到原视图读取位置正好是带长度数据之后,就可以继续处理后续的消息了。
`s:enter()`还支持接受两个参数i和j直接给出偏移量位置。注意这种情况下`s:leave()`就不会修改读取位置了——因为并没有读取操作。
下面是一个使用底层接口读取一个消息的示例:
```lua
local s = slice.new("<data here>")
local tag = s:unpack "v"
if tag%8 == 2 then -- tag 是 string/bytes 类型?这可能就是个子消息
s:enter() -- 读取这个带长度数据,进入数据本身
-- 现在可以对这个带长度数据做任何事儿了比如说读取一堆的fixed32数据
local t = {}
while #s > 0 do
t[#t+1] = s:unpack "d"
end
s:leave() -- 搞定了?回到上级视图继续读取接下来的数据
end
```
以下是`pb.slice`模块里的所有接口:
| 接口 | 返回 | 描述 |
| --------------- | ------- | --------------------------------------------- |
| `slice.new(data[,i[,j]])` | Slice object | 创建一个新的 slice 对象 |
| `s:delete()` | none | 和 `s:reset()`相同重置并释放slice对象引用的内存 |
| `tostring(s)` | string | 返回slice的字符串表示信息 |
| `#s` | number | 得到当前视图还未读取的字节数 |
| `s:result([i[, j]])` | String | 得到当前视图的二进制数据 |
| `s:reset([data[,i[,j]]])` | self | 将slice对象重置绑定另一个数据源 |
| `s:level()` | number | 返回当前视图栈的深度 |
| `s:level(number)` | p, i, j | 返回第n层视图栈的信息读取位置、视图偏移 |
| `s:enter()` | self | 读取一个带长度数据,并将其视图推入视图栈 |
| `s:enter(i[, j])` | self | 将[i,j]字节范围的数据推入视图栈 |
| `s:leave([number])` | self, n | 离开n层的视图栈默认离开一层返回当前视图栈深度 |
| `s:unpack(fmt, ...)` | values... | 利用fmt和额外参数读取当前视图内的信息 |
### `pb.buffer` 模块
Buffer模块本质上其实就是一个内存缓存类似Java的“StringBuilder”的东西。他是一个构建wireformat数据的底层接口。使用`buffer:pack()`可以向缓存里新增数据,使用`buffer:result()`可以得到编码后的二进制数据结构。或者使用`buffer:tohex()`获取人类可读的16进制编码字符串。
`buffer.pack()` 使用和 `slice.unpack()`相同的格式字符,请参见`pb.slice`的文档获取详细的格式字符的表格。除此以外pack还支持 `'()'`格式字符,用于支持编码嵌套的带长度数据——也就是嵌套消息的结构。括号格式字符是可以嵌套的。下面是一个例子:
```lua
b:pack("(vvv)", 1, 2, 3) -- 获得一个编码了3个varint的带长度数据
```
`buffer.pack()` 也支持 '#' 格式字符:该格式字符会额外读取一个长度参数`oldlen`,并将`buffer`从`oldlen`开始到结束的所有字节数据重新编码为一个带长度数据:
```lua
b:pack("#", 5) -- 将b里从第五个字节开始的所有数据编码为一个带长度数据类型数据
```
这个功能可以用来更方便地编码长度不清楚的嵌套子消息先读取一个当前长度然后直接将子消息编码进buffer一旦编码结束就编码一个`"#"`格式,这样之前编码的所有信息就自动成为了一个带长度数据——即合法的子消息类型数据。
下面是 `pb.buffer` 模块里所有的接口:
| 接口 | 返回 | 描述 |
| --------------- | ------- | --------------------------------------------- |
| `buffer.new([...])` | Buffer object | 创建一个新的buffer对象额外参数会传递给`b:reset(...)`函数 |
| `b:delete()` | none | 即`b:reset()`释放buffer使用的内存 |
| `tostring(b)` | string | 返回buffer的字符串表示信息 |
| `#b` | number | 返回buffer中已经完成编码的字节数 |
| `b:reset()` | self | 清空buffer中的所有数据 |
| `b:reset([...])` | self | 清空buffer并将其数据设置为所有的参数如同`io.write()`一样处理其参数 |
| `b:tohex([i[, j]])` | string | 返回可选范围默认是全部的数据的16进制表示 |
| `b:result([i[,j]])` | string | 返回编码后二进制数据。允许只返回一部分。默认返回全部 |
| `b:pack(fmt, ...)` | self | 利用fmt和额外参数将参数里提供的数据编码到buffer中 |
---

View file

@ -0,0 +1,151 @@
--lualoader, R"EOF(--
return
"\10\179;\10\16descriptor.proto\18\15google.protobuf\"M\10\17FileDescrip"..
"torSet\0188\10\4file\24\1 \3(\0112$.google.protobuf.FileDescriptorProto"..
"R\4file\"\228\4\10\19FileDescriptorProto\18\18\10\4name\24\1 \1(\9R\4na"..
"me\18\24\10\7package\24\2 \1(\9R\7package\18\30\10\10dependency\24\3 \3"..
"(\9R\10dependency\18+\10\17public_dependency\24\10 \3(\5R\16publicDepen"..
"dency\18'\10\15weak_dependency\24\11 \3(\5R\14weakDependency\18C\10\12m"..
"essage_type\24\4 \3(\0112 .google.protobuf.DescriptorProtoR\11messageTy"..
"pe\18A\10\9enum_type\24\5 \3(\0112$.google.protobuf.EnumDescriptorProto"..
"R\8enumType\18A\10\7service\24\6 \3(\0112'.google.protobuf.ServiceDescr"..
"iptorProtoR\7service\18C\10\9extension\24\7 \3(\0112%.google.protobuf.F"..
"ieldDescriptorProtoR\9extension\0186\10\7options\24\8 \1(\0112\28.googl"..
"e.protobuf.FileOptionsR\7options\18I\10\16source_code_info\24\9 \1(\011"..
"2\31.google.protobuf.SourceCodeInfoR\14sourceCodeInfo\18\22\10\6syntax"..
"\24\12 \1(\9R\6syntax\"\185\6\10\15DescriptorProto\18\18\10\4name\24\1 "..
"\1(\9R\4name\18;\10\5field\24\2 \3(\0112%.google.protobuf.FieldDescript"..
"orProtoR\5field\18C\10\9extension\24\6 \3(\0112%.google.protobuf.FieldD"..
"escriptorProtoR\9extension\18A\10\11nested_type\24\3 \3(\0112 .google.p"..
"rotobuf.DescriptorProtoR\10nestedType\18A\10\9enum_type\24\4 \3(\0112$."..
"google.protobuf.EnumDescriptorProtoR\8enumType\18X\10\15extension_range"..
"\24\5 \3(\0112/.google.protobuf.DescriptorProto.ExtensionRangeR\14exten"..
"sionRange\18D\10\10oneof_decl\24\8 \3(\0112%.google.protobuf.OneofDescr"..
"iptorProtoR\9oneofDecl\0189\10\7options\24\7 \1(\0112\31.google.protobu"..
"f.MessageOptionsR\7options\18U\10\14reserved_range\24\9 \3(\0112..googl"..
"e.protobuf.DescriptorProto.ReservedRangeR\13reservedRange\18#\10\13rese"..
"rved_name\24\10 \3(\9R\12reservedName\26z\10\14ExtensionRange\18\20\10"..
"\5start\24\1 \1(\5R\5start\18\16\10\3end\24\2 \1(\5R\3end\18@\10\7optio"..
"ns\24\3 \1(\0112&.google.protobuf.ExtensionRangeOptionsR\7options\0267"..
"\10\13ReservedRange\18\20\10\5start\24\1 \1(\5R\5start\18\16\10\3end\24"..
"\2 \1(\5R\3end\"|\10\21ExtensionRangeOptions\18X\10\20uninterpreted_opt"..
"ion\24\231\7 \3(\0112$.google.protobuf.UninterpretedOptionR\19uninterpr"..
"etedOption*\9\8\232\7\16\128\128\128\128\2\"\193\6\10\20FieldDescriptor"..
"Proto\18\18\10\4name\24\1 \1(\9R\4name\18\22\10\6number\24\3 \1(\5R\6nu"..
"mber\18A\10\5label\24\4 \1(\0142+.google.protobuf.FieldDescriptorProto."..
"LabelR\5label\18>\10\4type\24\5 \1(\0142*.google.protobuf.FieldDescript"..
"orProto.TypeR\4type\18\27\10\9type_name\24\6 \1(\9R\8typeName\18\26\10"..
"\8extendee\24\2 \1(\9R\8extendee\18#\10\13default_value\24\7 \1(\9R\12d"..
"efaultValue\18\31\10\11oneof_index\24\9 \1(\5R\10oneofIndex\18\27\10\9j"..
"son_name\24\10 \1(\9R\8jsonName\0187\10\7options\24\8 \1(\0112\29.googl"..
"e.protobuf.FieldOptionsR\7options\18'\10\15proto3_optional\24\17 \1(\8R"..
"\14proto3Optional\"\182\2\10\4Type\18\15\10\11TYPE_DOUBLE\16\1\18\14\10"..
"\10TYPE_FLOAT\16\2\18\14\10\10TYPE_INT64\16\3\18\15\10\11TYPE_UINT64\16"..
"\4\18\14\10\10TYPE_INT32\16\5\18\16\10\12TYPE_FIXED64\16\6\18\16\10\12T"..
"YPE_FIXED32\16\7\18\13\10\9TYPE_BOOL\16\8\18\15\10\11TYPE_STRING\16\9"..
"\18\14\10\10TYPE_GROUP\16\10\18\16\10\12TYPE_MESSAGE\16\11\18\14\10\10T"..
"YPE_BYTES\16\12\18\15\10\11TYPE_UINT32\16\13\18\13\10\9TYPE_ENUM\16\14"..
"\18\17\10\13TYPE_SFIXED32\16\15\18\17\10\13TYPE_SFIXED64\16\16\18\15\10"..
"\11TYPE_SINT32\16\17\18\15\10\11TYPE_SINT64\16\18\"C\10\5Label\18\18\10"..
"\14LABEL_OPTIONAL\16\1\18\18\10\14LABEL_REQUIRED\16\2\18\18\10\14LABEL_"..
"REPEATED\16\3\"c\10\20OneofDescriptorProto\18\18\10\4name\24\1 \1(\9R\4"..
"name\0187\10\7options\24\2 \1(\0112\29.google.protobuf.OneofOptionsR\7o"..
"ptions\"\227\2\10\19EnumDescriptorProto\18\18\10\4name\24\1 \1(\9R\4nam"..
"e\18?\10\5value\24\2 \3(\0112).google.protobuf.EnumValueDescriptorProto"..
"R\5value\0186\10\7options\24\3 \1(\0112\28.google.protobuf.EnumOptionsR"..
"\7options\18]\10\14reserved_range\24\4 \3(\01126.google.protobuf.EnumDe"..
"scriptorProto.EnumReservedRangeR\13reservedRange\18#\10\13reserved_name"..
"\24\5 \3(\9R\12reservedName\26;\10\17EnumReservedRange\18\20\10\5start"..
"\24\1 \1(\5R\5start\18\16\10\3end\24\2 \1(\5R\3end\"\131\1\10\24EnumVal"..
"ueDescriptorProto\18\18\10\4name\24\1 \1(\9R\4name\18\22\10\6number\24"..
"\2 \1(\5R\6number\18;\10\7options\24\3 \1(\0112!.google.protobuf.EnumVa"..
"lueOptionsR\7options\"\167\1\10\22ServiceDescriptorProto\18\18\10\4name"..
"\24\1 \1(\9R\4name\18>\10\6method\24\2 \3(\0112&.google.protobuf.Method"..
"DescriptorProtoR\6method\0189\10\7options\24\3 \1(\0112\31.google.proto"..
"buf.ServiceOptionsR\7options\"\137\2\10\21MethodDescriptorProto\18\18"..
"\10\4name\24\1 \1(\9R\4name\18\29\10\10input_type\24\2 \1(\9R\9inputTyp"..
"e\18\31\10\11output_type\24\3 \1(\9R\10outputType\0188\10\7options\24\4"..
" \1(\0112\30.google.protobuf.MethodOptionsR\7options\0180\10\16client_s"..
"treaming\24\5 \1(\8:\5falseR\15clientStreaming\0180\10\16server_streami"..
"ng\24\6 \1(\8:\5falseR\15serverStreaming\"\145\9\10\11FileOptions\18!"..
"\10\12java_package\24\1 \1(\9R\11javaPackage\0180\10\20java_outer_class"..
"name\24\8 \1(\9R\18javaOuterClassname\0185\10\19java_multiple_files\24"..
"\10 \1(\8:\5falseR\17javaMultipleFiles\18D\10\29java_generate_equals_an"..
"d_hash\24\20 \1(\8B\2\24\1R\25javaGenerateEqualsAndHash\18:\10\22java_s"..
"tring_check_utf8\24\27 \1(\8:\5falseR\19javaStringCheckUtf8\18S\10\12op"..
"timize_for\24\9 \1(\0142).google.protobuf.FileOptions.OptimizeMode:\5SP"..
"EEDR\11optimizeFor\18\29\10\10go_package\24\11 \1(\9R\9goPackage\0185"..
"\10\19cc_generic_services\24\16 \1(\8:\5falseR\17ccGenericServices\0189"..
"\10\21java_generic_services\24\17 \1(\8:\5falseR\19javaGenericServices"..
"\0185\10\19py_generic_services\24\18 \1(\8:\5falseR\17pyGenericServices"..
"\0187\10\20php_generic_services\24* \1(\8:\5falseR\18phpGenericServices"..
"\18%\10\10deprecated\24\23 \1(\8:\5falseR\10deprecated\18.\10\16cc_enab"..
"le_arenas\24\31 \1(\8:\4trueR\14ccEnableArenas\18*\10\17objc_class_pref"..
"ix\24$ \1(\9R\15objcClassPrefix\18)\10\16csharp_namespace\24% \1(\9R\15"..
"csharpNamespace\18!\10\12swift_prefix\24' \1(\9R\11swiftPrefix\18(\10"..
"\16php_class_prefix\24( \1(\9R\14phpClassPrefix\18#\10\13php_namespace"..
"\24) \1(\9R\12phpNamespace\0184\10\22php_metadata_namespace\24, \1(\9R"..
"\20phpMetadataNamespace\18!\10\12ruby_package\24- \1(\9R\11rubyPackage"..
"\18X\10\20uninterpreted_option\24\231\7 \3(\0112$.google.protobuf.Unint"..
"erpretedOptionR\19uninterpretedOption\":\10\12OptimizeMode\18\9\10\5SPE"..
"ED\16\1\18\13\10\9CODE_SIZE\16\2\18\16\10\12LITE_RUNTIME\16\3*\9\8\232"..
"\7\16\128\128\128\128\2J\4\8&\16'\"\227\2\10\14MessageOptions\18<\10\23"..
"message_set_wire_format\24\1 \1(\8:\5falseR\20messageSetWireFormat\18L"..
"\10\31no_standard_descriptor_accessor\24\2 \1(\8:\5falseR\28noStandardD"..
"escriptorAccessor\18%\10\10deprecated\24\3 \1(\8:\5falseR\10deprecated"..
"\18\27\10\9map_entry\24\7 \1(\8R\8mapEntry\18X\10\20uninterpreted_optio"..
"n\24\231\7 \3(\0112$.google.protobuf.UninterpretedOptionR\19uninterpret"..
"edOption*\9\8\232\7\16\128\128\128\128\2J\4\8\4\16\5J\4\8\5\16\6J\4\8\6"..
"\16\7J\4\8\8\16\9J\4\8\9\16\10\"\226\3\10\12FieldOptions\18A\10\5ctype"..
"\24\1 \1(\0142#.google.protobuf.FieldOptions.CType:\6STRINGR\5ctype\18"..
"\22\10\6packed\24\2 \1(\8R\6packed\18G\10\6jstype\24\6 \1(\0142$.google"..
".protobuf.FieldOptions.JSType:\9JS_NORMALR\6jstype\18\25\10\4lazy\24\5 "..
"\1(\8:\5falseR\4lazy\18%\10\10deprecated\24\3 \1(\8:\5falseR\10deprecat"..
"ed\18\25\10\4weak\24\10 \1(\8:\5falseR\4weak\18X\10\20uninterpreted_opt"..
"ion\24\231\7 \3(\0112$.google.protobuf.UninterpretedOptionR\19uninterpr"..
"etedOption\"/\10\5CType\18\10\10\6STRING\16\0\18\8\10\4CORD\16\1\18\16"..
"\10\12STRING_PIECE\16\2\"5\10\6JSType\18\13\10\9JS_NORMAL\16\0\18\13\10"..
"\9JS_STRING\16\1\18\13\10\9JS_NUMBER\16\2*\9\8\232\7\16\128\128\128\128"..
"\2J\4\8\4\16\5\"s\10\12OneofOptions\18X\10\20uninterpreted_option\24"..
"\231\7 \3(\0112$.google.protobuf.UninterpretedOptionR\19uninterpretedOp"..
"tion*\9\8\232\7\16\128\128\128\128\2\"\192\1\10\11EnumOptions\18\31\10"..
"\11allow_alias\24\2 \1(\8R\10allowAlias\18%\10\10deprecated\24\3 \1(\8:"..
"\5falseR\10deprecated\18X\10\20uninterpreted_option\24\231\7 \3(\0112$."..
"google.protobuf.UninterpretedOptionR\19uninterpretedOption*\9\8\232\7"..
"\16\128\128\128\128\2J\4\8\5\16\6\"\158\1\10\16EnumValueOptions\18%\10"..
"\10deprecated\24\1 \1(\8:\5falseR\10deprecated\18X\10\20uninterpreted_o"..
"ption\24\231\7 \3(\0112$.google.protobuf.UninterpretedOptionR\19uninter"..
"pretedOption*\9\8\232\7\16\128\128\128\128\2\"\156\1\10\14ServiceOption"..
"s\18%\10\10deprecated\24! \1(\8:\5falseR\10deprecated\18X\10\20uninterp"..
"reted_option\24\231\7 \3(\0112$.google.protobuf.UninterpretedOptionR\19"..
"uninterpretedOption*\9\8\232\7\16\128\128\128\128\2\"\224\2\10\13Method"..
"Options\18%\10\10deprecated\24! \1(\8:\5falseR\10deprecated\18q\10\17id"..
"empotency_level\24\" \1(\0142/.google.protobuf.MethodOptions.Idempotenc"..
"yLevel:\19IDEMPOTENCY_UNKNOWNR\16idempotencyLevel\18X\10\20uninterprete"..
"d_option\24\231\7 \3(\0112$.google.protobuf.UninterpretedOptionR\19unin"..
"terpretedOption\"P\10\16IdempotencyLevel\18\23\10\19IDEMPOTENCY_UNKNOWN"..
"\16\0\18\19\10\15NO_SIDE_EFFECTS\16\1\18\14\10\10IDEMPOTENT\16\2*\9\8"..
"\232\7\16\128\128\128\128\2\"\154\3\10\19UninterpretedOption\18A\10\4na"..
"me\24\2 \3(\0112-.google.protobuf.UninterpretedOption.NamePartR\4name"..
"\18)\10\16identifier_value\24\3 \1(\9R\15identifierValue\18,\10\18posit"..
"ive_int_value\24\4 \1(\4R\16positiveIntValue\18,\10\18negative_int_valu"..
"e\24\5 \1(\3R\16negativeIntValue\18!\10\12double_value\24\6 \1(\1R\11do"..
"ubleValue\18!\10\12string_value\24\7 \1(\12R\11stringValue\18'\10\15agg"..
"regate_value\24\8 \1(\9R\14aggregateValue\26J\10\8NamePart\18\27\10\9na"..
"me_part\24\1 \2(\9R\8namePart\18!\10\12is_extension\24\2 \2(\8R\11isExt"..
"ension\"\167\2\10\14SourceCodeInfo\18D\10\8location\24\1 \3(\0112(.goog"..
"le.protobuf.SourceCodeInfo.LocationR\8location\26\206\1\10\8Location\18"..
"\22\10\4path\24\1 \3(\5B\2\16\1R\4path\18\22\10\4span\24\2 \3(\5B\2\16"..
"\1R\4span\18)\10\16leading_comments\24\3 \1(\9R\15leadingComments\18+"..
"\10\17trailing_comments\24\4 \1(\9R\16trailingComments\18:\10\25leading"..
"_detached_comments\24\6 \3(\9R\23leadingDetachedComments\"\209\1\10\17G"..
"eneratedCodeInfo\18M\10\10annotation\24\1 \3(\0112-.google.protobuf.Gen"..
"eratedCodeInfo.AnnotationR\10annotation\26m\10\10Annotation\18\22\10\4p"..
"ath\24\1 \3(\5B\2\16\1R\4path\18\31\10\11source_file\24\2 \1(\9R\10sour"..
"ceFile\18\20\10\5begin\24\3 \1(\5R\5begin\18\16\10\3end\24\4 \1(\5R\3en"..
"dB~\10\19com.google.protobufB\16DescriptorProtosH\1Z-google.golang.org/"..
"protobuf/types/descriptorpb\248\1\1\162\2\3GPB\170\2\26Google.Protobuf."..
"Reflection"
-- )EOF"

297
third_party/lua-protobuf/lexer.lua vendored Normal file
View file

@ -0,0 +1,297 @@
--lualoader, R"EOF(--
local string = string
local tonumber = tonumber
local setmetatable = setmetatable
local error = error
local ipairs = ipairs
local io = io
local table = table
local math = math
local assert = assert
local tostring = tostring
local type = type
local insert_tab = table.insert
local function meta(name, t)
t = t or {}
t.__name = name
t.__index = t
return t
end
local function default(t, k, def)
local v = t[k]
if not v then
v = def or {}
t[k] = v
end
return v
end
local Lexer = meta "Lexer" do
local escape = {
a = "\a", b = "\b", f = "\f", n = "\n",
r = "\r", t = "\t", v = "\v"
}
local function tohex(x) return string.byte(tonumber(x, 16)) end
local function todec(x) return string.byte(tonumber(x, 10)) end
local function toesc(x) return escape[x] or x end
function Lexer.new(name, src)
local self = {
name = name,
src = src,
pos = 1
}
return setmetatable(self, Lexer)
end
function Lexer:__call(patt, pos)
return self.src:match(patt, pos or self.pos)
end
function Lexer:test(patt)
self:whitespace()
local pos = self('^'..patt..'%s*()')
if not pos then return false end
self.pos = pos
return true
end
function Lexer:expected(patt, name)
if not self:test(patt) then
return self:error((name or ("'"..patt.."'")).." expected")
end
return self
end
function Lexer:pos2loc(pos)
local linenr = 1
pos = pos or self.pos
for start, stop in self.src:gmatch "()[^\n]*()\n?" do
if start <= pos and pos <= stop then
return linenr, pos - start + 1
end
linenr = linenr + 1
end
end
function Lexer:error(fmt, ...)
local ln, co = self:pos2loc()
return error(("%s:%d:%d: "..fmt):format(self.name, ln, co, ...))
end
function Lexer:opterror(opt, msg)
if not opt then return self:error(msg) end
return nil
end
function Lexer:whitespace()
local pos, c = self "^%s*()(%/?)"
self.pos = pos
if c == '' then return self end
return self:comment()
end
function Lexer:comment()
local pos = self "^%/%/[^\n]*\n?()"
if not pos then
if self "^%/%*" then
pos = self "^%/%*.-%*%/()"
if not pos then
self:error "unfinished comment"
end
end
end
if not pos then return self end
self.pos = pos
return self:whitespace()
end
function Lexer:line_end(opt)
self:whitespace()
local pos = self '^[%s;]*%s*()'
if not pos then
return self:opterror(opt, "';' expected")
end
self.pos = pos
return pos
end
function Lexer:eof()
self:whitespace()
return self.pos > #self.src
end
function Lexer:keyword(kw, opt)
self:whitespace()
local ident, pos = self "^([%a_][%w_]*)%s*()"
if not ident or ident ~= kw then
return self:opterror(opt, "''"..kw..'" expected')
end
self.pos = pos
return kw
end
function Lexer:ident(name, opt)
self:whitespace()
local b, ident, pos = self "^()([%a_][%w_]*)%s*()"
if not ident then
return self:opterror(opt, (name or 'name')..' expected')
end
self.pos = pos
return ident, b
end
function Lexer:full_ident(name, opt)
self:whitespace()
local b, ident, pos = self "^()([%a_][%w_.]*)%s*()"
if not ident or ident:match "%.%.+" then
return self:opterror(opt, (name or 'name')..' expected')
end
self.pos = pos
return ident, b
end
function Lexer:integer(opt)
self:whitespace()
local ns, oct, hex, s, pos =
self "^([+-]?)(0?)([xX]?)([0-9a-fA-F]+)%s*()"
local n
if oct == '0' and hex == '' then
n = tonumber(s, 8)
elseif oct == '' and hex == '' then
n = tonumber(s, 10)
elseif oct == '0' and hex ~= '' then
n = tonumber(s, 16)
end
if not n then
return self:opterror(opt, 'integer expected')
end
self.pos = pos
return ns == '-' and -n or n
end
function Lexer:number(opt)
self:whitespace()
if self:test "nan%f[%A]" then
return 0.0/0.0
elseif self:test "inf%f[%A]" then
return 1.0/0.0
end
local ns, d1, s, d2, s2, pos = self "^([+-]?)(%.?)([0-9]+)(%.?)([0-9]*)()"
if not ns then
return self:opterror(opt, 'floating-point number expected')
end
local es, pos2 = self("(^[eE][+-]?[0-9]+)%s*()", pos)
if d1 == "." and d2 == "." then
return self:error "malformed floating-point number"
end
self.pos = pos2 or pos
local n = tonumber(d1..s..d2..s2..(es or ""))
return ns == '-' and -n or n
end
function Lexer:quote(opt)
self:whitespace()
local q, start = self '^(["\'])()'
if not start then
return self:opterror(opt, 'string expected')
end
self.pos = start
local patt = '()(\\?'..q..')%s*()'
while true do
local stop, s, pos = self(patt)
if not stop then
self.pos = start-1
return self:error "unfinished string"
end
self.pos = pos
if s == q then
return self.src:sub(start, stop-1)
:gsub("\\x(%x+)", tohex)
:gsub("\\(%d+)", todec)
:gsub("\\(.)", toesc)
end
end
end
function Lexer:structure(opt)
self:whitespace()
if not self:test "{" then
return self:opterror(opt, 'opening curly brace expected')
end
local t = {}
while not self:test "}" do
local pos, name, npos = self "^%s*()(%b[])()"
if not pos then
name = self:full_ident "field name"
else
self.pos = npos
end
self:test ":"
local value = self:constant()
self:test ","
self:line_end "opt"
t[name] = value
end
return t
end
function Lexer:array(opt)
self:whitespace()
if not self:test "%[" then
return self:opterror(opt, 'opening square bracket expected')
end
local t = {}
while not self:test "]" do
local value = self:constant()
self:test ","
t[#t + 1] = value
end
return t
end
function Lexer:constant(opt)
local c = self:full_ident('constant', 'opt') or
self:number('opt') or
self:quote('opt') or
self:structure('opt') or
self:array('opt')
if not c and not opt then
return self:error "constant expected"
end
return c
end
function Lexer:option_name()
local ident
if self:test "%(" then
ident = self:full_ident "option name"
self:expected "%)"
else
ident = self:ident "option name"
end
while self:test "%." do
ident = ident .. "." .. self:ident()
end
return ident
end
function Lexer:type_name()
if self:test "%." then
local id, pos = self:full_ident "type name"
return "."..id, pos
else
return self:full_ident "type name"
end
end
end
return Lexer
-- )EOF"

2971
third_party/lua-protobuf/luaunit.lua vendored Normal file

File diff suppressed because it is too large Load diff

2125
third_party/lua-protobuf/pb.c vendored Normal file

File diff suppressed because it is too large Load diff

1771
third_party/lua-protobuf/pb.h vendored Normal file

File diff suppressed because it is too large Load diff

341
third_party/lua-protobuf/protoc.lua vendored Normal file
View file

@ -0,0 +1,341 @@
--lualoader, R"EOF(--
local string = string
local tonumber = tonumber
local setmetatable = setmetatable
local error = error
local ipairs = ipairs
local io = io
local table = table
local math = math
local assert = assert
local tostring = tostring
local type = type
local insert_tab = table.insert
local function meta(name, t)
t = t or {}
t.__name = name
t.__index = t
return t
end
local function default(t, k, def)
local v = t[k]
if not v then
v = def or {}
t[k] = v
end
return v
end
local Lexer = require 'pb.Lexer'
local Parser = meta "Parser" do
Parser.typemap = {}
Parser.loaded = {}
Parser.paths = { "", "." }
function Parser.new()
local self = {}
self.typemap = {}
self.loaded = {}
self.paths = { "", "." }
return setmetatable(self, Parser)
end
function Parser:reset()
self.typemap = {}
self.loaded = {}
return self
end
function Parser:error(msg)
return self.lex:error(msg)
end
function Parser:addpath(path)
insert_tab(self.paths, path)
end
function Parser:parsefile(name)
local info = self.loaded[name]
if info then return info end
local errors = {}
for _, path in ipairs(self.paths) do
local fn = path ~= "" and path.."/"..name or name
local fh, err = io.open(fn)
if fh then
local content = fh:read "*a"
info = self:parse(content, name)
fh:close()
return info
end
insert_tab(errors, err or fn..": ".."unknown error")
end
local import_fallback = self.unknown_import
if import_fallback == true then
info = import_fallback
elseif import_fallback then
info = import_fallback(self, name)
end
if not info then
error("module load error: "..name.."\n\t"..table.concat(errors, "\n\t"))
end
return info
end
-- parser
local toplevel = require 'pb.TopLevel'
local function make_context(self, lex)
local ctx = {
syntax = "proto2";
locmap = {};
prefix = ".";
lex = lex;
}
ctx.loaded = self.loaded
ctx.typemap = self.typemap
ctx.paths = self.paths
ctx.proto3_optional =
self.proto3_optional or self.experimental_allow_proto3_optional
ctx.unknown_type = self.unknown_type
ctx.unknown_import = self.unknown_import
ctx.on_import = self.on_import
return setmetatable(ctx, Parser)
end
function Parser:parse(src, name)
local loaded = self.loaded[name]
if loaded then
if loaded == true then
error("loop loaded: "..name)
end
return loaded
end
name = name or "<input>"
self.loaded[name] = true
local lex = Lexer.new(name, src)
local ctx = make_context(self, lex)
local info = { name = lex.name, syntax = ctx.syntax }
local syntax = lex:keyword('syntax', 'opt')
if syntax then
info.syntax = lex:expected '=' :quote()
ctx.syntax = info.syntax
lex:line_end()
end
while not lex:eof() do
local ident = lex:ident()
local top_parser = toplevel[ident]
if top_parser then
top_parser(ctx, lex, info)
else
lex:error("unknown keyword '"..ident.."'")
end
lex:line_end "opt"
end
self.loaded[name] = name ~= "<input>" and info or nil
return ctx:resolve(lex, info)
end
-- resolver
local function empty() end
local function iter(t, k)
local v = t[k]
if v then return ipairs(v) end
return empty
end
local function check_dup(self, lex, typ, map, k, v)
local old = map[v[k]]
if old then
local ln, co = lex:pos2loc(self.locmap[old])
lex:error("%s '%s' exists, previous at %d:%d",
typ, v[k], ln, co)
end
map[v[k]] = v
end
local function check_type(self, lex, tname)
if tname:match "^%." then
local t = self.typemap[tname]
if not t then
return lex:error("unknown type '%s'", tname)
end
return t, tname
end
local prefix = self.prefix
for i = #prefix+1, 1, -1 do
local op = prefix[i]
prefix[i] = tname
local tn = table.concat(prefix, ".", 1, i)
prefix[i] = op
local t = self.typemap[tn]
if t then return t, tn end
end
local tn, t
local type_fallback = self.unknown_type
if type_fallback then
if type_fallback == true then
tn = true
elseif type(type_fallback) == 'string' then
tn = tname:match(type_fallback) and true
else
tn = type_fallback(self, tname)
end
end
if tn then
t = types[t or "message"]
if tn == true then tn = "."..tname end
return t, tn
end
return lex:error("unknown type '%s'", tname)
end
local function check_field(self, lex, info)
if info.extendee then
local t, tn = check_type(self, lex, info.extendee)
if t ~= types.message then
lex:error("message type expected in extension")
end
info.extendee = tn
end
if info.type_name then
local t, tn = check_type(self, lex, info.type_name)
info.type = t
info.type_name = tn
end
end
local function check_enum(self, lex, info)
local names, numbers = {}, {}
for _, v in iter(info, 'value') do
lex.pos = assert(self.locmap[v])
check_dup(self, lex, 'enum name', names, 'name', v)
if not (info.options and info.options.allow_alias) then
check_dup(self, lex, 'enum number', numbers, 'number', v)
end
end
end
local function check_message(self, lex, info)
insert_tab(self.prefix, info.name)
local names, numbers = {}, {}
for _, v in iter(info, 'field') do
lex.pos = assert(self.locmap[v])
check_dup(self, lex, 'field name', names, 'name', v)
check_dup(self, lex, 'field number', numbers, 'number', v)
check_field(self, lex, v)
end
for _, v in iter(info, 'nested_type') do
check_message(self, lex, v)
end
for _, v in iter(info, 'extension') do
lex.pos = assert(self.locmap[v])
check_field(self, lex, v)
end
self.prefix[#self.prefix] = nil
end
local function check_service(self, lex, info)
local names = {}
for _, v in iter(info, 'method') do
lex.pos = self.locmap[v]
check_dup(self, lex, 'rpc name', names, 'name', v)
local t, tn = check_type(self, lex, v.input_type)
v.input_type = tn
if t ~= types.message then
lex:error "message type expected in parameter"
end
t, tn = check_type(self, lex, v.output_type)
v.output_type = tn
if t ~= types.message then
lex:error "message type expected in return"
end
end
end
function Parser:resolve(lex, info)
self.prefix = { "", info.package }
for _, v in iter(info, 'message_type') do
check_message(self, lex, v)
end
for _, v in iter(info, 'enum_type') do
check_enum(self, lex, v)
end
for _, v in iter(info, 'service') do
check_service(self, lex, v)
end
for _, v in iter(info, 'extension') do
lex.pos = assert(self.locmap[v])
check_field(self, lex, v)
end
self.prefix = nil
return info
end
end
local has_pb, pb = pcall(require, "pb") do
if has_pb then
function Parser.reload()
assert(pb.load(require 'pb.Descriptor'), "load descriptor msg failed")
end
local function do_compile(self, f, ...)
if self.include_imports then
local old = self.on_import
local infos = {}
function self.on_import(info)
insert_tab(infos, info)
end
local r = f(...)
insert_tab(infos, r)
self.on_import = old
return { file = infos }
end
return { file = { f(...) } }
end
function Parser:compile(s, name)
if self == Parser then self = Parser.new() end
local set = do_compile(self, self.parse, self, s, name)
return pb.encode('.google.protobuf.FileDescriptorSet', set)
end
function Parser:compilefile(fn)
if self == Parser then self = Parser.new() end
local set = do_compile(self, self.parsefile, self, fn)
return pb.encode('.google.protobuf.FileDescriptorSet', set)
end
function Parser:load(s, name)
if self == Parser then self = Parser.new() end
local ret, pos = pb.load(self:compile(s, name))
if ret then return ret, pos end
error("load failed at offset "..pos)
end
function Parser:loadfile(fn)
if self == Parser then self = Parser.new() end
local ret, pos = pb.load(self:compilefile(fn))
if ret then return ret, pos end
error("load failed at offset "..pos)
end
Parser.reload()
end
end
return Parser
-- )EOF"

View file

@ -0,0 +1,24 @@
package = "lua-protobuf"
version = "0.4.0-1"
source = {
url = "git+https://github.com/starwing/lua-protobuf.git",
tag = "0.4.0"
}
description = {
summary = "protobuf data support for Lua",
detailed = [[
This project offers a simple C library for basic protobuf wire format encode/decode.
]],
homepage = "https://github.com/starwing/lua-protobuf",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
pb = "pb.c",
protoc = "protoc.lua"
}
}

View file

@ -0,0 +1,27 @@
package = "lua-protobuf"
version = "scm-1"
source = {
url = "git+https://github.com/starwing/lua-protobuf.git",
}
description = {
summary = "protobuf data support for Lua",
detailed = [[
This project offers a simple C library for basic protobuf wire format encode/decode.
]],
homepage = "https://github.com/starwing/lua-protobuf",
license = "MIT",
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
pb = "pb.c";
protoc = "protoc.lua";
}
}

140
third_party/lua-protobuf/serpent.lua vendored Normal file
View file

@ -0,0 +1,140 @@
local n, v = "serpent", "0.30" -- (C) 2012-17 Paul Kulchenko; MIT License
local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
local badtype = {thread = true, userdata = true, cdata = true}
local getmetatable = debug and debug.getmetatable or getmetatable
local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
local keyword, globals, G = {}, {}, (_G or _ENV)
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end
local function s(t, opts)
local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
local numformat = opts.numformat or "%.17g"
local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0
local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)",
-- tostring(val) is needed because __tostring may return a non-string value
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end
local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s))
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
local n = name == nil and '' or name
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
local safe = plain and n or '['..safestr(n)..']'
return (path or '')..(plain and path and '.' or '')..safe, safe end
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end
table.sort(k, function(a,b)
-- sort numeric keys first: k[key] is not nil for numerical keys
return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
< (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
local function val2str(t, name, indent, insref, path, plainindex, level)
local ttype, level, mt = type(t), (level or 0), getmetatable(t)
local spath, sname = safename(path, name)
local tag = plainindex and
((type(name) == "number") and '' or name..space..'='..space) or
(name ~= nil and sname..space..'='..space or '')
if seen[t] then -- already seen this element
sref[#sref+1] = spath..space..'='..space..seen[t]
return tag..'nil'..comment('ref', level) end
-- protect from those cases where __tostring may fail
if type(mt) == 'table' then
local to, tr = pcall(function() return mt.__tostring(t) end)
local so, sr = pcall(function() return mt.__serialize(t) end)
if (opts.metatostring ~= false and to or so) then -- knows how to serialize itself
seen[t] = insref or spath
t = so and sr or tr
ttype = type(t)
end -- new value falls through to be serialized
end
if ttype == "table" then
if level >= maxl then return tag..'{}'..comment('maxlvl', level) end
seen[t] = insref or spath
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end
local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
for key = 1, maxn do o[key] = key end
if not maxnum or #o < maxnum then
local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end
if maxnum and #o > maxnum then o[maxnum+1] = nil end
if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
for n, key in ipairs(o) do
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
or opts.keyallow and not opts.keyallow[key]
or opts.keyignore and opts.keyignore[key]
or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
or sparse and value == nil then -- skipping nils; do nothing
elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
if not seen[key] and not globals[key] then
sref[#sref+1] = 'placeholder'
local sname = safename(iname, gensym(key)) -- iname is table for local variables
sref[#sref] = val2str(key,sname,indent,sname,iname,true) end
sref[#sref+1] = 'placeholder'
local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']'
sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path))
else
out[#out+1] = val2str(value,key,indent,insref,seen[t],plainindex,level+1)
if maxlen then
maxlen = maxlen - #out[#out]
if maxlen < 0 then break end
end
end
end
local prefix = string.rep(indent or '', level)
local head = indent and '{\n'..prefix..indent or '{'
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
local tail = indent and "\n"..prefix..'}' or '}'
return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level)
elseif badtype[ttype] then
seen[t] = insref or spath
return tag..globerr(t, level)
elseif ttype == 'function' then
seen[t] = insref or spath
if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end
local ok, res = pcall(string.dump, t)
local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level)
return tag..(func or globerr(t, level))
else return tag..safestr(t) end -- handle all other types
end
local sepr = indent and "\n" or ";"..space
local body = val2str(t, name, indent) -- this call also populates sref
local tail = #sref>1 and table.concat(sref, sepr)..sepr or ''
local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or ''
return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
end
local function deserialize(data, opts)
local env = (opts and opts.safe == false) and G
or setmetatable({}, {
__index = function(t,k) return t end,
__call = function(t,...) error("cannot call functions") end
})
local f, res = (loadstring or load)('return '..data, nil, nil, env)
if not f then f, res = (loadstring or load)(data, nil, nil, env) end
if not f then return f, res end
if setfenv then setfenv(f, env) end
return pcall(f)
end
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
load = deserialize,
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }

478
third_party/lua-protobuf/toplevel.lua vendored Normal file
View file

@ -0,0 +1,478 @@
--lualoader, R"EOF(--
local string = string
local tonumber = tonumber
local setmetatable = setmetatable
local error = error
local ipairs = ipairs
local io = io
local table = table
local math = math
local assert = assert
local tostring = tostring
local type = type
local insert_tab = table.insert
local function meta(name, t)
t = t or {}
t.__name = name
t.__index = t
return t
end
local function default(t, k, def)
local v = t[k]
if not v then
v = def or {}
t[k] = v
end
return v
end
local toplevel = {} do
local labels = { optional = 1; required = 2; repeated = 3 }
local key_types = {
int32 = 5; int64 = 3; uint32 = 13;
uint64 = 4; sint32 = 17; sint64 = 18;
fixed32 = 7; fixed64 = 6; sfixed32 = 15;
sfixed64 = 16; bool = 8; string = 9;
}
local com_types = {
group = 10; message = 11; enum = 14;
}
local types = {
double = 1; float = 2; int32 = 5;
int64 = 3; uint32 = 13; uint64 = 4;
sint32 = 17; sint64 = 18; fixed32 = 7;
fixed64 = 6; sfixed32 = 15; sfixed64 = 16;
bool = 8; string = 9; bytes = 12;
group = 10; message = 11; enum = 14;
}
local function register_type(self, lex, tname, typ)
if not tname:match "%."then
tname = self.prefix..tname
end
if self.typemap[tname] then
return lex:error("type %s already defined", tname)
end
self.typemap[tname] = typ
end
local function type_info(lex, tname)
local tenum = types[tname]
if com_types[tname] then
return lex:error("invalid type name: "..tname)
elseif tenum then
tname = nil
end
return tenum, tname
end
local function map_info(lex)
local keyt = lex:ident "key type"
if not key_types[keyt] then
return lex:error("invalid key type: "..keyt)
end
local valt = lex:expected "," :type_name()
local name = lex:expected ">" :ident()
local ident = name:gsub("^%a", string.upper)
:gsub("_(%a)", string.upper).."Entry"
local kt, ktn = type_info(lex, keyt)
local vt, vtn = type_info(lex, valt)
return name, types.message, ident, {
name = ident,
field = {
{
name = "key",
number = 1;
label = labels.optional,
type = kt,
type_name = ktn
},
{
name = "value",
number = 2;
label = labels.optional,
type = vt,
type_name = vtn
},
},
options = { map_entry = true }
}
end
local function inline_option(lex, info)
if lex:test "%[" then
info = info or {}
while true do
local name = lex:option_name()
local value = lex:expected '=' :constant()
info[name] = value
if lex:test "%]" then
return info
end
lex:expected ','
end
end
end
local function field(self, lex, ident)
local name, typ, type_name, map_entry
if ident == "map" and lex:test "%<" then
name, typ, type_name, map_entry = map_info(lex)
self.locmap[map_entry.field[1]] = lex.pos
self.locmap[map_entry.field[2]] = lex.pos
register_type(self, lex, type_name, types.message)
else
typ, type_name = type_info(lex, ident)
name = lex:ident()
end
local info = {
name = name,
number = lex:expected "=":integer(),
label = ident == "map" and labels.repeated or labels.optional,
type = typ,
type_name = type_name
}
local options = inline_option(lex)
if options then
info.default_value, options.default = tostring(options.default), nil
info.json_name, options.json_name = options.json_name, nil
if options.packed and options.packed == "false" then
options.packed = false
end
info.options = options
end
if info.number <= 0 then
lex:error("invalid tag number: "..info.number)
end
return info, map_entry
end
local function label_field(self, lex, ident, parent)
local label = labels[ident]
local info, map_entry
if not label then
if self.syntax == "proto2" and ident ~= "map" then
return lex:error("proto2 disallow missing label")
end
return field(self, lex, ident)
end
local proto3_optional = label == labels.optional and self.syntax == "proto3"
if proto3_optional and not (self.proto3_optional and parent) then
return lex:error("proto3 disallow 'optional' label")
end
info, map_entry = field(self, lex, lex:type_name())
if proto3_optional then
local ot = default(parent, "oneof_decl")
info.oneof_index = #ot
ot[#ot+1] = { name = "optional_" .. info.name }
else
info.label = label
end
return info, map_entry
end
function toplevel:package(lex, info)
local package = lex:full_ident 'package name'
lex:line_end()
info.package = package
self.prefix = "."..package.."."
return self
end
function toplevel:import(lex, info)
local mode = lex:ident('"weak" or "public"', 'opt') or "public"
if mode ~= 'weak' and mode ~= 'public' then
return lex:error '"weak or "public" expected'
end
local name = lex:quote()
lex:line_end()
local result = self:parsefile(name)
if self.on_import then
self.on_import(result)
end
local dep = default(info, 'dependency')
local index = #dep
dep[index+1] = name
if mode == "public" then
local it = default(info, 'public_dependency')
insert_tab(it, index)
else
local it = default(info, 'weak_dependency')
insert_tab(it, index)
end
end
local msgbody = {} do
function msgbody:message(lex, info)
local nested_type = default(info, 'nested_type')
insert_tab(nested_type, toplevel.message(self, lex))
return self
end
function msgbody:enum(lex, info)
local nested_type = default(info, 'enum_type')
insert_tab(nested_type, toplevel.enum(self, lex))
return self
end
function msgbody:extend(lex, info)
local extension = default(info, 'extension')
local nested_type = default(info, 'nested_type')
local ft, mt = toplevel.extend(self, lex, {})
for _, v in ipairs(ft) do
insert_tab(extension, v)
end
for _, v in ipairs(mt) do
insert_tab(nested_type, v)
end
return self
end
function msgbody:extensions(lex, info)
local rt = default(info, 'extension_range')
local idx = #rt
repeat
local start = lex:integer "field number range"
local stop = math.floor(2^29)
if lex:keyword('to', 'opt') then
if not lex:keyword('max', 'opt') then
stop = lex:integer "field number range end or 'max'"
end
insert_tab(rt, { start = start, ['end'] = stop })
else
insert_tab(rt, { start = start, ['end'] = start })
end
until not lex:test ','
rt[idx+1].options = inline_option(lex)
lex:line_end()
return self
end
function msgbody:reserved(lex, info)
lex:whitespace()
if not lex '^%d' then
local rt = default(info, 'reserved_name')
repeat
insert_tab(rt, (lex:quote()))
until not lex:test ','
else
local rt = default(info, 'reserved_range')
local first = true
repeat
local start = lex:integer(first and 'field name or number range'
or 'field number range')
if lex:keyword('to', 'opt') then
if lex:keyword('max', 'opt') then
insert_tab(rt, { start = start, ['end'] = 2^29-1 })
else
local stop = lex:integer 'field number range end'
insert_tab(rt, { start = start, ['end'] = stop })
end
else
insert_tab(rt, { start = start, ['end'] = start })
end
first = false
until not lex:test ','
end
lex:line_end()
return self
end
function msgbody:oneof(lex, info)
local fs = default(info, "field")
local ts = default(info, "nested_type")
local ot = default(info, "oneof_decl")
local index = #ot + 1
local oneof = { name = lex:ident() }
lex:expected "{"
while not lex:test "}" do
local ident = lex:type_name()
if ident == "option" then
toplevel.option(self, lex, oneof)
else
local f, t = field(self, lex, ident)
self.locmap[f] = lex.pos
if t then insert_tab(ts, t) end
f.oneof_index = index - 1
insert_tab(fs, f)
end
lex:line_end 'opt'
end
ot[index] = oneof
end
function msgbody:option(lex, info)
toplevel.option(self, lex, info)
end
end
function toplevel:message(lex, info)
local name = lex:ident 'message name'
local typ = { name = name }
register_type(self, lex, name, types.message)
local prefix = self.prefix
self.prefix = prefix..name.."."
lex:expected "{"
while not lex:test "}" do
local ident, pos = lex:type_name()
local body_parser = msgbody[ident]
if body_parser then
body_parser(self, lex, typ)
else
local fs = default(typ, 'field')
local f, t = label_field(self, lex, ident, typ)
self.locmap[f] = pos
insert_tab(fs, f)
if t then
local ts = default(typ, 'nested_type')
insert_tab(ts, t)
end
end
lex:line_end 'opt'
end
lex:line_end 'opt'
if info then
info = default(info, 'message_type')
insert_tab(info, typ)
end
self.prefix = prefix
return typ
end
function toplevel:enum(lex, info)
local name, pos = lex:ident 'enum name'
local enum = { name = name }
self.locmap[enum] = pos
register_type(self, lex, name, types.enum)
lex:expected "{"
while not lex:test "}" do
local ident, pos = lex:ident 'enum constant name'
if ident == 'option' then
toplevel.option(self, lex, enum)
elseif ident == 'reserved' then
msgbody.reserved(self, lex, enum)
else
local values = default(enum, 'value')
local number = lex:expected '=' :integer()
local value = {
name = ident,
number = number,
options = inline_option(lex)
}
self.locmap[value] = pos
insert_tab(values, value)
end
lex:line_end 'opt'
end
lex:line_end 'opt'
if info then
info = default(info, 'enum_type')
insert_tab(info, enum)
end
return enum
end
function toplevel:option(lex, info)
local ident = lex:option_name()
lex:expected "="
local value = lex:constant()
lex:line_end()
local options = info and default(info, 'options') or {}
options[ident] = value
return options, self
end
function toplevel:extend(lex, info)
local name = lex:type_name()
local ft = info and default(info, 'extension') or {}
local mt = info and default(info, 'message_type') or {}
lex:expected "{"
while not lex:test "}" do
local ident, pos = lex:type_name()
local f, t = label_field(self, lex, ident)
self.locmap[f] = pos
f.extendee = name
insert_tab(ft, f)
insert_tab(mt, t)
lex:line_end 'opt'
end
return ft, mt
end
local svr_body = {} do
function svr_body:rpc(lex, info)
local name, pos = lex:ident "rpc name"
local rpc = { name = name }
self.locmap[rpc] = pos
local _, tn
lex:expected "%("
rpc.client_streaming = lex:keyword("stream", "opt")
_, tn = type_info(lex, lex:type_name())
if not tn then return lex:error "rpc input type must by message" end
rpc.input_type = tn
lex:expected "%)" :expected "returns" :expected "%("
rpc.server_streaming = lex:keyword("stream", "opt")
_, tn = type_info(lex, lex:type_name())
if not tn then return lex:error "rpc output type must by message" end
rpc.output_type = tn
lex:expected "%)"
if lex:test "{" then
while not lex:test "}" do
lex:line_end "opt"
lex:keyword "option"
toplevel.option(self, lex, rpc)
end
end
lex:line_end "opt"
local t = default(info, "method")
insert_tab(t, rpc)
end
function svr_body:option(lex, info)
return toplevel.option(self, lex, info)
end
function svr_body.stream(_, lex)
lex:error "stream not implement yet"
end
end
function toplevel:service(lex, info)
local name, pos = lex:ident 'service name'
local svr = { name = name }
self.locmap[svr] = pos
lex:expected "{"
while not lex:test "}" do
local ident = lex:type_name()
local body_parser = svr_body[ident]
if body_parser then
body_parser(self, lex, svr)
else
return lex:error "expected 'rpc' or 'option' in service body"
end
lex:line_end 'opt'
end
lex:line_end 'opt'
if info then
info = default(info, 'service')
insert_tab(info, svr)
end
return svr
end
end
return toplevel
-- )EOF"

View file

@ -40,6 +40,7 @@
<ClCompile Include="..\..\src\lua\luafile.cc" />
<ClCompile Include="..\..\src\lua\luawrapper.cc" />
<ClCompile Include="..\..\src\lua\zlibffi.cc" />
<ClCompile Include="..\..\third_party\lua-protobuf\pb.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\lua\extra.h" />
@ -47,6 +48,7 @@
<ClInclude Include="..\..\src\lua\luafile.h" />
<ClInclude Include="..\..\src\lua\luawrapper.h" />
<ClInclude Include="..\..\src\lua\zlibffi.h" />
<ClInclude Include="..\..\third_party\lua-protobuf\pb.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\src\lua\fileffi.lua" />
@ -59,6 +61,10 @@
<None Include="..\..\src\lua\glffi-enum3.lua" />
<None Include="..\..\src\lua\glffi-typedefs.lua" />
<None Include="..\..\third_party\ffi-reflect\reflect.lua" />
<None Include="..\..\third_party\lua-protobuf\descriptor.pb.lua" />
<None Include="..\..\third_party\lua-protobuf\lexer.lua" />
<None Include="..\..\third_party\lua-protobuf\protoc.lua" />
<None Include="..\..\third_party\lua-protobuf\toplevel.lua" />
<None Include="..\..\third_party\pprint.lua\pprint.lua" />
<None Include="..\..\third_party\zlibffi\zlibffi.lua" />
<None Include="packages.config" />

View file

@ -30,6 +30,9 @@
<ClCompile Include="..\..\src\lua\extra.cc">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\third_party\lua-protobuf\pb.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\lua\luawrapper.h">
@ -47,6 +50,9 @@
<ClInclude Include="..\..\src\lua\extra.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\third_party\lua-protobuf\pb.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\src\lua\glffi-enum2.lua">
@ -86,5 +92,17 @@
<Filter>Source Files</Filter>
</None>
<None Include="packages.config" />
<None Include="..\..\third_party\lua-protobuf\protoc.lua">
<Filter>Source Files</Filter>
</None>
<None Include="..\..\third_party\lua-protobuf\lexer.lua">
<Filter>Source Files</Filter>
</None>
<None Include="..\..\third_party\lua-protobuf\toplevel.lua">
<Filter>Source Files</Filter>
</None>
<None Include="..\..\third_party\lua-protobuf\descriptor.pb.lua">
<Filter>Source Files</Filter>
</None>
</ItemGroup>
</Project>