├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── TODO ├── benchmark.lua ├── bin ├── capnpc-echo ├── capnpc-llua ├── capnpc-lua ├── lua-capnproto-test └── schema.capnp ├── capnp.lua ├── capnp ├── compile.lua └── util.lua ├── cpp └── main.c++ ├── doc └── document.md ├── example ├── AddressBook.capnp └── main.lua ├── lua ├── handwritten_capnp.lua ├── handwritten_data_generator.lua ├── random.lua ├── schema_capnp.lua ├── test.lua └── tool.lua ├── proto ├── c++.capnp ├── enums.capnp ├── example.capnp ├── lua.capnp └── struct.capnp ├── rockspec ├── lua-capnproto-0.1.3-3.rockspec └── lua-capnproto-scm-1.rockspec ├── test.sh └── tests ├── 01-sanity.lua ├── 02-util.lua ├── 03-compile.lua ├── 10-encode-decode.lua └── run_tests.sh /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | name: Semgrep config 10 | jobs: 11 | semgrep: 12 | name: semgrep/ci 13 | runs-on: ubuntu-20.04 14 | env: 15 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 16 | SEMGREP_URL: https://cloudflare.semgrep.dev 17 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 19 | container: 20 | image: returntocorp/semgrep 21 | steps: 22 | - uses: actions/checkout@v3 23 | - run: semgrep ci 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # Vim files 16 | *.sw* 17 | *~ 18 | 19 | # Project temp files 20 | *.data 21 | proto/example.capnp.c++ 22 | proto/*.capnp.h 23 | proto/*.capnp.c++ 24 | proto/*_capnp.lua 25 | proto/*_capnp.llua 26 | #proto/enums.capnp.h 27 | test.schema.txt 28 | main 29 | *.bak 30 | test.schema.lua 31 | CLIP 32 | example_capnp.lua 33 | data_generator.lua 34 | *.json 35 | *.log 36 | *.bin 37 | 38 | lua-capnproto-*.rockspec 39 | *.src.rock 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Jiale Zhi 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION:=0.1.3-2 2 | CXXFLAGS:=-std=gnu++11 -g -Iproto -I/usr/local/include 3 | LDFLAGS:=-L/usr/local/lib -lcapnp -lkj -pthread 4 | CAPNP_TEST:=../capnp_test 5 | PWD:=$(shell pwd) 6 | #CXX:=g++-4.7 7 | 8 | export PATH:=bin:$(PATH) 9 | export LUA_PATH:=$(PWD)/?.lua;$(PWD)/proto/?.lua;$(PWD)/lua/?.lua;$(PWD)/proto/?.lua;$(PWD)/tests/?.lua;$(PWD)/$(CAPNP_TEST)/?.lua;$(LUA_PATH);; 10 | export VERBOSE 11 | 12 | compiled: proto/example.capnp proto/enums.capnp 13 | capnp compile -oc++ $+ 14 | 15 | test.schema.txt: proto/enums.capnp proto/example.capnp 16 | capnp compile -oecho $+ > /tmp/capnp.bin 17 | capnp decode proto/schema.capnp CodeGeneratorRequest > $@ < /tmp/capnp.bin 18 | 19 | cpp/example_capnp.o: proto/example.capnp.c++ compiled 20 | $(CXX) -c $(CXXFLAGS) $< -o $@ 21 | 22 | cpp/enums_capnp.o: proto/enums.capnp.c++ compiled 23 | $(CXX) -c $(CXXFLAGS) $< -o $@ 24 | 25 | cpp/main.o: cpp/main.c++ compiled 26 | $(CXX) -c $(CXXFLAGS) $< -o $@ 27 | 28 | cpp/main: cpp/main.o cpp/example_capnp.o cpp/enums_capnp.o 29 | $(CXX) $(CXXFLAGS) -o $@ $+ $(LDFLAGS) 30 | 31 | proto/example_capnp.lua: proto/example.capnp proto/enums.capnp proto/struct.capnp proto/lua.capnp 32 | capnp compile -obin/capnpc-lua $+ 33 | 34 | test: clean proto/example_capnp.lua 35 | tests/run_tests.sh 36 | 37 | test1: 38 | capnp compile -olua $(CAPNP_TEST)/test.capnp ../capnproto/c++/src/capnp/c++.capnp 39 | $(MAKE) -C $(CAPNP_TEST) CAPNP_TEST_APP=`pwd`/bin/lua-capnproto-test 40 | 41 | all: cpp/main 42 | 43 | clean: 44 | -rm proto/example.capnp.c++ proto/example.capnp.h cpp/*.o cpp/main test.schema.lua proto/example_capnp.lua a.data c.data test.schema.txt *.data 45 | 46 | tag_and_pack: 47 | ifeq ($(shell git tag --sort=version:refname|tail -n 1), v$(VERSION)) 48 | @echo "Need to \"make version\" first" 49 | @exit 1 50 | endif 51 | @echo "Add git tag v$(VERSION)?" 52 | @read -r FOO 53 | git tag -f v$(VERSION) 54 | @echo "Push tags?" 55 | @read -r FOO 56 | git push --tags 57 | @echo "Build package?" 58 | @read -r FOO 59 | cp lua-capnproto.rockspec lua-capnproto-$(VERSION).rockspec 60 | luarocks pack lua-capnproto-$(VERSION).rockspec 61 | 62 | version: 63 | @echo "Old version is \"$(VERSION)\"" 64 | @echo "Enter new version: " 65 | @# The use of variable "new_version" ($$new_version) should be in the same line as where it gets its value 66 | @read new_version; perl -pi -e "s/$(VERSION)/$$new_version/" Makefile bin/capnpc-lua lua-capnproto.rockspec 67 | git add Makefile bin/capnpc-lua lua-capnproto.rockspec 68 | git commit -m 'Bump version number' 69 | 70 | release: tag_and_pack 71 | 72 | release_clean: 73 | -rm -f lua-capnproto-*.rockspec *.rock 74 | 75 | .PHONY: all clean test release 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua-capnproto 2 | ============= 3 | 4 | [Cap’n Proto](http://kentonv.github.io/capnproto/index.html) is an insanely fast data interchange format and capability-based RPC system. 5 | 6 | Lua-capnproto is a pure lua implementation of Cap'n Proto based on `LuaJIT`. 7 | 8 | This project is still under early development and is not production-ready. 9 | 10 | Synopsis 11 | ======== 12 | Suppose you have a Cap'n Proto file called example.capnp. You can compile this file like this: 13 | 14 | $capnp compile -olua example.capnp 15 | 16 | The default output file is `example_capnp.lua` 17 | 18 | local example_capnp = require "example_capnp" 19 | 20 | Check out example/AddressBook.capnp and example/main.lua for how to use generated lua file. 21 | 22 | Installation 23 | ============ 24 | To install lua-capnproto, you need to install Cap'n Proto , LuaJIT and luarocks first. 25 | 26 | `Currently, lua-capnproto only works with LuaJIT v2.1`. You can install LuaJIT v2.1 using the following commands: 27 | 28 | $git clone http://luajit.org/git/luajit-2.0.git 29 | $git checkout v2.1 30 | $make && sudo make install 31 | $sudo ln -sf luajit-2.1.0-alpha /usr/local/bin/luajit 32 | 33 | Then you can install lua-capnproto using the following commands: 34 | 35 | $sudo luarocks install lua-capnproto 36 | 37 | Let's compile an example file to test whether lua-capnproto was installed successfully: 38 | 39 | $capnp compile -olua proto/example.capnp proto/enums.capnp proto/lua.capnp proto/struct.capnp 40 | 41 | Normally, you should see no errors and a file named "proto/example_capnp.lua" is generated. 42 | 43 | How to use 44 | ========== 45 | Please see my blog post on how to use lua-capnproto [here](http://blog.cloudflare.com/introducing-lua-capnproto-better-serialization-in-lua). 46 | 47 | Testing 48 | ======= 49 | 50 | If you want to run unit tests, you need to install lunitx and lua-cjson: 51 | 52 | $sudo luarocks install lua-cjson 53 | $sudo luarocks install lunitx 54 | 55 | If your Linux distribution have Lua 5.2 installed, using this [instruction](https://github.com/calio/lua-capnproto/issues/1) to install required lua modules. 56 | 57 | To run tests: 58 | 59 | $./test.sh 60 | 61 | Limitations 62 | =========== 63 | * Currently, lua-capnproto only works with LuaJIT v2.1. This is because lua-capnproto needs 64 bit integer support and 64bit number bit operations, but only LuaJIT v2.1 provides a decent way to do all these. I'm working on LuaJIT 2.0/ Lua 5.1 / Lua 5.2 support, hopefully you can use lua-capnproto with your favorite lua soon. 64 | * Cap'n Proto RPC is not implemented yet 65 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * performance turning 2 | * multiple files input 3 | * make capnpc-llua work 4 | * fix 0-item list 5 | * calculate new_tab arguments 6 | * AnyPointer support 7 | * uInt8DefaultValueTest decoding test case 8 | * packing/unpacking 9 | * compatibility with old formats 10 | * support writing non-cdata 64 bit number 11 | -------------------------------------------------------------------------------- /benchmark.lua: -------------------------------------------------------------------------------- 1 | jit.opt.start("loopunroll=1000", "maxrecord=5000", "maxmcode=1024") 2 | 3 | package.path = "lua/?.lua;proto/?.lua;" .. package.path 4 | 5 | local test_capnp = require "handwritten_capnp" 6 | 7 | local ffi = require "ffi" 8 | local test_capnp = require "handwritten_capnp" 9 | local capnp = require "capnp" 10 | local cjson = require "cjson" 11 | 12 | local times = arg[1] or 1000000 13 | 14 | local data = { 15 | i0 = 32, 16 | i1 = 16, 17 | i2 = 127, 18 | b0 = true, 19 | b1 = true, 20 | i3 = 65536, 21 | e0 = "enum3", 22 | s0 = { 23 | f0 = 3.14, 24 | f1 = 3.14159265358979, 25 | }, 26 | l0 = { 28, 29 }, 27 | t0 = "hello", 28 | e1 = "enum7", 29 | } 30 | 31 | local size = test_capnp.T1.calc_size(data) 32 | local buf = ffi.new("char[?]", size) 33 | local bin = test_capnp.T1.serialize(data) 34 | local json_data = cjson.encode(data) 35 | local tab = {} 36 | 37 | function run4() 38 | return test_capnp.T1.serialize(data, buf, size) 39 | end 40 | 41 | function run3() 42 | return test_capnp.T1.serialize(data) 43 | end 44 | 45 | function run2() 46 | return cjson.encode(data) 47 | end 48 | 49 | function run4() 50 | return cjson.decode(json_data) 51 | end 52 | 53 | function run1() 54 | return test_capnp.T1.parse(bin, tab) 55 | end 56 | 57 | print("Benchmarking ", times .. " times.") 58 | 59 | local res 60 | 61 | function bench(name, func) 62 | local t1 = os.clock() 63 | 64 | for i=1, times do 65 | res = func() 66 | end 67 | 68 | print(name, " Elapsed: ", (os.clock() - t1) .. "s") 69 | end 70 | 71 | bench("cjson encode", run2) 72 | bench("capnp encode", run3) 73 | bench("cjson decode", run4) 74 | --bench("capnp-noalloc", run4) 75 | bench("capnp decode", run1) 76 | 77 | --print(cjson.encode(res)) 78 | -------------------------------------------------------------------------------- /bin/capnpc-echo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat - 4 | -------------------------------------------------------------------------------- /bin/capnpc-llua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | 3 | --package.path="lua/?.lua;;" 4 | 5 | local schema_capnp = require("schema_capnp") 6 | local cjson = require("cjson") 7 | local compile = require("capnp.compile") 8 | local util = require("capnp.util") 9 | 10 | local schema_json_file = "llua.schema.json" 11 | 12 | local content = io.stdin:read("*a") 13 | local schema = assert(schema_capnp.CodeGeneratorRequest.parse(content)) 14 | 15 | schema.__compiler = "lua-capnp(decoded by lua)" 16 | util.write_file(schema_json_file, cjson.encode(schema)) 17 | local res = compile.compile(schema) 18 | 19 | local outfile = util.get_output_name(schema) .. ".llua" 20 | util.write_file(outfile, res) 21 | -------------------------------------------------------------------------------- /bin/capnpc-lua: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=0.1.3-1 4 | 5 | usage() { 6 | cat <...] 8 | 9 | This is a Cap'n Proto compiler plugin which compiles the schema into lua 10 | files. This is meant to be run using the Cap'n Proto compiler, e.g.: 11 | capnp compile -olua foo.capnp 12 | 13 | Options: 14 | --verbose 15 | Write schema file: test.schema.lua and lua.schema.json; useful for debugging. 16 | --version 17 | Print version information and exit. 18 | --help 19 | Display this help text and exit. 20 | EOF 21 | exit 22 | } 23 | 24 | version() { 25 | echo "Cap'n Proto Lua plug-in version $VERSION" 26 | exit 27 | } 28 | 29 | realpath() { 30 | perl -MCwd -e 'print Cwd::abs_path shift' $1; 31 | } 32 | 33 | 34 | VERBOSE=${VERBOSE:="false"} 35 | FILE=$(realpath $0) 36 | DIR=$(dirname $FILE) 37 | export LUA_PATH="$DIR/../?.lua;$LUA_PATH;;" 38 | 39 | 40 | while : ; do 41 | case $1 in 42 | --version) 43 | version ;; 44 | --help) 45 | usage ;; 46 | --verbose) 47 | #[ -n "${VERBOSE}" ] && usage 48 | VERBOSE="true" 49 | shift ;; 50 | *) 51 | break ;; 52 | esac 53 | done 54 | 55 | BASE=$(dirname $(realpath $0)) 56 | #export LUA_PATH="$BASE/lua/?.lua;$BASE/proto/?.lua;$LUA_PATH;;" 57 | 58 | luajit - <(cat - | capnp decode $BASE/schema.capnp CodeGeneratorRequest) $@ < 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | using Cxx = import "c++.capnp"; 25 | 26 | @0xa93fc509624c72d9; 27 | $Cxx.namespace("capnp::schema"); 28 | 29 | using Id = UInt64; 30 | # The globally-unique ID of a file, type, or annotation. 31 | 32 | struct Node { 33 | id @0 :Id; 34 | 35 | displayName @1 :Text; 36 | # Name to present to humans to identify this Node. You should not attempt to parse this. Its 37 | # format could change. It is not guaranteed to be unique. 38 | # 39 | # (On Zooko's triangle, this is the node's nickname.) 40 | 41 | displayNamePrefixLength @2 :UInt32; 42 | # If you want a shorter version of `displayName` (just naming this node, without its surrounding 43 | # scope), chop off this many characters from the beginning of `displayName`. 44 | 45 | scopeId @3 :Id; 46 | # ID of the lexical parent node. Typically, the scope node will have a NestedNode pointing back 47 | # at this node, but robust code should avoid relying on this (and, in fact, group nodes are not 48 | # listed in the outer struct's nestedNodes, since they are listed in the fields). `scopeId` is 49 | # zero if the node has no parent, which is normally only the case with files, but should be 50 | # allowed for any kind of node (in order to make runtime type generation easier). 51 | 52 | nestedNodes @4 :List(NestedNode); 53 | # List of nodes nested within this node, along with the names under which they were declared. 54 | 55 | struct NestedNode { 56 | name @0 :Text; 57 | # Unqualified symbol name. Unlike Node.name, this *can* be used programmatically. 58 | # 59 | # (On Zooko's triangle, this is the node's petname according to its parent scope.) 60 | 61 | id @1 :Id; 62 | # ID of the nested node. Typically, the target node's scopeId points back to this node, but 63 | # robust code should avoid relying on this. 64 | } 65 | 66 | annotations @5 :List(Annotation); 67 | # Annotations applied to this node. 68 | 69 | union { 70 | # Info specific to each kind of node. 71 | 72 | file @6 :Void; 73 | 74 | struct :group { 75 | dataWordCount @7 :UInt16; 76 | # Size of the data section, in words. 77 | 78 | pointerCount @8 :UInt16; 79 | # Size of the pointer section, in pointers (which are one word each). 80 | 81 | preferredListEncoding @9 :ElementSize; 82 | # The preferred element size to use when encoding a list of this struct. If this is anything 83 | # other than `inlineComposite` then the struct is one word or less in size and is a candidate 84 | # for list packing optimization. 85 | 86 | isGroup @10 :Bool; 87 | # If true, then this "struct" node is actually not an independent node, but merely represents 88 | # some named union or group within a particular parent struct. This node's scopeId refers 89 | # to the parent struct, which may itself be a union/group in yet another struct. 90 | # 91 | # All group nodes share the same dataWordCount and pointerCount as the top-level 92 | # struct, and their fields live in the same ordinal and offset spaces as all other fields in 93 | # the struct. 94 | # 95 | # Note that a named union is considered a special kind of group -- in fact, a named union 96 | # is exactly equivalent to a group that contains nothing but an unnamed union. 97 | 98 | discriminantCount @11 :UInt16; 99 | # Number of fields in this struct which are members of an anonymous union, and thus may 100 | # overlap. If this is non-zero, then a 16-bit discriminant is present indicating which 101 | # of the overlapping fields is active. This can never be 1 -- if it is non-zero, it must be 102 | # two or more. 103 | # 104 | # Note that the fields of an unnamed union are considered fields of the scope containing the 105 | # union -- an unnamed union is not its own group. So, a top-level struct may contain a 106 | # non-zero discriminant count. Named unions, on the other hand, are equivalent to groups 107 | # containing unnamed unions. So, a named union has its own independent schema node, with 108 | # `isGroup` = true. 109 | 110 | discriminantOffset @12 :UInt32; 111 | # If `discriminantCount` is non-zero, this is the offset of the union discriminant, in 112 | # multiples of 16 bits. 113 | 114 | fields @13 :List(Field); 115 | # Fields defined within this scope (either the struct's top-level fields, or the fields of 116 | # a particular group; see `isGroup`). 117 | # 118 | # The fields are sorted by ordinal number, but note that because groups share the same 119 | # ordinal space, the field's index in this list is not necessarily exactly its ordinal. 120 | # On the other hand, the field's position in this list does remain the same even as the 121 | # protocol evolves, since it is not possible to insert or remove an earlier ordinal. 122 | # Therefore, for most use cases, if you want to identify a field by number, it may make the 123 | # most sense to use the field's index in this list rather than its ordinal. 124 | } 125 | 126 | enum :group { 127 | enumerants@14 :List(Enumerant); 128 | # Enumerants ordered by numeric value (ordinal). 129 | } 130 | 131 | interface :group { 132 | methods @15 :List(Method); 133 | # Methods ordered by ordinal. 134 | 135 | extends @31 :List(Id); 136 | # Superclasses of this interface. 137 | } 138 | 139 | const :group { 140 | type @16 :Type; 141 | value @17 :Value; 142 | } 143 | 144 | annotation :group { 145 | type @18 :Type; 146 | 147 | targetsFile @19 :Bool; 148 | targetsConst @20 :Bool; 149 | targetsEnum @21 :Bool; 150 | targetsEnumerant @22 :Bool; 151 | targetsStruct @23 :Bool; 152 | targetsField @24 :Bool; 153 | targetsUnion @25 :Bool; 154 | targetsGroup @26 :Bool; 155 | targetsInterface @27 :Bool; 156 | targetsMethod @28 :Bool; 157 | targetsParam @29 :Bool; 158 | targetsAnnotation @30 :Bool; 159 | } 160 | } 161 | } 162 | 163 | struct Field { 164 | # Schema for a field of a struct. 165 | 166 | name @0 :Text; 167 | 168 | codeOrder @1 :UInt16; 169 | # Indicates where this member appeared in the code, relative to other members. 170 | # Code ordering may have semantic relevance -- programmers tend to place related fields 171 | # together. So, using code ordering makes sense in human-readable formats where ordering is 172 | # otherwise irrelevant, like JSON. The values of codeOrder are tightly-packed, so the maximum 173 | # value is count(members) - 1. Fields that are members of a union are only ordered relative to 174 | # the other members of that union, so the maximum value there is count(union.members). 175 | 176 | annotations @2 :List(Annotation); 177 | 178 | const noDiscriminant :UInt16 = 0xffff; 179 | 180 | discriminantValue @3 :UInt16 = Field.noDiscriminant; 181 | # If the field is in a union, this is the value which the union's discriminant should take when 182 | # the field is active. If the field is not in a union, this is 0xffff. 183 | 184 | union { 185 | slot :group { 186 | # A regular, non-group, non-fixed-list field. 187 | 188 | offset @4 :UInt32; 189 | # Offset, in units of the field's size, from the beginning of the section in which the field 190 | # resides. E.g. for a UInt32 field, multiply this by 4 to get the byte offset from the 191 | # beginning of the data section. 192 | 193 | type @5 :Type; 194 | defaultValue @6 :Value; 195 | 196 | hadExplicitDefault @10 :Bool; 197 | # Whether the default value was specified explicitly. Non-explicit default values are always 198 | # zero or empty values. Usually, whether the default value was explicit shouldn't matter. 199 | # The main use case for this flag is for structs representing method parameters: 200 | # explicitly-defaulted parameters may be allowed to be omitted when calling the method. 201 | } 202 | 203 | group :group { 204 | # A group. 205 | 206 | typeId @7 :Id; 207 | # The ID of the group's node. 208 | } 209 | } 210 | 211 | ordinal :union { 212 | implicit @8 :Void; 213 | explicit @9 :UInt16; 214 | # The original ordinal number given to the field. You probably should NOT use this; if you need 215 | # a numeric identifier for a field, use its position within the field array for its scope. 216 | # The ordinal is given here mainly just so that the original schema text can be reproduced given 217 | # the compiled version -- i.e. so that `capnp compile -ocapnp` can do its job. 218 | } 219 | } 220 | 221 | struct Enumerant { 222 | # Schema for member of an enum. 223 | 224 | name @0 :Text; 225 | 226 | codeOrder @1 :UInt16; 227 | # Specifies order in which the enumerants were declared in the code. 228 | # Like Struct.Field.codeOrder. 229 | 230 | annotations @2 :List(Annotation); 231 | } 232 | 233 | struct Method { 234 | # Schema for method of an interface. 235 | 236 | name @0 :Text; 237 | 238 | codeOrder @1 :UInt16; 239 | # Specifies order in which the methods were declared in the code. 240 | # Like Struct.Field.codeOrder. 241 | 242 | paramStructType @2 :Id; 243 | # ID of the parameter struct type. If a named parameter list was specified in the method 244 | # declaration (rather than a single struct parameter type) then a corresponding struct type is 245 | # auto-generated. Such an auto-generated type will not be listed in the interface's 246 | # `nestedNodes` and its `scopeId` will be zero -- it is completely detached from the namespace. 247 | 248 | resultStructType @3 :Id; 249 | # ID of the return struct type; similar to `paramStructType`. 250 | 251 | annotations @4 :List(Annotation); 252 | } 253 | 254 | struct Type { 255 | # Represents a type expression. 256 | 257 | union { 258 | # The ordinals intentionally match those of Value. 259 | 260 | void @0 :Void; 261 | bool @1 :Void; 262 | int8 @2 :Void; 263 | int16 @3 :Void; 264 | int32 @4 :Void; 265 | int64 @5 :Void; 266 | uint8 @6 :Void; 267 | uint16 @7 :Void; 268 | uint32 @8 :Void; 269 | uint64 @9 :Void; 270 | float32 @10 :Void; 271 | float64 @11 :Void; 272 | text @12 :Void; 273 | data @13 :Void; 274 | 275 | list :group { 276 | elementType @14 :Type; 277 | } 278 | 279 | enum :group { 280 | typeId @15 :Id; 281 | } 282 | struct :group { 283 | typeId @16 :Id; 284 | } 285 | interface :group { 286 | typeId @17 :Id; 287 | } 288 | 289 | anyPointer @18 :Void; 290 | } 291 | } 292 | 293 | struct Value { 294 | # Represents a value, e.g. a field default value, constant value, or annotation value. 295 | 296 | union { 297 | # The ordinals intentionally match those of Type. 298 | 299 | void @0 :Void; 300 | bool @1 :Bool; 301 | int8 @2 :Int8; 302 | int16 @3 :Int16; 303 | int32 @4 :Int32; 304 | int64 @5 :Int64; 305 | uint8 @6 :UInt8; 306 | uint16 @7 :UInt16; 307 | uint32 @8 :UInt32; 308 | uint64 @9 :UInt64; 309 | float32 @10 :Float32; 310 | float64 @11 :Float64; 311 | text @12 :Text; 312 | data @13 :Data; 313 | 314 | list @14 :AnyPointer; 315 | 316 | enum @15 :UInt16; 317 | struct @16 :AnyPointer; 318 | 319 | interface @17 :Void; 320 | # The only interface value that can be represented statically is "null", whose methods always 321 | # throw exceptions. 322 | 323 | anyPointer @18 :AnyPointer; 324 | } 325 | } 326 | 327 | struct Annotation { 328 | # Describes an annotation applied to a declaration. Note AnnotationNode describes the 329 | # annotation's declaration, while this describes a use of the annotation. 330 | 331 | id @0 :Id; 332 | # ID of the annotation node. 333 | 334 | value @1 :Value; 335 | } 336 | 337 | enum ElementSize { 338 | # Possible element sizes for encoded lists. These correspond exactly to the possible values of 339 | # the 3-bit element size component of a list pointer. 340 | 341 | empty @0; # aka "void", but that's a keyword. 342 | bit @1; 343 | byte @2; 344 | twoBytes @3; 345 | fourBytes @4; 346 | eightBytes @5; 347 | pointer @6; 348 | inlineComposite @7; 349 | } 350 | 351 | struct CodeGeneratorRequest { 352 | nodes @0 :List(Node); 353 | # All nodes parsed by the compiler, including for the files on the command line and their 354 | # imports. 355 | 356 | requestedFiles @1 :List(RequestedFile); 357 | # Files which were listed on the command line. 358 | 359 | struct RequestedFile { 360 | id @0 :Id; 361 | # ID of the file. 362 | 363 | filename @1 :Text; 364 | # Name of the file as it appeared on the command-line (minus the src-prefix). You may use 365 | # this to decide where to write the output. 366 | 367 | imports @2 :List(Import); 368 | # List of all imported paths seen in this file. 369 | 370 | struct Import { 371 | id @0 :Id; 372 | # ID of the imported file. 373 | 374 | name @1 :Text; 375 | # Name which *this* file used to refer to the foreign file. This may be a relative name. 376 | # This information is provided because it might be useful for code generation, e.g. to 377 | # generate #include directives in C++. We don't put this in Node.file because this 378 | # information is only meaningful at compile time anyway. 379 | # 380 | # (On Zooko's triangle, this is the import's petname according to the importing file.) 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /capnp.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------- 2 | -- lua-capnproto runtime module. 3 | -- @copyright 2013-2014 Jiale Zhi (vipcalio@gmail.com) 4 | ----------------------------------------------------------- 5 | 6 | local ffi = require "ffi" 7 | local bit = require "bit" 8 | 9 | local arshift = bit.arshift 10 | local lshift, rshift = bit.lshift, bit.rshift 11 | local band, bor, bxor = bit.band, bit.bor, bit.bxor 12 | 13 | local typeof = ffi.typeof 14 | local cast = ffi.cast 15 | local ffistr = ffi.string 16 | local copy = ffi.copy 17 | local ceil = math.ceil 18 | local floor = math.floor 19 | local type = type 20 | local error = error 21 | 22 | -- Only works with Little Endian for now 23 | assert(ffi.abi("le") == true) 24 | assert(ffi.sizeof("float") == 4) 25 | assert(ffi.sizeof("double") == 8) 26 | 27 | 28 | local bfloat32 = ffi.new('float[?]', 2) 29 | local bfloat64 = ffi.new('double[?]', 3) 30 | local bint32 = ffi.new('int[?]', 2) 31 | local buint64 = ffi.new('uint64_t[?]', 2) 32 | 33 | local round8 = function(size) 34 | return ceil(size / 8) * 8 35 | end 36 | 37 | 38 | -- table.new(narr, nrec) 39 | local ok, new_tab = pcall(require, "table.new") 40 | if not ok then 41 | new_tab = function () return {} end 42 | end 43 | 44 | 45 | local _M = new_tab(0, 32) 46 | 47 | 48 | local pint32 = typeof("int32_t *") 49 | local puint32 = typeof("uint32_t *") 50 | local puint64 = typeof("uint64_t *") 51 | local pfloat32 = typeof("float *") 52 | local pfloat64 = typeof("double *") 53 | 54 | local pointer_map = { 55 | int8 = typeof("int8_t *"), 56 | int16 = typeof("int16_t *"), 57 | int32 = typeof("int32_t *"), 58 | int64 = typeof("int64_t *"), 59 | uint8 = typeof("uint8_t *"), 60 | uint16 = typeof("uint16_t *"), 61 | uint32 = typeof("uint32_t *"), 62 | uint64 = typeof("uint64_t *"), 63 | bool = typeof("uint8_t *"), 64 | float32 = typeof("float *"), 65 | float64 = typeof("double *"), 66 | } 67 | 68 | --- Calculate offset within size 69 | -- @param bit_off offset in bits 70 | -- @param size size in bits 71 | -- @return n: number of size-long space 72 | -- @return s: offset within a size-long space 73 | local function get_bit_offset(bit_off, size) 74 | local n, s 75 | n = floor(bit_off / size) 76 | s = bit_off % size 77 | 78 | return n, s 79 | end 80 | 81 | --- Get a pointer cdata from a int32 pointer and a Cap'n Proto type 82 | -- @param p32 int32 pointer 83 | -- @param field_type Cap'n Proto type string 84 | -- @return pointer cdata 85 | local function get_pointer_from_type(p32, field_type) 86 | local t = pointer_map[field_type] 87 | if not t then 88 | error("not supported type: " .. field_type) 89 | end 90 | 91 | return cast(t, p32) 92 | end 93 | 94 | --- Use LuaJIT FFI to calculate xor for float number 95 | -- @param val input float32 number 96 | -- @param default its default value 97 | -- @return xor'ed value 98 | function _M.fix_float32_default(val, default) 99 | local float 100 | bfloat32[0] = default 101 | bfloat32[1] = val 102 | 103 | -- default float value 104 | local uint_def = cast(puint32, bfloat32) 105 | local uint_val = cast(puint32, bfloat32 + 1) 106 | val = bxor(uint_val[0], uint_def[0]) 107 | bint32[0] = val 108 | float = cast(pfloat32, bint32) 109 | return float[0] 110 | end 111 | 112 | --- Use LuaJIT FFI to calculate xor for float number 113 | -- @param val input float64 number 114 | -- @param default its default value 115 | -- @return xor'ed value 116 | function _M.fix_float64_default(val, default) 117 | local float 118 | bfloat64[0] = default 119 | bfloat64[1] = val 120 | local uint_def = cast(puint64, bfloat64) 121 | local uint_val = cast(puint64, bfloat64 + 1) 122 | val = bxor(uint_val[0], uint_def[0]) 123 | buint64[0] = val 124 | float = cast(pfloat64, buint64) 125 | return float[0] 126 | end 127 | 128 | --- Read a structure field 129 | -- @param p32 a int32 pointer points to the start of a struct 130 | -- @param field_type struct field type 131 | -- @param size field size in bits 132 | -- @param off field offset (from parsed schema) 133 | -- @param default field default value 134 | -- @return parsed field 135 | function _M.read_struct_field(p32, field_type, size, off, default) 136 | if field_type == "void" then 137 | return "Void" 138 | end 139 | 140 | local p = get_pointer_from_type(p32, field_type) 141 | 142 | local val 143 | if size >= 8 then 144 | local n, s = get_bit_offset(size * off, size) 145 | val = p[n] 146 | else 147 | local n, s = get_bit_offset(size * off, 8) 148 | local mask = 2^size - 1 149 | mask = lshift(mask, s) 150 | val = rshift(band(mask, p[n]), s) 151 | end 152 | 153 | if default then 154 | if field_type == "float32" then 155 | val = _M.fix_float32_default(val, default) 156 | elseif field_type == "float64" then 157 | val = _M.fix_float64_default(val, default) 158 | else 159 | val = bxor(val, default) 160 | end 161 | end 162 | 163 | if field_type == "bool" then 164 | if val and val ~= 0 then 165 | return true 166 | else 167 | return false 168 | end 169 | end 170 | 171 | if field_type == "int64" or field_type == "uint64" then 172 | return val 173 | elseif field_type == "uint32" then 174 | -- uint32 is treated as signed int32 by bit operations 175 | if val < 0 then 176 | val = 2^32 + val 177 | end 178 | return val 179 | else 180 | return val 181 | end 182 | end 183 | 184 | --- Write a bit (boolean value) 185 | function _M.write_bit(p8, val, bit_off, default) 186 | if type(val) == "boolean" then 187 | val = val and 1 or 0 188 | end 189 | if default then 190 | val = bxor(val, default) 191 | end 192 | p8[0] = bor(p8[0], lshift(val, bit_off)) 193 | end 194 | 195 | --- Write a number 196 | function _M.write_num(p, val, field_type, default) 197 | if default then 198 | if field_type == "float32" then 199 | val = _M.fix_float32_default(val, default) 200 | elseif field_type == "float64" then 201 | val = _M.fix_float64_default(val, default) 202 | else 203 | if default ~= 0 then 204 | val = bxor(val, default) 205 | end 206 | end 207 | end 208 | p[0] = val 209 | end 210 | 211 | --- Write a structure field 212 | -- @param p32 a int32 pointer points to the start of a struct 213 | -- @param val field value 214 | -- @param field_type struct field type 215 | -- @param size field size in bits 216 | -- @param off field offset (from parsed schema) 217 | -- @param default field default value 218 | function _M.write_struct_field(p32, val, field_type, size, off, default) 219 | --local p = get_pointer_from_val(p32, size, val) 220 | local p = get_pointer_from_type(p32, field_type) 221 | if type(val) == "boolean" then 222 | val = val and 1 or 0 223 | end 224 | 225 | if size >= 8 then 226 | local n, s = get_bit_offset(size * off, size) 227 | _M.write_num(p + n, val, field_type, default) 228 | --p[n] = val 229 | else 230 | local n, s = get_bit_offset(size * off, 8) 231 | _M.write_bit(p + n, val, s, default) 232 | --p[n] = bor(p[n], lshift(val, s)) 233 | end 234 | end 235 | 236 | --- Read text (not including list pointer) starting from buf, works for both 237 | -- "text" type and "data" type 238 | function _M.read_text_data(buf, num) 239 | return ffistr(buf, num) -- dataWordCount + offset + pointerSize + data_off 240 | end 241 | 242 | --- Write text (not including list pointer) starting from buf 243 | -- @param buf free space that text data will be written to 244 | -- @text text to be written 245 | -- @is_binary if writes "data", is_binary is true, otherwise false. 246 | function _M.write_text_data(buf, text, is_binary) 247 | local len = #text 248 | copy(buf, text, len) 249 | if is_binary then 250 | return round8(len) 251 | else 252 | return round8(len + 1) 253 | end 254 | end 255 | 256 | function _M.read_text(buf, header, T, offset, default) 257 | local res 258 | local data_off, size, num = _M.read_listp_struct(buf, header, T, 2) 259 | if data_off and num then 260 | res = ffistr(buf + (T.dataWordCount + offset + 1 + data_off) * 2, 261 | num - 1) -- dataWordCount + offset + pointerSize + data_off 262 | else 263 | res = default 264 | end 265 | return res 266 | end 267 | 268 | --- Write text 269 | -- @param p32 270 | -- @param text 271 | -- @param data_off words between the end of list pointer and the first 272 | -- byte of text data 273 | function _M.write_text(p32, text, data_off, is_binary) 274 | local len = #text 275 | if not is_binary then 276 | len = len + 1 277 | end 278 | _M.write_listp(p32, 2, len, data_off) 279 | return _M.write_text_data(p32 + data_off * 2 + 2, text, is_binary) 280 | --copy(p32 + data_off*2 + 2, text) 281 | --return round8(len) 282 | end 283 | 284 | function _M.get_data_off(T, offset, pos) 285 | return (pos - T.dataWordCount * 8 - offset * 8 - 8) / 8 286 | end 287 | 288 | function _M.read_composite_tag(buf) 289 | local p = cast(pint32, buf) 290 | local val = p[0] 291 | local sig = band(val, 0x03) 292 | if sig ~= 0 then 293 | error("corrupt data, expected struct signature(composite list tag) " .. 294 | "0 but have " .. sig) 295 | end 296 | 297 | -- pointer offset (B) instead indicates the number of elements in the list 298 | local num = rshift(val, 2) 299 | val = p[1] 300 | local dt = band(val, 0xffff) 301 | local pt = rshift(val, 16) 302 | --p[1] = lshift(T.pointerCount, 16) + T.dataWordCount 303 | return num, dt, pt 304 | end 305 | 306 | function _M.write_composite_tag(p32, T, num) 307 | --local p = ffi.cast("int32_t *", buf) 308 | local p = p32 309 | -- pointer offset (B) instead indicates the number of elements in the list 310 | p[0] = lshift(num, 2) 311 | p[1] = lshift(T.pointerCount, 16) + T.dataWordCount 312 | end 313 | 314 | function _M.read_struct_pointer(p) 315 | local offset = arshift(p[0], 2) 316 | local data_word_count = band(p[1], 0xffff) 317 | local pointer_count = rshift(p[1], 16) 318 | 319 | return offset, data_word_count, pointer_count 320 | end 321 | 322 | function _M.write_structp(p32, T, data_off) 323 | p32 = cast(pint32, p32) 324 | p32[0] = lshift(data_off, 2) 325 | p32[1] = lshift(T.pointerCount, 16) + T.dataWordCount 326 | end 327 | 328 | function _M.write_structp_buf(p32, T, TSub, offset, data_off) 329 | p32 = cast(pint32, p32) 330 | local base = T.dataWordCount * 2 + offset * 2 331 | p32[base] = lshift(data_off, 2) 332 | p32[base + 1] = lshift(TSub.pointerCount, 16) + TSub.dataWordCount 333 | end 334 | 335 | 336 | function _M.get_enum_name(v, default, enum_schema, name) 337 | v = bxor(v, default) -- starts from 0 338 | local r = enum_schema[v] 339 | if not r then 340 | error((name or "") .. " Unknown enum val:" .. v .. ", out of range") 341 | end 342 | return r 343 | end 344 | 345 | function _M.get_enum_val(v, default, enum_schema, name) 346 | local t = type(v) 347 | 348 | if t == "number" then 349 | -- we don't check upper boundary here because max enum value may 350 | -- increase in later schema 351 | if v >= 0 then 352 | return v 353 | end 354 | return default 355 | end 356 | 357 | if t ~= "string" or v == "" then 358 | return default 359 | end 360 | 361 | local r = enum_schema[v] 362 | if not r then 363 | print(name, " Unknown enum val: " .. v) 364 | --error("Unknown enum val:" .. v) 365 | return default 366 | end 367 | return r 368 | end 369 | 370 | function _M.read_listp(p32, header) 371 | local val0 = p32[0] 372 | local val1 = p32[1] 373 | 374 | if val0 == 0 and val1 == 0 then 375 | return 376 | end 377 | 378 | local sig = band(val0, 0x03) 379 | if sig == 1 then 380 | local offset = arshift(val0, 2) 381 | 382 | local size_type = band(val1, 0x07) 383 | local num = rshift(val1, 3) 384 | 385 | return offset, size_type, num 386 | -- return read_list_pointer(p32) 387 | elseif sig == 2 then 388 | --print("single far pointer") 389 | return _M.read_far_pointer(p32, header, _M.read_listp) 390 | else 391 | error("corrupt data, expected list signature 1 or far pointer 2, " .. 392 | "but have " .. sig) 393 | end 394 | end 395 | 396 | -- write list pointer to a pointed memory 397 | -- @param p32 32 bit pointer 398 | -- @param size_type element size type 399 | -- @param num number of elements 400 | -- @param data_off data offset of this list pointer 401 | function _M.write_listp(p32, size_type, num, data_off) 402 | p32[0] = lshift(data_off, 2) + 1 403 | p32[1] = lshift(num, 3) + size_type 404 | end 405 | 406 | function _M.write_listp_buf(p32, T, offset, size_type, num, data_off) 407 | p32 = cast(pint32, p32) 408 | local base = T.dataWordCount * 2 + offset * 2 409 | 410 | p32[base] = lshift(data_off, 2) + 1 411 | p32[base + 1] = lshift(num, 3) + size_type 412 | end 413 | 414 | --- map size type to its size 415 | -- @see http://kentonv.github.io/_Mroto/encoding.html#lists 416 | local list_size_map = { 417 | [0] = 0, 418 | [1] = 0.125, 419 | [2] = 1, 420 | [3] = 2, 421 | [4] = 4, 422 | [5] = 8, 423 | [6] = 8, 424 | -- 7 = ?, 425 | } 426 | 427 | local type_to_size_type = { 428 | int8 = 2, 429 | int16 = 3, 430 | int32 = 4, 431 | int64 = 5, 432 | uint8 = 2, 433 | uint16 = 3, 434 | uint32 = 4, 435 | uint64 = 5, 436 | bool = 1, 437 | float32 = 4, 438 | float64 = 5, 439 | list = 6, 440 | text = 6, 441 | data = 6, 442 | struct = 7, -- composite 443 | } 444 | 445 | --- Write list data (not including list pointer) 446 | -- @param p32 write list data to this position 447 | -- @return space consumed in bytes 448 | -- space is allocated by itself 449 | function _M.write_list_data(p32, data, pos, elm_type, ...) 450 | local start = pos 451 | if not elm_type then 452 | return 0 453 | end 454 | local len = #data 455 | if elm_type == "list" then 456 | pos = pos + len * 8 457 | for i = 1, len do 458 | pos = pos + _M.write_list(p32 + (i - 1) * 2, data[i], pos - 459 | 8 * (i - 1), elm_type, ...) 460 | end 461 | elseif elm_type == "text" then 462 | pos = pos + 8 * len 463 | for i = 1, len do 464 | 465 | local data_off = (pos - i * 8) / 8 -- pos is in bytes 466 | pos = pos + _M.write_text(p32 + (i - 1) * 2, data[i], data_off, 467 | false) 468 | end 469 | elseif elm_type == "data" then 470 | pos = pos + 8 * len 471 | for i = 1, len do 472 | local data_off = (pos - i * 8) / 8 -- pos is in bytes 473 | local data_len = #data[i] 474 | _M.write_listp(p32 + (i - 1) * 2, 2, data_len, data_off) 475 | pos = pos + _M.write_text_data(p32 + pos / 4, data[i], true) 476 | end 477 | elseif elm_type == "struct" then 478 | local T = ... 479 | 480 | _M.write_composite_tag(p32 + pos / 4, T, len) 481 | pos = pos + 8 482 | local offset = pos 483 | 484 | local struct_size = (T.dataWordCount + T.pointerCount) * 8 485 | pos = pos + struct_size * len 486 | for i = 1, len do 487 | local sp32 = p32 + offset / 4 488 | local new_pos = pos - offset-- - offset 489 | 490 | local ssize = T.flat_serialize(data[i], sp32, new_pos) 491 | 492 | pos = pos + ssize - struct_size 493 | offset = offset + struct_size 494 | end 495 | elseif elm_type == "bool" then 496 | local p = get_pointer_from_type(p32, elm_type) 497 | for i = 1, len do 498 | local n, s = get_bit_offset(i - 1, 8) 499 | _M.write_bit(p + n, data[i], s) 500 | end 501 | return round8(len / 8) 502 | else 503 | local p = get_pointer_from_type(p32, elm_type) 504 | for i = 1, len do 505 | -- No default value available from AST, so no need to pass 506 | -- default value 507 | _M.write_num(p + i - 1, data[i], elm_type) 508 | end 509 | local size = assert(list_size_map[type_to_size_type[elm_type]]) 510 | return round8(size * len) 511 | end 512 | return pos - start 513 | end 514 | 515 | --- Write a list (including list pointer and list data) 516 | -- space for list pointer is allocated from outside 517 | -- @param p32 518 | -- @param pos free space offset from p32 519 | function _M.write_list(p32, data, pos, typ, ...) 520 | local size 521 | local data_off = (pos - 8) / 8 --get_data_off(parentT, offset, pos) 522 | 523 | local elm_type, T = ... 524 | local size_type = type_to_size_type[elm_type] 525 | if not size_type then 526 | error("unknown eml_type: " .. elm_type) 527 | end 528 | 529 | local num = #data 530 | 531 | local dp32 = p32 + pos / 4 532 | --size = size + len * 8 533 | size = _M.write_list_data(dp32, data, 0, ...) 534 | 535 | if elm_type == "struct" then 536 | -- When size_type = 7, section (D) of the list pointer – which normally 537 | -- would store this element count – instead stores the total number of 538 | -- words in the list (not counting the tag word). 539 | num = (T.dataWordCount + T.pointerCount) * num 540 | end 541 | --write_listp_buf(p32, parentT, offset, size_type, len, data_off) 542 | _M.write_listp(p32, size_type, num, data_off) 543 | --print("write list done") 544 | return size 545 | end 546 | 547 | --- read data part of a list 548 | -- @param p start of data buffer 549 | -- @param header stream header, see http://kentonv.github.io/capnproto/encoding.html#serialization_over_a_stream 550 | -- @param num number of elements in this list 551 | -- @param elm_type elememt type: "int32", "data", "list", etc. 552 | function _M.read_list_data(p32, header, num, elm_type, ...) 553 | p32 = cast(puint32, p32) 554 | if not elm_type then 555 | return 556 | end 557 | 558 | local t = new_tab(num, 0) 559 | 560 | if elm_type == "list" then 561 | -- print("list data: list") 562 | for i = 1, num do 563 | local off, child_size, child_num = _M.read_listp_list(p32, 564 | header, i) 565 | if off and child_num then 566 | t[i] = _M.read_list_data(p32 + (i + off) * 2, header, 567 | child_num, ...) 568 | end 569 | end 570 | 571 | elseif elm_type == "text" then 572 | -- print("list data: text: ", num) 573 | for i = 1, num do 574 | local off, child_size, child_num = _M.read_listp_list(p32, 575 | header, i) 576 | --print(off, child_size, child_num) 577 | if off and child_num then 578 | t[i] = _M.read_text_data(p32 + (i + off) * 2, child_num - 1) 579 | end 580 | end 581 | elseif elm_type == "data" then 582 | -- print("list data: data") 583 | for i = 1, num do 584 | local off, child_size, child_num = _M.read_listp_list(p32, 585 | header, i) 586 | if off and child_num then 587 | t[i] = _M.read_text_data(p32 + (i + off) * 2, child_num) 588 | end 589 | end 590 | elseif elm_type == "struct" then 591 | -- the number of struct elements in a list stores in tag value 592 | -- and num (from list pointer, normally would be element count) is total 593 | -- number of words in the list (not including the tag word) 594 | local real_num, dt, pt = _M.read_composite_tag(p32) 595 | local T = ... 596 | local struct_size = (dt + pt) * 8 597 | for i = 1, real_num do 598 | -- TODO reuse table 599 | t[i] = new_tab(0, 8) 600 | T.parse_struct_data(p32 + 2 + (i - 1) * struct_size / 4, dt, 601 | pt, header, assert(t[i])) 602 | end 603 | else 604 | --[[ 605 | local size = list_size_map[size_type] 606 | if not size then 607 | error("corrupt data, unknown size type: " .. size_type) 608 | end 609 | 610 | size = size * 8 611 | ]] 612 | p32 = get_pointer_from_type(p32, elm_type) 613 | 614 | for i = 1, num do 615 | t[i] = p32[i - 1] 616 | end 617 | 618 | end 619 | return t 620 | end 621 | 622 | -- @index: start from 1 623 | function _M.read_listp_list(p32, header, index) 624 | return _M.read_listp(p32 + (index - 1) * 2, header) 625 | end 626 | 627 | function _M.read_listp_struct(buf, header, T, offset) 628 | local p = cast(pint32, buf) 629 | local base = T.dataWordCount * 2 + offset * 2 630 | 631 | return _M.read_listp(p + base, header) 632 | end 633 | 634 | function _M.read_far_pointer(buf, header, parser) 635 | local p = buf 636 | 637 | local landing = rshift(band(p[0], 0x04), 2) 638 | 639 | assert(landing == 0, "double far pointer not supported yet") 640 | local offset = rshift(p[0], 3) 641 | local seg_id = tonumber(p[1]) 642 | --print("landing, offset, seg_id:", landing, offset, seg_id) 643 | 644 | -- object pointer offset 645 | local op_offset = header.header_size 646 | for i=1, seg_id do 647 | op_offset = op_offset + header.seg_sizes[i] 648 | end 649 | op_offset = op_offset + offset -- offset is in words 650 | local pp = header.base + op_offset * 2 -- header.base is uint32_t * 651 | 652 | local p_offset, r1, r2 = parser(pp, header) 653 | p_offset = p_offset + (pp - p) / 2 -- p and pp are uint32_t * 654 | 655 | return p_offset, r1, r2 656 | end 657 | 658 | function _M.read_struct_buf(p32, header) 659 | local p = p32 660 | if p[0] == 0 and p[1] == 0 then 661 | -- not set 662 | return 663 | end 664 | 665 | local sig = band(p[0], 0x03) 666 | 667 | if sig == 0 then 668 | return _M.read_struct_pointer(p) 669 | elseif sig == 2 then 670 | return _M.read_far_pointer(p, header, _M.read_struct_pointer) 671 | else 672 | error("corrupt data, expected struct signature 0 or far pointer 2, " .. 673 | "but have " .. sig) 674 | end 675 | end 676 | 677 | 678 | return _M 679 | -------------------------------------------------------------------------------- /capnp/compile.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------- 2 | -- lua-capnproto compiler 3 | -- @copyright 2013-2016 Jiale Zhi (vipcalio@gmail.com) 4 | ----------------------------------------------------------------- 5 | 6 | local cjson = require("cjson") 7 | local encode = cjson.encode 8 | local util = require "capnp.util" 9 | 10 | local insert = table.insert 11 | local concat = table.concat 12 | local format = string.format 13 | local lower = string.lower 14 | local gsub = string.gsub 15 | local sub = string.sub 16 | 17 | local debug = false 18 | local NOT_UNION = 65535 19 | 20 | local _M = {} 21 | 22 | local missing_enums = {} 23 | 24 | local config = { 25 | default_naming_func = util.lower_underscore_naming, 26 | default_enum_naming_func = util.upper_underscore_naming, 27 | } 28 | 29 | 30 | function _M.set_debug(_debug) 31 | debug = _debug 32 | end 33 | 34 | function dbg(...) 35 | if not debug then 36 | return 37 | end 38 | print(...) 39 | end 40 | 41 | function dbgf(...) 42 | if not debug then 43 | return 44 | end 45 | print(format(...)) 46 | end 47 | 48 | function get_schema_text(file) 49 | local f = io.open(file) 50 | if not f then 51 | return nil, "Can't open file: " .. tostring(file) 52 | end 53 | 54 | local s = f:read("*a") 55 | f:close() 56 | 57 | s = string.gsub(s, "%(", "{") 58 | s = string.gsub(s, "%)", "}") 59 | s = string.gsub(s, "%[", "{") 60 | s = string.gsub(s, "%]", "}") 61 | s = string.gsub(s, "%<", "\"") 62 | s = string.gsub(s, "%>", "\"") 63 | s = string.gsub(s, "id = (%d+)", "id = \"%1\"") 64 | s = string.gsub(s, "typeId = (%d+)", "typeId = \"%1\"") 65 | s = string.gsub(s, "void", "\"void\"") 66 | return "return " .. s 67 | end 68 | 69 | function comp_header(res, nodes) 70 | dbg("compile_headers") 71 | insert(res, format([[ 72 | -- Generated by lua-capnproto %s on %s 73 | -- https://github.com/cloudflare/lua-capnproto.git 74 | 75 | 76 | ]], config.version, os.date())) 77 | insert(res, format([[ 78 | local ffi = require "ffi" 79 | local capnp = require "capnp" 80 | local bit = require "bit" 81 | 82 | local ceil = math.ceil 83 | local write_struct_field= capnp.write_struct_field 84 | local read_struct_field = capnp.read_struct_field 85 | local read_text = capnp.read_text 86 | local write_text = capnp.write_text 87 | local get_enum_val = capnp.get_enum_val 88 | local get_enum_name = capnp.get_enum_name 89 | local get_data_off = capnp.get_data_off 90 | local write_listp_buf = capnp.write_listp_buf 91 | local write_structp_buf = capnp.write_structp_buf 92 | local write_structp = capnp.write_structp 93 | local read_struct_buf = capnp.read_struct_buf 94 | local read_listp_struct = capnp.read_listp_struct 95 | local read_list_data = capnp.read_list_data 96 | local write_list = capnp.write_list 97 | local write_list_data = capnp.write_list_data 98 | local ffi_new = ffi.new 99 | local ffi_string = ffi.string 100 | local ffi_cast = ffi.cast 101 | local ffi_copy = ffi.copy 102 | local ffi_fill = ffi.fill 103 | local ffi_typeof = ffi.typeof 104 | local band, bor, bxor = bit.band, bit.bor, bit.bxor 105 | 106 | local pint8 = ffi_typeof("int8_t *") 107 | local pint16 = ffi_typeof("int16_t *") 108 | local pint32 = ffi_typeof("int32_t *") 109 | local pint64 = ffi_typeof("int64_t *") 110 | local puint8 = ffi_typeof("uint8_t *") 111 | local puint16 = ffi_typeof("uint16_t *") 112 | local puint32 = ffi_typeof("uint32_t *") 113 | local puint64 = ffi_typeof("uint64_t *") 114 | local pbool = ffi_typeof("uint8_t *") 115 | local pfloat32 = ffi_typeof("float *") 116 | local pfloat64 = ffi_typeof("double *") 117 | 118 | 119 | local ok, new_tab = pcall(require, "table.new") 120 | 121 | if not ok then 122 | new_tab = function (narr, nrec) return {} end 123 | end 124 | 125 | local round8 = function(size) 126 | return ceil(size / 8) * 8 127 | end 128 | 129 | local str_buf 130 | local default_segment_size = 4096 131 | 132 | local function get_str_buf(size) 133 | if size > default_segment_size then 134 | return ffi_new("char[?]", size) 135 | end 136 | 137 | if not str_buf then 138 | str_buf = ffi_new("char[?]", default_segment_size) 139 | end 140 | return str_buf 141 | end 142 | 143 | -- Estimated from #nodes, not accurate 144 | local _M = new_tab(0, %d) 145 | 146 | ]], #nodes)) 147 | end 148 | 149 | function get_name(display_name) 150 | local n = string.find(display_name, ":") 151 | return string.sub(display_name, n + 1) 152 | end 153 | 154 | --- @see http://kentonv.github.io/_Mroto/encoding.html#lists 155 | local list_size_map = { 156 | [0] = 0, 157 | [1] = 0.125, 158 | [2] = 1, 159 | [3] = 2, 160 | [4] = 4, 161 | [5] = 8, 162 | [6] = 8, 163 | -- 7 = ?, 164 | } 165 | 166 | local size_map = { 167 | void = 0, 168 | bool = 1, 169 | int8 = 8, 170 | int16 = 16, 171 | int32 = 32, 172 | int64 = 64, 173 | uint8 = 8, 174 | uint16 = 16, 175 | uint32 = 32, 176 | uint64 = 64, 177 | float32 = 32, 178 | float64 = 64, 179 | text = "2", -- list(uint8) 180 | data = "2", 181 | list = 2, -- size: list item size id, not actual size 182 | struct = 8, -- struct pointer 183 | enum = 16, 184 | object = 8, -- FIXME object is a pointer ? 185 | anyPointer = 8, -- FIXME object is a pointer ? 186 | group = 0, -- TODO 187 | } 188 | 189 | local check_type = { 190 | struct = 'type(value) == "table"', 191 | group = 'type(value) == "table"', 192 | enum = 'type(value) == "string" or type(value) == "number"', 193 | list = 'type(value) == "table"', 194 | text = 'type(value) == "string"', 195 | data = 'type(value) == "string"', 196 | } 197 | 198 | 199 | function get_size(type_name) 200 | local size = size_map[type_name] 201 | if not size then 202 | error("Unknown type_name:" .. type_name) 203 | end 204 | 205 | return size 206 | end 207 | 208 | function _set_field_default(nodes, field, slot) 209 | local default 210 | if slot.defaultValue 211 | and field.type_name ~= "object" 212 | and field.type_name ~= "anyPointer" 213 | then 214 | 215 | for k, v in pairs(slot.defaultValue) do 216 | if field.type_name == "bool" then 217 | field.print_default_value = v and 1 or 0 218 | field.default_value = field.print_default_value 219 | elseif field.type_name == "text" or field.type_name == "data" then 220 | field.print_default_value = '"' .. v .. '"' 221 | field.default_value = field.print_default_value 222 | elseif field.type_name == "struct" or field.type_name == "list" 223 | or field.type_name == "object" 224 | or field.type_name == "anyPointer" 225 | then 226 | field.print_default_value = '"' .. v .. '"' 227 | elseif field.type_name == "void" then 228 | field.print_default_value = "\"Void\"" 229 | field.default_value = field.print_default_value 230 | elseif field.type_name == "enum" then 231 | local enum = assert(nodes[slot["type"].enum.typeId].enum) 232 | field.print_default_value = '"' .. enum.enumerants[v + 1].name 233 | .. '"' 234 | 235 | field.default_value = v 236 | else 237 | field.print_default_value = v 238 | field.default_value = field.print_default_value 239 | end 240 | break 241 | end 242 | dbgf("[%s] %s.print_default_value=%s", field.type_name, field.name, 243 | field.print_default_value) 244 | end 245 | if not field.default_value then 246 | field.default_value = "Nil" 247 | end 248 | if not field.print_default_value then 249 | field.print_default_value = "Nil" 250 | end 251 | end 252 | 253 | function _get_type(type_field) 254 | local type_name 255 | for k, v in pairs(type_field) do 256 | type_name = k 257 | break 258 | end 259 | return type_name 260 | end 261 | 262 | function _set_field_type(field, slot, nodes) 263 | local type_name 264 | if field.group then 265 | field.type_name = "group" 266 | field.type_display_name = get_name( 267 | nodes[field.group.typeId].displayName) 268 | else 269 | for k, v in pairs(slot["type"]) do 270 | type_name = k 271 | if type_name == "struct" then 272 | field.type_display_name = get_name(nodes[v.typeId].displayName) 273 | elseif type_name == "enum" then 274 | field.enum_id = v.typeId 275 | field.type_display_name = get_name(nodes[v.typeId].displayName) 276 | elseif type_name == "list" then 277 | local list_type 278 | for k, v in pairs(field.slot["type"].list.elementType) do 279 | list_type = k 280 | if list_type == "struct" then 281 | field.type_display_name = get_name( 282 | nodes[v.typeId].displayName) 283 | end 284 | break 285 | end 286 | field.element_type = list_type 287 | else 288 | -- default = v 289 | end 290 | 291 | field.type_name = type_name 292 | --field.default = default 293 | 294 | break 295 | end 296 | end 297 | dbgf("field %s.type_name = %s", field.name, field.type_name) 298 | assert(field.type_name) 299 | end 300 | 301 | function comp_field(res, nodes, field) 302 | dbg("comp_field") 303 | local slot = field.slot 304 | if not slot then 305 | slot = {} 306 | field.slot = slot 307 | end 308 | if not slot.offset then 309 | slot.offset = 0 310 | end 311 | 312 | field.name = config.default_naming_func(field.name) 313 | 314 | _set_field_type(field, slot, nodes) 315 | _set_field_default(nodes, field, slot) 316 | 317 | -- print("default:", field.name, field.default_value) 318 | if not field.type_name then 319 | field.type_name = "void" 320 | field.size = 0 321 | else 322 | field.size = get_size(field.type_name) 323 | end 324 | end 325 | 326 | local function process_list_type(list_type, nodes) 327 | -- first one is not element type, so remove it 328 | --table.remove(list_type, 1) 329 | if list_type[#list_type - 1] == "struct" then 330 | local id = list_type[#list_type] 331 | local struct_name = get_name(nodes[id].displayName) 332 | for i=1, #list_type - 1 do 333 | list_type[i] = '"' .. list_type[i] .. '"' 334 | end 335 | list_type[#list_type] = "_M." .. struct_name 336 | else 337 | for i, v in ipairs(list_type) do 338 | list_type[i] = '"' .. v .. '"' 339 | end 340 | end 341 | end 342 | 343 | function comp_parse_struct_data(res, nodes, struct, fields, size, name) 344 | insert(res, format([[ 345 | 346 | parse_struct_data = function(p32, data_word_count, pointer_count, header, 347 | tab) 348 | 349 | local s = tab 350 | ]], size)) 351 | 352 | if struct.discriminantCount and struct.discriminantCount > 0 then 353 | insert(res, format([[ 354 | 355 | local dscrm = _M.%s.which(p32, %d)]], name, struct.discriminantOffset)) 356 | end 357 | 358 | for i, field in ipairs(fields) do 359 | if field.discriminantValue and field.discriminantValue ~= NOT_UNION then 360 | insert(res, format([[ 361 | 362 | -- union 363 | if dscrm == %d then 364 | ]],field.discriminantValue)) 365 | 366 | end 367 | if field.group then 368 | insert(res, format([[ 369 | 370 | -- group 371 | if not s["%s"] then 372 | s["%s"] = new_tab(0, 4) 373 | end 374 | _M.%s["%s"].parse_struct_data(p32, _M.%s.dataWordCount, 375 | _M.%s.pointerCount, header, s["%s"]) 376 | ]], field.name, field.name, name, field.name, name, name, field.name)) 377 | 378 | elseif field.type_name == "enum" then 379 | insert(res, format([[ 380 | 381 | -- enum 382 | local val = read_struct_field(p32, "uint16", %d, %d) 383 | s["%s"] = get_enum_name(val, %d, _M.%sStr) 384 | ]], field.size, field.slot.offset, field.name, field.default_value, 385 | field.type_display_name)) 386 | 387 | elseif field.type_name == "list" then 388 | local off = field.slot.offset 389 | local list_type = util.get_field_type(field) 390 | table.remove(list_type, 1) 391 | process_list_type(list_type, nodes) 392 | 393 | local types = concat(list_type, ", ") 394 | 395 | insert(res, format([[ 396 | 397 | -- list 398 | local off, size, num = read_listp_struct(p32, header, _M.%s, %d) 399 | if off and num then 400 | -- dataWordCount + offset + pointerSize + off 401 | s["%s"] = read_list_data(p32 + (%d + %d + 1 + off) * 2, header, 402 | num, %s) 403 | else 404 | s["%s"] = nil 405 | end 406 | ]], name, off, field.name, struct.dataWordCount, off, types, field.name)) 407 | 408 | elseif field.type_name == "struct" then 409 | local off = field.slot.offset 410 | 411 | insert(res, format([[ 412 | 413 | -- struct 414 | local p = p32 + (%d + %d) * 2 -- p32, dataWordCount, offset 415 | local off, dw, pw = read_struct_buf(p, header) 416 | if off and dw and pw then 417 | if not s["%s"] then 418 | s["%s"] = new_tab(0, 2) 419 | end 420 | _M.%s.parse_struct_data(p + 2 + off * 2, dw, pw, header, s["%s"]) 421 | else 422 | s["%s"] = nil 423 | end 424 | ]], struct.dataWordCount, off, field.name, field.name, field.type_display_name, 425 | field.name, field.name)) 426 | 427 | elseif field.type_name == "text" then 428 | local off = field.slot.offset 429 | insert(res, format([[ 430 | 431 | -- text 432 | local off, size, num = read_listp_struct(p32, header, _M.%s, %d) 433 | if off and num then 434 | -- dataWordCount + offset + pointerSize + off 435 | local p8 = ffi_cast(pint8, p32 + (%d + %d + 1 + off) * 2) 436 | s["%s"] = ffi_string(p8, num - 1) 437 | else 438 | s["%s"] = nil 439 | end 440 | ]], name, off, struct.dataWordCount, off, field.name, field.name)) 441 | 442 | elseif field.type_name == "data" then 443 | local off = field.slot.offset 444 | insert(res, format([[ 445 | 446 | -- data 447 | local off, size, num = read_listp_struct(p32, header, _M.%s, %d) 448 | if off and num then 449 | -- dataWordCount + offset + pointerSize + off 450 | local p8 = ffi_cast(pint8, p32 + (%d + %d + 1 + off) * 2) 451 | s["%s"] = ffi_string(p8, num) 452 | else 453 | s["%s"] = nil 454 | end 455 | ]], name, off, struct.dataWordCount, off, field.name, field.name)) 456 | 457 | elseif field.type_name == "anyPointer" then 458 | -- TODO support anyPointer 459 | elseif field.type_name == "void" then 460 | insert(res, format([[ 461 | 462 | s["%s"] = "Void"]], field.name)) 463 | else 464 | local default = field.default_value and field.default_value or "nil" 465 | insert(res, format([[ 466 | 467 | s["%s"] = read_struct_field(p32, "%s", %d, %d, %s) 468 | ]], field.name, field.type_name, field.size, field.slot.offset, default)) 469 | 470 | end 471 | 472 | if field.discriminantValue and field.discriminantValue ~= NOT_UNION then 473 | insert(res, format([[ 474 | 475 | else 476 | s["%s"] = nil 477 | end 478 | ]],field.name)) 479 | end 480 | end 481 | 482 | insert(res, [[ 483 | 484 | return s 485 | end, 486 | ]]) 487 | end 488 | 489 | function comp_parse(res, name) 490 | insert(res, format([[ 491 | 492 | parse = function(bin, tab) 493 | if #bin < 16 then 494 | return nil, "message too short" 495 | end 496 | 497 | local header = new_tab(0, 4) 498 | local p32 = ffi_cast(puint32, bin) 499 | header.base = p32 500 | 501 | local nsegs = p32[0] + 1 502 | header.seg_sizes = {} 503 | for i=1, nsegs do 504 | header.seg_sizes[i] = p32[i] 505 | end 506 | local pos = round8(4 + nsegs * 4) 507 | header.header_size = pos / 8 508 | p32 = p32 + pos / 4 509 | 510 | if not tab then 511 | tab = new_tab(0, 8) 512 | end 513 | local off, dw, pw = read_struct_buf(p32, header) 514 | if off and dw and pw then 515 | return _M.%s.parse_struct_data(p32 + 2 + off * 2, dw, pw, 516 | header, tab) 517 | else 518 | return nil 519 | end 520 | end, 521 | ]], name)) 522 | end 523 | 524 | function comp_serialize(res, name) 525 | insert(res, format([[ 526 | 527 | -- Serialize and return pointer to char[] and size 528 | serialize_cdata = function(data, p8, size) 529 | if p8 == nil then 530 | size = _M.%s.calc_size(data) 531 | 532 | p8 = get_str_buf(size) 533 | end 534 | ffi_fill(p8, size) 535 | local p32 = ffi_cast(puint32, p8) 536 | 537 | -- Because needed size has been calculated, only 1 segment is needed 538 | p32[0] = 0 539 | p32[1] = (size - 8) / 8 540 | 541 | -- skip header 542 | write_structp(p32 + 2, _M.%s, 0) 543 | 544 | -- skip header & struct pointer 545 | _M.%s.flat_serialize(data, p32 + 4) 546 | 547 | return p8, size 548 | end, 549 | 550 | serialize = function(data, p8, size) 551 | p8, size = _M.%s.serialize_cdata(data, p8, size) 552 | return ffi_string(p8, size) 553 | end, 554 | ]], name, name, name, name)) 555 | end 556 | 557 | function comp_flat_serialize(res, nodes, struct, fields, size, name) 558 | dbgf("comp_flat_serialize") 559 | insert(res, format([[ 560 | 561 | flat_serialize = function(data, p32, pos) 562 | pos = pos and pos or %d -- struct size in bytes 563 | local start = pos 564 | local dscrm]], size)) 565 | 566 | insert(res, [[ 567 | 568 | local value]]) 569 | 570 | for i, field in ipairs(fields) do 571 | insert(res, format([=[ 572 | 573 | 574 | value = data["%s"]]=], field.name)) 575 | --print("comp_field", field.name) 576 | -- union 577 | if field.discriminantValue and field.discriminantValue ~= NOT_UNION then 578 | dbgf("field %s: union", field.name) 579 | insert(res, format([[ 580 | 581 | if value then 582 | dscrm = %d 583 | end 584 | ]], field.discriminantValue)) 585 | end 586 | if field.group then 587 | dbgf("field %s: group", field.name) 588 | -- group size is the same as the struct, so we can use "size" to 589 | -- represent group size 590 | 591 | insert(res, format([[ 592 | 593 | if ]] .. check_type["group"] .. [[ then 594 | -- groups are just namespaces, field offsets are set within parent 595 | -- structs 596 | pos = pos + _M.%s.%s.flat_serialize(value, p32, pos) - %d 597 | end 598 | ]], name, field.name, size)) 599 | 600 | elseif field.type_name == "enum" then 601 | dbgf("field %s: enum", field.name) 602 | insert(res, format([[ 603 | 604 | if ]] .. check_type["enum"] .. [[ then 605 | local val = get_enum_val(value, %d, _M.%s, "%s.%s") 606 | write_struct_field(p32, val, "uint16", %d, %d) 607 | end]], field.default_value, field.type_display_name, name, 608 | field.name, field.size, field.slot.offset)) 609 | 610 | elseif field.type_name == "list" then 611 | dbgf("field %s: list", field.name) 612 | local off = field.slot.offset 613 | local list_type = util.get_field_type(field) 614 | -- nested list 615 | if #list_type > 1 then 616 | -- composite 617 | if list_type[#list_type -1] == "struct" then 618 | field.size = 7 619 | else 620 | -- pointer 621 | field.size = 6 622 | end 623 | end 624 | process_list_type(list_type, nodes) 625 | 626 | local types = concat(list_type, ", ") 627 | 628 | insert(res, format([[ 629 | 630 | if ]] .. check_type["list"] .. [[ then 631 | local data_off = get_data_off(_M.%s, %d, pos) 632 | pos = pos + write_list(p32 + _M.%s.dataWordCount * 2 + %d * 2, 633 | value, (data_off + 1) * 8, %s) 634 | end]], name, off, name, off, types)) 635 | 636 | elseif field.type_name == "struct" then 637 | dbgf("field %s: struct", field.name) 638 | local off = field.slot.offset 639 | insert(res, format([[ 640 | 641 | if ]] .. check_type["struct"] .. [[ then 642 | local data_off = get_data_off(_M.%s, %d, pos) 643 | write_structp_buf(p32, _M.%s, _M.%s, %d, data_off) 644 | local size = _M.%s.flat_serialize(value, p32 + pos / 4) 645 | pos = pos + size 646 | end]], name, off, name, field.type_display_name, 647 | off, field.type_display_name)) 648 | 649 | elseif field.type_name == "text" then 650 | dbgf("field %s: text", field.name) 651 | local off = field.slot.offset 652 | insert(res, format([[ 653 | 654 | if ]] .. check_type["text"] .. [[ then 655 | local data_off = get_data_off(_M.%s, %d, pos) 656 | 657 | local len = #value + 1 658 | write_listp_buf(p32, _M.%s, %d, %d, len, data_off) 659 | 660 | ffi_copy(p32 + pos / 4, value) 661 | pos = pos + round8(len) 662 | end]], name, off, name, off, 2)) 663 | 664 | elseif field.type_name == "data" then 665 | dbgf("field %s: data", field.name) 666 | local off = field.slot.offset 667 | insert(res, format([[ 668 | 669 | if ]] .. check_type["data"] .. [[ then 670 | local data_off = get_data_off(_M.%s, %d, pos) 671 | 672 | local len = #value 673 | write_listp_buf(p32, _M.%s, %d, %d, len, data_off) 674 | 675 | -- prevent copying trailing '\0' 676 | ffi_copy(p32 + pos / 4, value, len) 677 | pos = pos + round8(len) 678 | end]], name, off, name, off, 2)) 679 | 680 | else 681 | dbgf("field %s: %s", field.name, field.type_name) 682 | local default = field.default_value and field.default_value or "nil" 683 | local cdata_condition = "" 684 | if field.type_name == "uint64" or field.type_name == "int64" then 685 | cdata_condition = 'or data_type == "cdata"' 686 | end 687 | if field.type_name ~= "void" then 688 | insert(res, format([[ 689 | 690 | local data_type = type(value) 691 | if (data_type == "number" 692 | or data_type == "boolean" %s) then 693 | 694 | write_struct_field(p32, value, "%s", %d, %d, %s) 695 | end]], cdata_condition, field.type_name, field.size, 696 | field.slot.offset, default)) 697 | end 698 | end 699 | 700 | end 701 | 702 | if struct.discriminantCount and struct.discriminantCount ~= 0 then 703 | insert(res, format([[ 704 | 705 | if dscrm then 706 | --buf, discriminantOffset, discriminantValue 707 | _M.%s.which(p32, %d, dscrm) 708 | end 709 | ]], name, struct.discriminantOffset)) 710 | end 711 | 712 | insert(res, format([[ 713 | 714 | return pos - start + %d 715 | end, 716 | ]], size)) 717 | end 718 | 719 | -- insert a list with indent level 720 | function insertlt(res, level, data_table) 721 | for i, v in ipairs(data_table) do 722 | insertl(res, level, v) 723 | end 724 | end 725 | 726 | -- insert with indent level 727 | function insertl(res, level, data) 728 | for i=1, level * 4 do 729 | insert(res, " ") 730 | end 731 | insert(res, data) 732 | end 733 | 734 | function _M.comp_calc_list_size(res, field, nodes, name, level, elm_type, ...) 735 | if not elm_type then 736 | return 737 | end 738 | 739 | insertl(res, level, format("if %s and " .. 740 | "type(%s) == \"table\" then\n", name, name)) 741 | 742 | if elm_type == "object" or elm_type == "anyPointer" 743 | or elm_type == "group" then 744 | 745 | error("List of object/anyPointer/group type is not supported yet.") 746 | end 747 | 748 | if elm_type ~= "struct" and elm_type ~= "list" and elm_type ~= "data" 749 | and elm_type ~= "text" then 750 | 751 | -- elm_type is a plain type. 752 | local elm_size = get_size(elm_type) / 8 753 | insertlt(res, level + 1, { 754 | "-- num * acutal size\n", 755 | format("size = size + round8(#%s * %d)\n", 756 | name, elm_size) 757 | }) 758 | else 759 | -- struct tag 760 | if elm_type == "struct" then 761 | insertl(res, level + 1, format("size = size + 8\n")) 762 | end 763 | 764 | local new_name = name .. "[i" .. level .. "]" 765 | -- calculate body size 766 | insertl(res, level + 1, format("local num%d = #%s\n", 767 | level, name)) 768 | insertl(res, level + 1, format("for %s=1, num%d do\n", 769 | "i" .. level, level)) 770 | 771 | if elm_type == "list" then 772 | insertl(res, level + 2, format("size = size + 8\n")) 773 | _M.comp_calc_list_size(res, field, nodes, new_name, level + 2, ...) 774 | elseif elm_type == "text" then 775 | insertl(res, level + 2, format("size = size + 8\n")) 776 | insertlt(res, level + 2, { 777 | " -- num * acutal size\n", 778 | format("size = size + round8(#%s * 1 + 1)\n", new_name) 779 | }) 780 | elseif elm_type == "data" then 781 | insertl(res, level + 2, format("size = size + 8\n")) 782 | insertlt(res, level + 2, { 783 | " -- num * acutal size\n", 784 | format("size = size + round8(#%s * 1)\n", new_name) 785 | }) 786 | elseif elm_type == "struct" then 787 | local id = ... 788 | local struct_name = get_name(nodes[id].displayName) 789 | insertl(res, level + 2, format( 790 | "size = size + _M.%s.calc_size_struct(%s)\n", 791 | struct_name, new_name)) 792 | end 793 | insertl(res, level + 1, "end\n") 794 | end 795 | insertl(res, level, "end") 796 | end 797 | 798 | function comp_calc_size(res, fields, size, name, nodes, is_group) 799 | dbgf("comp_calc_size") 800 | if is_group then 801 | size = 0 802 | end 803 | insert(res, format([[ 804 | 805 | calc_size_struct = function(data) 806 | local size = %d]], size)) 807 | 808 | insert(res, [[ 809 | 810 | local value]]) 811 | 812 | for i, field in ipairs(fields) do 813 | dbgf("field %s is %s", field.name, field.type_name) 814 | 815 | if field.type_name == "list" then 816 | local list_type = util.get_field_type(field) 817 | 818 | insert(res, "\n") 819 | -- list_type[1] must be "list" and should be skipped because is 820 | -- is not element type 821 | insert(res, " -- list\n") 822 | _M.comp_calc_list_size(res, field, nodes, 823 | format("data[\"%s\"]", field.name), 2, 824 | select(2, unpack(list_type))) 825 | elseif field.type_name == "struct" or field.type_name == "group" then 826 | insert(res, format([[ 827 | 828 | -- struct 829 | value = data["%s"] 830 | if ]] .. check_type["struct"] .. [[ then 831 | size = size + _M.%s.calc_size_struct(value) 832 | end]], field.name, field.type_display_name)) 833 | 834 | elseif field.type_name == "text" then 835 | insert(res, format([[ 836 | 837 | -- text 838 | value = data["%s"] 839 | if ]] .. check_type["text"] .. [[ then 840 | -- size 1, including trailing NULL 841 | size = size + round8(#value + 1) 842 | end]], field.name)) 843 | 844 | elseif field.type_name == "data" then 845 | insert(res, format([[ 846 | 847 | -- data 848 | value = data["%s"] 849 | if ]] .. check_type["data"] .. [[ then 850 | size = size + round8(#value) 851 | end]], field.name)) 852 | 853 | end 854 | 855 | end 856 | 857 | insert(res, format([[ 858 | 859 | return size 860 | end, 861 | 862 | calc_size = function(data) 863 | local size = 16 -- header + root struct pointer 864 | return size + _M.%s.calc_size_struct(data) 865 | end, 866 | ]], name)) 867 | end 868 | 869 | function comp_which(res) 870 | insert(res, [[ 871 | 872 | which = function(buf, offset, n) 873 | if n then 874 | -- set value 875 | write_struct_field(buf, n, "uint16", 16, offset) 876 | else 877 | -- get value 878 | return read_struct_field(buf, "uint16", 16, offset) 879 | end 880 | end, 881 | ]]) 882 | end 883 | 884 | function comp_fields(res, nodes, node, struct) 885 | insert(res, [[ 886 | 887 | fields = { 888 | ]]) 889 | for i, field in ipairs(struct.fields) do 890 | comp_field(res, nodes, field) 891 | if field.group then 892 | if not node.nestedNodes then 893 | node.nestedNodes = {} 894 | end 895 | insert(node.nestedNodes, 896 | { name = field.name, id = field.group.typeId }) 897 | end 898 | insert(res, format([[ 899 | { name = "%s", default = %s, ["type"] = "%s" }, 900 | ]], field.name, field.print_default_value, field.type_name)) 901 | end 902 | dbg("struct:", name) 903 | insert(res, format([[ 904 | }, 905 | ]])) 906 | end 907 | 908 | function comp_struct(res, nodes, node, struct, name) 909 | 910 | if not struct.dataWordCount then 911 | struct.dataWordCount = 0 912 | end 913 | if not struct.pointerCount then 914 | struct.pointerCount = 0 915 | end 916 | 917 | insert(res, " dataWordCount = ") 918 | insert(res, struct.dataWordCount) 919 | insert(res, ",\n") 920 | 921 | insert(res, " pointerCount = ") 922 | insert(res, struct.pointerCount) 923 | insert(res, ",\n") 924 | 925 | if struct.discriminantCount then 926 | insert(res, " discriminantCount = ") 927 | insert(res, struct.discriminantCount) 928 | insert(res, ",\n") 929 | end 930 | if struct.discriminantOffset then 931 | insert(res, " discriminantOffset = ") 932 | insert(res, struct.discriminantOffset) 933 | insert(res, ",\n") 934 | end 935 | if struct.isGroup then 936 | insert(res, " isGroup = true,\n") 937 | end 938 | 939 | struct.size = struct.dataWordCount * 8 + struct.pointerCount * 8 940 | 941 | if struct.fields then 942 | insert(res, " field_count = ") 943 | insert(res, #struct.fields) 944 | insert(res, ",\n") 945 | 946 | comp_fields(res, nodes, node, struct) 947 | --if not struct.isGroup then 948 | 949 | --end 950 | comp_calc_size(res, struct.fields, struct.size, 951 | struct.type_name, nodes, struct.isGroup) 952 | comp_flat_serialize(res, nodes, struct, struct.fields, struct.size, 953 | struct.type_name) 954 | if not struct.isGroup then 955 | comp_serialize(res, struct.type_name) 956 | end 957 | if struct.discriminantCount and struct.discriminantCount > 0 then 958 | comp_which(res) 959 | end 960 | comp_parse_struct_data(res, nodes, struct, struct.fields, 961 | struct.size, struct.type_name) 962 | if not struct.isGroup then 963 | comp_parse(res, struct.type_name) 964 | end 965 | end 966 | end 967 | 968 | function comp_enum(res, nodes, enum, name, enum_naming_func) 969 | if not enum_naming_func then 970 | enum_naming_func = config.default_enum_naming_func 971 | end 972 | 973 | -- string to enum 974 | insert(res, format([[ 975 | 976 | _M.%s = { 977 | ]], name)) 978 | 979 | for i, v in ipairs(enum.enumerants) do 980 | -- inherent parent naming function 981 | v.naming_func = enum_naming_func 982 | 983 | if not v.codeOrder then 984 | v.codeOrder = 0 985 | end 986 | 987 | if v.annotations then 988 | local anno_res = {} 989 | dbgf("%s annotations: %s", name, cjson.encode(v.annotations)) 990 | process_annotations(v.annotations, nodes) 991 | 992 | for i, anno in ipairs(v.annotations) do 993 | if anno.name == "naming" then 994 | v.naming_func = get_naming_func(anno.value) 995 | dbgf("Naming function: %s", anno.value) 996 | if not v.naming_func then 997 | error("Unknown naming annotation: " .. anno.value) 998 | end 999 | elseif anno.name == "literal" then 1000 | dbgf("enumerant literal: %s", anno.value) 1001 | v.literal = anno.value 1002 | end 1003 | end 1004 | end 1005 | 1006 | -- literal has higher priority 1007 | if v.literal then 1008 | insert(res, format(" [\"%s\"] = %s,\n", 1009 | v.literal, v.codeOrder)) 1010 | else 1011 | insert(res, format(" [\"%s\"] = %s,\n", 1012 | v.naming_func(v.name), v.codeOrder)) 1013 | end 1014 | end 1015 | insert(res, "\n}\n") 1016 | 1017 | -- enum to string 1018 | insert(res, format([[ 1019 | 1020 | _M.%sStr = { 1021 | ]], name)) 1022 | 1023 | for i, v in ipairs(enum.enumerants) do 1024 | if not v.codeOrder then 1025 | v.codeOrder = 0 1026 | end 1027 | if v.literal then 1028 | insert(res, format(" [%s] = \"%s\",\n", 1029 | v.codeOrder, v.literal)) 1030 | else 1031 | insert(res, format(" [%s] = \"%s\",\n", 1032 | v.codeOrder, v.naming_func(v.name))) 1033 | end 1034 | end 1035 | insert(res, "\n}\n") 1036 | end 1037 | 1038 | _M.naming_funcs = { 1039 | upper_dash = util.upper_dash_naming, 1040 | lower_underscore = util.lower_underscore_naming, 1041 | upper_underscore = util.upper_underscore_naming, 1042 | camel = util.camel_naming, 1043 | lower_space = util.lower_space_naming, 1044 | } 1045 | 1046 | function process_annotations(annos, nodes) 1047 | dbg("process_annotations:" .. encode(annos)) 1048 | for i, anno in ipairs(annos) do 1049 | local id = anno.id 1050 | anno.name = get_name(nodes[id].displayName) 1051 | anno.value_saved = anno.value 1052 | assert(type(anno.value_saved) == "table", 'expected "table" but got "' 1053 | .. type(anno.value_saved) .. "\": " .. tostring(anno.value_saved)) 1054 | 1055 | for k, v in pairs(anno.value_saved) do 1056 | anno["type"] = k 1057 | anno["value"] = v 1058 | break 1059 | end 1060 | anno.value_saved = nil 1061 | end 1062 | end 1063 | 1064 | function get_naming_func(name) 1065 | local func = _M.naming_funcs[name] 1066 | if not func then 1067 | error("unknown naming: " .. tostring(name)) 1068 | end 1069 | 1070 | return func 1071 | end 1072 | 1073 | function comp_node(res, nodes, node, name) 1074 | dbgf("comp_node: %s, %s", name, node.id) 1075 | if not node then 1076 | print("Ignore node: ", name) 1077 | return 1078 | end 1079 | 1080 | if node.annotation then 1081 | -- do not need to generation any code for annotations 1082 | return 1083 | end 1084 | 1085 | node.name = name 1086 | node.type_name = get_name(node.displayName) 1087 | 1088 | local s = node.struct 1089 | if s then 1090 | 1091 | insert(res, format([[ 1092 | 1093 | _M.%s = { 1094 | ]], name)) 1095 | s.type_name = node.type_name 1096 | insert(res, format([[ 1097 | id = "%s", 1098 | displayName = "%s", 1099 | ]], node.id, node.displayName)) 1100 | comp_struct(res, nodes, node, s, name) 1101 | insert(res, "\n}\n") 1102 | end 1103 | 1104 | local e = node.enum 1105 | if e then 1106 | local anno_res = {} 1107 | if node.annotations then 1108 | process_annotations(node.annotations, nodes) 1109 | end 1110 | 1111 | local naming_func 1112 | if node.annotations then 1113 | for i, anno in ipairs(node.annotations) do 1114 | if anno.name == "naming" then 1115 | naming_func = get_naming_func(anno.value) 1116 | end 1117 | break 1118 | end 1119 | end 1120 | comp_enum(res, nodes, e, name, naming_func) 1121 | end 1122 | 1123 | if node.const then 1124 | dbgf("compile const: %s", name) 1125 | local const = node.const 1126 | local const_type = _get_type(const["type"]) 1127 | 1128 | if const_type == "text" or const_type == "data" or const_type == "void" 1129 | or const_type == "list" or const_type == "struct" 1130 | or const_type == "enum" or const_type == "group" 1131 | or const_type == "anyPointer" 1132 | then 1133 | insert(res, format([[ 1134 | 1135 | _M.%s = "%s" 1136 | ]], name, const.value[const_type])) 1137 | else 1138 | insert(res, format([[ 1139 | 1140 | _M.%s = %s 1141 | ]], name, const.value[const_type])) 1142 | 1143 | end 1144 | end 1145 | 1146 | if node.nestedNodes then 1147 | for i, child in ipairs(node.nestedNodes) do 1148 | comp_node(res, nodes, nodes[child.id], name .. "." .. child.name) 1149 | end 1150 | end 1151 | end 1152 | 1153 | function comp_body(res, schema) 1154 | dbg("comp_body") 1155 | local nodes = schema.nodes 1156 | for i, v in ipairs(nodes) do 1157 | nodes[v.id] = v 1158 | end 1159 | 1160 | local files = schema.requestedFiles 1161 | 1162 | for i, file in ipairs(files) do 1163 | comp_file(res, nodes, file) 1164 | 1165 | local imports = file.imports 1166 | for i, import in ipairs(imports) do 1167 | --import node are compiled later by comp_file 1168 | --comp_import(res, nodes, import) 1169 | check_import(files, import) 1170 | end 1171 | end 1172 | 1173 | for k, v in pairs(missing_enums) do 1174 | insert(res, k .. ".enum_schema = _M." .. 1175 | get_name(nodes[v].displayName .. "\n")) 1176 | end 1177 | 1178 | insert(res, "\nreturn _M\n") 1179 | end 1180 | 1181 | function check_import(files, import) 1182 | local id = import.id 1183 | local name = import.name 1184 | 1185 | for i, file in ipairs(files) do 1186 | if file.id == id then 1187 | return true 1188 | end 1189 | end 1190 | 1191 | error('imported file "' .. name .. '" is missing, compile it together with' 1192 | .. ' other Cap\'n Proto files') 1193 | end 1194 | 1195 | function comp_import(res, nodes, import) 1196 | local id = import.id 1197 | 1198 | dbgf("comp_import: %s", id) 1199 | 1200 | local import_node = nodes[id] 1201 | for i, node in ipairs(import_node.nestedNodes) do 1202 | comp_node(res, nodes, nodes[node.id], node.name) 1203 | end 1204 | end 1205 | 1206 | function comp_file(res, nodes, file) 1207 | dbg("comp_file") 1208 | local id = file.id 1209 | 1210 | local file_node = nodes[id] 1211 | for i, node in ipairs(file_node.nestedNodes) do 1212 | comp_node(res, nodes, nodes[node.id], node.name) 1213 | end 1214 | end 1215 | 1216 | function comp_dg_node(res, nodes, node) 1217 | if not node.struct then 1218 | return 1219 | end 1220 | 1221 | local name = gsub(lower(node.name), "%.", "_") 1222 | insert(res, format([[ 1223 | function gen_%s() 1224 | if rand.random_nil() then 1225 | return nil 1226 | end 1227 | 1228 | ]], name)) 1229 | 1230 | if node.nestedNodes then 1231 | for i, child in ipairs(node.nestedNodes) do 1232 | comp_dg_node(res, nodes, nodes[child.id]) 1233 | end 1234 | end 1235 | 1236 | insert(res, format(" local %s = {}\n", name)) 1237 | for i, field in ipairs(node.struct.fields) do 1238 | if field.group then 1239 | -- TODO group stuffs 1240 | elseif field.type_name == "struct" then 1241 | insert(res, format(" %s.%s = gen_%s()\n", name, 1242 | field.name, 1243 | gsub(lower(field.type_display_name), "%.", "_"))) 1244 | 1245 | elseif field.type_name == "enum" then 1246 | elseif field.type_name == "list" then 1247 | local list_type = field.element_type 1248 | insert(res, format([[ 1249 | %s["%s"] = rand.%s(rand.uint8(), rand.%s)]], name, 1250 | field.name, field.type_name, list_type)) 1251 | 1252 | else 1253 | insert(res, format(' %s["%s"] = rand.%s()\n', name, 1254 | field.name, field.type_name)) 1255 | end 1256 | end 1257 | 1258 | 1259 | insert(res, format([[ 1260 | return %s 1261 | end 1262 | 1263 | ]], name)) 1264 | end 1265 | 1266 | function _M.compile_data_generator(schema) 1267 | local res = {} 1268 | insert(res, [[ 1269 | 1270 | local rand = require("random") 1271 | local cjson = require("cjson") 1272 | local pairs = pairs 1273 | 1274 | local ok, new_tab = pcall(require, "table.new") 1275 | 1276 | if not ok then 1277 | new_tab = function (narr, nrec) return {} end 1278 | end 1279 | 1280 | module(...) 1281 | ]]) 1282 | 1283 | local files = schema.requestedFiles 1284 | local nodes = schema.nodes 1285 | 1286 | for i, file in ipairs(files) do 1287 | local file_node = nodes[file.id] 1288 | 1289 | for i, node in ipairs(file_node.nestedNodes) do 1290 | comp_dg_node(res, nodes, nodes[node.id]) 1291 | end 1292 | end 1293 | 1294 | return table.concat(res) 1295 | end 1296 | 1297 | function _M.compile(schema) 1298 | local res = {} 1299 | 1300 | comp_header(res, schema.nodes) 1301 | comp_body(res, schema) 1302 | 1303 | return table.concat(res) 1304 | end 1305 | 1306 | function _M.init(user_conf) 1307 | dbg("set config init") 1308 | for k, v in pairs(user_conf) do 1309 | --if not config[k] then 1310 | -- print(format("Unknown user config: %s, ignored.", k)) 1311 | --end 1312 | config[k] = v 1313 | dbg("set config " .. k) 1314 | end 1315 | end 1316 | 1317 | return _M 1318 | -------------------------------------------------------------------------------- /capnp/util.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | local bit = require "bit" 3 | local lower = string.lower 4 | local upper = string.upper 5 | local gsub = string.gsub 6 | local format = string.format 7 | local concat = table.concat 8 | local insert = table.insert 9 | 10 | local tohex = bit.tohex 11 | 12 | local _M = {} 13 | 14 | function _M.upper_dash_naming(name) 15 | return upper(gsub(name, "(%u+)", "-%1")) 16 | end 17 | 18 | function _M.lower_underscore_naming(name) 19 | return lower(gsub(name, "(%u+)", "_%1")) 20 | end 21 | 22 | function _M.upper_underscore_naming(name) 23 | return upper(gsub(name, "(%u+)", "_%1")) 24 | end 25 | 26 | function _M.lower_space_naming(name) 27 | return lower(gsub(name, "(%u+)", " %1")) 28 | end 29 | 30 | -- capnp only allow camel naming for enums 31 | function _M.camel_naming(name) 32 | return name 33 | end 34 | 35 | function _M.parse_capnp_txt(s) 36 | s = gsub(s, "%(", "{") 37 | s = gsub(s, "%)", "}") 38 | s = gsub(s, "%[", "{") 39 | s = gsub(s, "%]", "}") 40 | s = gsub(s, "%<", "'") 41 | s = gsub(s, "%>", "'") 42 | s = gsub(s, "id = (%d+)", "id = \"%1\"") 43 | s = gsub(s, "typeId = (%d+)", "typeId = \"%1\"") 44 | s = gsub(s, "scopeId = (%d+)", "scopeId = \"%1\"") 45 | s = gsub(s, "= void([^\"])", "= \"void\"%1") 46 | s = gsub(s, "type = {", '[\"type\"] = {') 47 | s = "return " .. s 48 | 49 | return s 50 | end 51 | 52 | function _M.parse_capnp_decode_txt(infile) 53 | local f = io.open(infile) 54 | if not f then 55 | return nil, "Can't open file: " .. tostring(infile) 56 | end 57 | 58 | local s = f:read("*a") 59 | f:close() 60 | 61 | return _M.parse_capnp_txt(s) 62 | end 63 | 64 | function _M.table_diff(t1, t2, namespace) 65 | local keys = {} 66 | 67 | if not namespace then 68 | namespace = "" 69 | end 70 | 71 | for k, v in pairs(t1) do 72 | k = _M.lower_underscore_naming(k) 73 | keys[k] = true 74 | t1[k] = v 75 | end 76 | 77 | for k, v in pairs(t2) do 78 | k = _M.lower_underscore_naming(k) 79 | keys[k] = true 80 | t2[k] = v 81 | end 82 | 83 | for k, v in pairs(keys) do 84 | local name = namespace .. "." .. k 85 | local v1 = t1[k] 86 | local v2 = t2[k] 87 | 88 | local t1 = type(v1) 89 | local t2 = type(v2) 90 | 91 | if t1 ~= t2 then 92 | print(format("%s: different type: %s %s, value: %s %s", name, 93 | t1, t2, tostring(v1), tostring(v2))) 94 | elseif t1 == "table" then 95 | _M.table_diff(v1, v2, namespace .. "." .. k) 96 | elseif v1 ~= v2 then 97 | print(format("%s: different value: %s %s", name, 98 | tostring(v1), tostring(v2))) 99 | end 100 | end 101 | end 102 | 103 | function _M.read_file(name) 104 | local f = assert(io.open(name, "r")) 105 | local content = f:read("*a") 106 | f:close() 107 | return content 108 | end 109 | 110 | function _M.write_file(name, content) 111 | local f = assert(io.open(name, "w")) 112 | f:write(content) 113 | f:close() 114 | end 115 | 116 | function _M.get_output_name(schema) 117 | return string.gsub(schema.requestedFiles[1].filename, "%.capnp", "_capnp") 118 | end 119 | 120 | function _M.hex_buf_str(buf, len) 121 | local str = ffi.string(buf, len) 122 | local t = {} 123 | for i = 1, len do 124 | table.insert(t, tohex(string.byte(str, i), 2)) 125 | end 126 | return table.concat(t, " ") 127 | end 128 | function _M.print_hex_buf(buf, len) 129 | local str = _M.hex_buf_str(buf, len) 130 | print(str) 131 | end 132 | 133 | function _M.new_buf(hex, ct) 134 | if type(hex) ~= "table" then 135 | error("expected the first argument as a table") 136 | end 137 | local len = #hex 138 | local buf = ffi.new("char[?]", len) 139 | for i=1, len do 140 | buf[i - 1] = hex[i] 141 | end 142 | if not ct then 143 | ct = "uint32_t *" 144 | end 145 | return ffi.cast(ct, buf) 146 | end 147 | 148 | local function equal(a, b) 149 | if type(a) == "boolean" then 150 | a = a and 1 or 0 151 | end 152 | if type(b) == "boolean" then 153 | b = b and 1 or 0 154 | end 155 | return a == b 156 | end 157 | 158 | local function to_text_core(val, T, res) 159 | local typ = type(val) 160 | if typ == "table" then 161 | if #val > 0 then 162 | -- list 163 | insert(res, "[") 164 | for i = 1, #val do 165 | if i ~= 1 then 166 | insert(res, ", ") 167 | end 168 | insert(res, '"') 169 | insert(res, val[i]) 170 | insert(res, '"') 171 | end 172 | insert(res, "]") 173 | else 174 | -- struct 175 | insert(res, "(") 176 | local i = 1 177 | for _, item in pairs(T.fields) do 178 | local k = item.name 179 | local default = item.default 180 | if type(default) == "boolean" then 181 | default = default and 1 or 0 182 | end 183 | if val[k] ~= nil then 184 | -- if not equal(val[k], default) then 185 | if i ~= 1 then 186 | insert(res, ", ") 187 | end 188 | insert(res, k) 189 | insert(res, " = ") 190 | to_text_core(val[k], T[k], res) 191 | i = i + 1 192 | -- end 193 | end 194 | end 195 | insert(res, ")") 196 | end 197 | elseif typ == "string" then 198 | if val == "Void" then 199 | insert(res, "void") 200 | else 201 | insert(res, '"') 202 | insert(res, val) 203 | insert(res, '"') 204 | end 205 | elseif typ == "boolean" then 206 | insert(res, val and "true" or "false") 207 | else 208 | if type(val) == "cdata" then 209 | --val = string.sub(tostring(val), 1, -3) 210 | val = tostring(val) 211 | end 212 | insert(res, val) 213 | end 214 | end 215 | 216 | function _M.to_text(val, T) 217 | local res = {} 218 | to_text_core(val, T, res) 219 | return concat(res) 220 | end 221 | 222 | local function get_type(typ) 223 | if not typ then 224 | return 225 | end 226 | 227 | for k, v in pairs(typ) do 228 | if k == "struct" then 229 | return k, typ[k].typeId 230 | end 231 | return k, get_type(typ[k].elementType) 232 | end 233 | end 234 | 235 | function _M.get_field_type(field) 236 | if field and field.slot and field.slot["type"] then 237 | return { get_type(field.slot["type"]) } 238 | end 239 | 240 | return 241 | end 242 | return _M 243 | -------------------------------------------------------------------------------- /cpp/main.c++: -------------------------------------------------------------------------------- 1 | #include "example.capnp.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | void writeT1(int fd) { 11 | ::capnp::MallocMessageBuilder message; 12 | 13 | T1::Builder t1 = message.initRoot(); 14 | //::capnp::List::Builder people = addressBook.initPeople(2); 15 | t1.setI0(32); 16 | t1.setI1(16); 17 | t1.setB0(true); 18 | t1.setI2(127); 19 | t1.setB1(true); 20 | t1.setI3(65536); 21 | t1.setE0(::T1::EnumType1::ENUM3); 22 | T1::T2::Builder t2 = t1.initS0(); 23 | t2.setF0(3.14); 24 | t2.setF1(3.14159265358979); 25 | 26 | ::capnp::List< ::int8_t>::Builder l0 = t1.initL0(2); 27 | l0.set(0, 28); 28 | l0.set(1, 29); 29 | 30 | t1.setT0("hello"); 31 | const char *str = "\1\2\3\4\5\6\7"; 32 | t1.setD0(::capnp::Data::Reader(reinterpret_cast(str), 33 | strlen(str))); 34 | 35 | t1.setE1(::EnumType2::ENUM7); 36 | 37 | //t1.setUi0(0xf0f0); 38 | t1.setUi1(0x0f0f); 39 | 40 | T1::G0::Builder g0 = t1.initG0(); 41 | g0.setUi2(0x12345678); 42 | 43 | T1::U0::Builder u0 = t1.initU0(); 44 | T1::U0::Ug0::Builder ug0 = u0.initUg0(); 45 | ug0.setUgu0(32); 46 | 47 | ::capnp::List< ::T1::T2>::Builder ls0 = t1.initLs0(2); 48 | ls0[0].setF0(0.61803398875); 49 | ls0[0].setF1(1.61803398875); 50 | ls0[1].setF0(2.71828182846); 51 | ls0[1].setF1(2.71828182846); 52 | 53 | //auto o0 = t1.getO0().initAs< ::T1::U0 >(); 54 | //o0.setUv1(); 55 | //o0.set(0, 2); 56 | 57 | ::capnp::List< ::capnp::Text>::Builder lt0 = t1.initLt0(2); 58 | lt0.set(0, "foo"); 59 | lt0.set(1, "bar"); 60 | 61 | writeMessageToFd(fd, message); 62 | } 63 | 64 | void readT1(int fd) { 65 | ::capnp::StreamFdMessageReader message(fd); 66 | T1::Reader t1 = message.getRoot(); 67 | 68 | printf("%d\n", t1.getI0()); 69 | } 70 | 71 | int main(int argc, char **argv) 72 | { 73 | if (argc >= 2) { 74 | readT1(STDIN_FILENO); 75 | } 76 | 77 | writeT1(STDOUT_FILENO); 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /doc/document.md: -------------------------------------------------------------------------------- 1 | # lua-capnproto 2 | 3 | Code structure 4 | ============== 5 | 6 | ├── bin Cap'n Proto compiler's plug-ins and Cap'n Proto schema file 7 | ├── capnp lua-capnproto library file (including compiler) 8 | ├── cpp cpp files used for testing lua-capnproto 9 | ├── example examples for how to use lua-capnproto 10 | ├── lua other lua files 11 | ├── proto all protos 12 | └── tests test files 13 | 14 | Debugging 15 | ========= 16 | 17 | Set environment variable `VERBOSE` to 1 enables debug mode. Compiler will generate more debug info and the following files: 18 | 19 | * `test.schema.lua` This is the schema passed from Cap'n Proto compiler to lua plug-in. Cap'n Proto text presentation has been translated to lua file. 20 | * `lua.schema.json` Schema that lua-capnproto compiler used 21 | 22 | Compiling proto files 23 | ===================== 24 | 25 | All proto files including imported proto files should be compiled together. Output file will use first input proto file's name plus a "_capnp.lua" suffix. For example: 26 | 27 | capnp compile -olua message.capnp constants.capnp 28 | 29 | Output file will be `message_capnp.lua` 30 | 31 | When developing, you may need to run the following command. This specifies which capnpc-lua to use. 32 | 33 | VERBOSE=1 capnp compile -o ../bin/capnpc-lua example.capnp enums.capnp lua.capnp struct.capnp 34 | 35 | How to add a new naming function 36 | ================================ 37 | 38 | * add a new naming function in capnp/util.lua 39 | * add a test case in tests/02-util.lua 40 | * test new naming function by `make test` 41 | * add your naming function to "naming_funcs" table in "capnp/compile.lua" using this kind of format: `name = function`. 'name' is what you will write in Cap'n Proto file using "$Lua.naming" annotation. 'function' is you actual naming function 42 | * add a new enum in proto/enums.capnp. 43 | * add a test case in tests/11-handwritten.lua (see test_lower_space_naming) 44 | * update lua/handwritten_capnp.lua using `vimdiff lua/handwritten_capnp.lua proto/example_capnp.lua` 45 | 46 | Generated code 47 | =============== 48 | 49 | `calc_size` - calculate size need for serialization using given input data, header size included 50 | `calc_size_struct` - calculate size need for serialization using given input data, header size not included 51 | -------------------------------------------------------------------------------- /example/AddressBook.capnp: -------------------------------------------------------------------------------- 1 | @0xdbb9ad1f14bf0b36; # unique file ID, generated by `capnp id` 2 | 3 | struct Person { 4 | id @0 :UInt32; 5 | name @1 :Text; 6 | email @2 :Text; 7 | phones @3 :List(PhoneNumber); 8 | 9 | struct PhoneNumber { 10 | number @0 :Text; 11 | type @1 :Type; 12 | 13 | enum Type { 14 | mobile @0; 15 | home @1; 16 | work @2; 17 | } 18 | } 19 | 20 | employment :union { 21 | unemployed @4 :Void; 22 | employer @5 :Text; 23 | school @6 :Text; 24 | selfEmployed @7 :Void; 25 | # We assume that a person is only one of these. 26 | } 27 | } 28 | 29 | struct AddressBook { 30 | people @0 :List(Person); 31 | } 32 | -------------------------------------------------------------------------------- /example/main.lua: -------------------------------------------------------------------------------- 1 | local addressBook = require "AddressBook_capnp" 2 | local capnp = require "capnp" 3 | local cjson = require "cjson" 4 | local util = require "capnp.util" 5 | 6 | local data = { 7 | people = { 8 | { 9 | id = 123, 10 | name = "Alice", 11 | email = "alice@example.com", 12 | phones = { 13 | { 14 | number = "555-1212", 15 | ["type"] = "MOBILE", 16 | }, 17 | }, 18 | employment = { 19 | school = "MIT", 20 | }, 21 | }, 22 | { 23 | id = 456, 24 | name = "Bob", 25 | email = "bob@example.com", 26 | phones = { 27 | { 28 | number = "555-4567", 29 | ["type"] = "HOME", 30 | }, 31 | { 32 | number = "555-7654", 33 | ["type"] = "WORK", 34 | }, 35 | }, 36 | employment = { 37 | unemployed = "Void", 38 | }, 39 | }, 40 | } 41 | } 42 | 43 | local bin = addressBook.AddressBook.serialize(data) 44 | local decoded = addressBook.AddressBook.parse(bin) 45 | 46 | print(cjson.encode(decoded)) 47 | -------------------------------------------------------------------------------- /lua/handwritten_capnp.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | local capnp = require "capnp" 3 | local bit = require "bit" 4 | 5 | local ceil = math.ceil 6 | local write_struct_field= capnp.write_struct_field 7 | local read_struct_field = capnp.read_struct_field 8 | local read_text = capnp.read_text 9 | local write_text = capnp.write_text 10 | local get_enum_val = capnp.get_enum_val 11 | local get_enum_name = capnp.get_enum_name 12 | local get_data_off = capnp.get_data_off 13 | local write_listp_buf = capnp.write_listp_buf 14 | local write_structp_buf = capnp.write_structp_buf 15 | local write_structp = capnp.write_structp 16 | local read_struct_buf = capnp.read_struct_buf 17 | local read_listp_struct = capnp.read_listp_struct 18 | local read_list_data = capnp.read_list_data 19 | local write_list = capnp.write_list 20 | local write_list_data = capnp.write_list_data 21 | local ffi_new = ffi.new 22 | local ffi_string = ffi.string 23 | local ffi_cast = ffi.cast 24 | local ffi_copy = ffi.copy 25 | local ffi_fill = ffi.fill 26 | local ffi_typeof = ffi.typeof 27 | local band, bor, bxor = bit.band, bit.bor, bit.bxor 28 | 29 | local pint8 = ffi_typeof("int8_t *") 30 | local pint16 = ffi_typeof("int16_t *") 31 | local pint32 = ffi_typeof("int32_t *") 32 | local pint64 = ffi_typeof("int64_t *") 33 | local puint8 = ffi_typeof("uint8_t *") 34 | local puint16 = ffi_typeof("uint16_t *") 35 | local puint32 = ffi_typeof("uint32_t *") 36 | local puint64 = ffi_typeof("uint64_t *") 37 | local pbool = ffi_typeof("uint8_t *") 38 | local pfloat32 = ffi_typeof("float *") 39 | local pfloat64 = ffi_typeof("double *") 40 | local ok, new_tab = pcall(require, "table.new") 41 | 42 | if not ok then 43 | new_tab = function (narr, nrec) return {} end 44 | end 45 | 46 | local round8 = function(size) 47 | return ceil(size / 8) * 8 48 | end 49 | 50 | local str_buf 51 | local default_segment_size = 4096 52 | 53 | local function get_str_buf(size) 54 | if size > default_segment_size then 55 | return ffi_new("char[?]", size) 56 | end 57 | 58 | if not str_buf then 59 | str_buf = ffi_new("char[?]", default_segment_size) 60 | end 61 | return str_buf 62 | end 63 | 64 | local _M = new_tab(0, 19) 65 | 66 | 67 | _M.pi = 3.14159 68 | 69 | _M.T1 = { 70 | id = "13624321058757364083", 71 | displayName = "proto/example.capnp:T1", 72 | dataWordCount = 6, 73 | pointerCount = 8, 74 | discriminantCount = 3, 75 | discriminantOffset = 10, 76 | 77 | fields = { 78 | { name = "i0", default = 0, ["type"] = "uint32" }, 79 | { name = "i1", default = 0, ["type"] = "uint16" }, 80 | { name = "b0", default = 0, ["type"] = "bool" }, 81 | { name = "i2", default = 0, ["type"] = "int8" }, 82 | { name = "b1", default = 0, ["type"] = "bool" }, 83 | { name = "i3", default = 0, ["type"] = "int32" }, 84 | { name = "s0", default = "opaque pointer", ["type"] = "struct" }, 85 | { name = "e0", default = "enum1", ["type"] = "enum" }, 86 | { name = "l0", default = "opaque pointer", ["type"] = "list" }, 87 | { name = "t0", default = "", ["type"] = "text" }, 88 | { name = "e1", default = "none", ["type"] = "enum" }, 89 | { name = "d0", default = "", ["type"] = "data" }, 90 | { name = "ui0", default = 0, ["type"] = "int32" }, 91 | { name = "ui1", default = 0, ["type"] = "int32" }, 92 | { name = "uv0", default = "Void", ["type"] = "void" }, 93 | { name = "g0", default = nil, ["type"] = "group" }, 94 | { name = "u0", default = nil, ["type"] = "group" }, 95 | { name = "ls0", default = "opaque pointer", ["type"] = "list" }, 96 | { name = "du0", default = 65535, ["type"] = "uint32" }, 97 | { name = "db0", default = 1, ["type"] = "bool" }, 98 | { name = "end", default = 0, ["type"] = "bool" }, 99 | { name = "o0", default = nil, ["type"] = "anyPointer" }, 100 | { name = "lt0", default = "opaque pointer", ["type"] = "list" }, 101 | { name = "u64", default = 0, ["type"] = "uint64" }, 102 | }, 103 | calc_size_struct = function(data) 104 | local size = 112 105 | -- struct 106 | if data["s0"] then 107 | size = size + _M.T1.T2.calc_size_struct(data["s0"]) 108 | end 109 | -- list 110 | if data["l0"] then 111 | -- num * actual size 112 | size = size + round8(#data["l0"] * 1) 113 | end 114 | -- text 115 | if data["t0"] then 116 | -- size 1, including trailing NULL 117 | size = size + round8(#data["t0"] + 1) 118 | end 119 | -- data 120 | if data["d0"] then 121 | size = size + round8(#data["d0"]) 122 | end 123 | -- composite list 124 | -- struct 125 | if data["g0"] then 126 | size = size + _M.T1.g0.calc_size_struct(data["g0"]) 127 | end 128 | -- struct 129 | if data["u0"] then 130 | size = size + _M.T1.u0.calc_size_struct(data["u0"]) 131 | end 132 | -- list 133 | if data["ls0"] then 134 | size = size + 8 135 | local num2 = #data["ls0"] 136 | for i2=1, num2 do 137 | size = size + _M.T1.T2.calc_size_struct(data["ls0"][i2]) 138 | end 139 | end 140 | -- list 141 | if data["lt0"] then 142 | local num2 = #data["lt0"] 143 | for i2=1, num2 do 144 | size = size + 8 145 | -- num * actual size 146 | size = size + round8(#data["lt0"][i2] * 1 + 1) 147 | end 148 | end 149 | return size 150 | end, 151 | 152 | calc_size = function(data) 153 | local size = 16 -- header + root struct pointer 154 | return size + _M.T1.calc_size_struct(data) 155 | end, 156 | 157 | flat_serialize = function(data, p32, pos) 158 | pos = pos and pos or 112 -- struct size in bytes 159 | local start = pos 160 | local dscrm 161 | local data_type = type(data["i0"]) 162 | if data["i0"] and (data_type == "number" 163 | or data_type == "boolean" ) then 164 | 165 | write_struct_field(p32, data["i0"], "uint32", 32, 0, 0) 166 | end 167 | local data_type = type(data["i1"]) 168 | if data["i1"] and (data_type == "number" 169 | or data_type == "boolean" ) then 170 | 171 | write_struct_field(p32, data["i1"], "uint16", 16, 2, 0) 172 | end 173 | local data_type = type(data["b0"]) 174 | if data["b0"] and (data_type == "number" 175 | or data_type == "boolean" ) then 176 | 177 | write_struct_field(p32, data["b0"], "bool", 1, 48, 0) 178 | end 179 | local data_type = type(data["i2"]) 180 | if data["i2"] and (data_type == "number" 181 | or data_type == "boolean" ) then 182 | 183 | write_struct_field(p32, data["i2"], "int8", 8, 7, 0) 184 | end 185 | local data_type = type(data["b1"]) 186 | if data["b1"] and (data_type == "number" 187 | or data_type == "boolean" ) then 188 | 189 | write_struct_field(p32, data["b1"], "bool", 1, 49, 0) 190 | end 191 | local data_type = type(data["i3"]) 192 | if data["i3"] and (data_type == "number" 193 | or data_type == "boolean" ) then 194 | 195 | write_struct_field(p32, data["i3"], "int32", 32, 2, 0) 196 | end 197 | if data["s0"] and type(data["s0"]) == "table" then 198 | local data_off = get_data_off(_M.T1, 0, pos) 199 | write_structp_buf(p32, _M.T1, _M.T1.T2, 0, data_off) 200 | local size = _M.T1.T2.flat_serialize(data["s0"], p32 + pos / 4) 201 | pos = pos + size 202 | end 203 | if data["e0"] and type(data["e0"]) == "string" then 204 | local val = get_enum_val(data["e0"], 0, _M.T1.EnumType1, "T1.e0") 205 | write_struct_field(p32, val, "uint16", 16, 6) 206 | end 207 | if data["l0"] and type(data["l0"]) == "table" then 208 | local data_off = get_data_off(_M.T1, 1, pos) 209 | pos = pos + write_list(p32 + _M.T1.dataWordCount * 2 + 1 * 2, 210 | data["l0"], (data_off + 1) * 8, "list", "int8") 211 | end 212 | if data["t0"] and type(data["t0"]) == "string" then 213 | local data_off = get_data_off(_M.T1, 2, pos) 214 | 215 | local len = #data["t0"] + 1 216 | write_listp_buf(p32, _M.T1, 2, 2, len, data_off) 217 | 218 | ffi_copy(p32 + pos / 4, data["t0"]) 219 | pos = pos + round8(len) 220 | end 221 | if data["e1"] and type(data["e1"]) == "string" then 222 | local val = get_enum_val(data["e1"], 0, _M.EnumType2, "T1.e1") 223 | write_struct_field(p32, val, "uint16", 16, 7) 224 | end 225 | if data["d0"] and type(data["d0"]) == "string" then 226 | local data_off = get_data_off(_M.T1, 3, pos) 227 | 228 | local len = #data["d0"] 229 | write_listp_buf(p32, _M.T1, 3, 2, len, data_off) 230 | 231 | -- prevent copying trailing '\0' 232 | ffi_copy(p32 + pos / 4, data["d0"], len) 233 | pos = pos + round8(len) 234 | end 235 | if data["ui0"] then 236 | dscrm = 0 237 | end 238 | 239 | local data_type = type(data["ui0"]) 240 | if data["ui0"] and (data_type == "number" 241 | or data_type == "boolean" ) then 242 | 243 | write_struct_field(p32, data["ui0"], "int32", 32, 4, 0) 244 | end 245 | if data["ui1"] then 246 | dscrm = 1 247 | end 248 | 249 | local data_type = type(data["ui1"]) 250 | if data["ui1"] and (data_type == "number" 251 | or data_type == "boolean" ) then 252 | 253 | write_struct_field(p32, data["ui1"], "int32", 32, 4, 0) 254 | end 255 | if data["uv0"] then 256 | dscrm = 2 257 | end 258 | 259 | if data["g0"] and type(data["g0"]) == "table" then 260 | -- groups are just namespaces, field offsets are set within parent 261 | -- structs 262 | pos = pos + _M.T1.g0.flat_serialize(data["g0"], p32, pos) - 112 263 | end 264 | 265 | if data["u0"] and type(data["u0"]) == "table" then 266 | -- groups are just namespaces, field offsets are set within parent 267 | -- structs 268 | pos = pos + _M.T1.u0.flat_serialize(data["u0"], p32, pos) - 112 269 | end 270 | 271 | if data["ls0"] and type(data["ls0"]) == "table" then 272 | local num, size, old_pos = #data["ls0"], 0, pos 273 | local data_off = get_data_off(_M.T1, 4, pos) 274 | pos = pos + write_list(p32 + _M.T1.dataWordCount * 2 + 4 * 2, 275 | data["ls0"], (data_off + 1) * 8, "list", "struct", _M.T1.T2) 276 | end 277 | local data_type = type(data["du0"]) 278 | if data["du0"] and (data_type == "number" 279 | or data_type == "boolean" ) then 280 | 281 | write_struct_field(p32, data["du0"], "uint32", 32, 9, 65535) 282 | end 283 | local data_type = type(data["db0"]) 284 | if data["db0"] and (data_type == "number" 285 | or data_type == "boolean" ) then 286 | 287 | write_struct_field(p32, data["db0"], "bool", 1, 50, 1) 288 | end 289 | local data_type = type(data["end"]) 290 | if data["end"] and (data_type == "number" 291 | or data_type == "boolean" ) then 292 | 293 | write_struct_field(p32, data["end"], "bool", 1, 51, 0) 294 | end 295 | if data["lt0"] and type(data["lt0"]) == "table" then 296 | local data_off = get_data_off(_M.T1, 6, pos) 297 | pos = pos + write_list(p32 + _M.T1.dataWordCount * 2 + 6 * 2, 298 | data["lt0"], (data_off + 1) * 8, "list", "text") 299 | end 300 | local data_type = type(data["u64"]) 301 | if data["u64"] and (data_type == "number" 302 | or data_type == "boolean" or data_type == "cdata") then 303 | 304 | write_struct_field(p32, data["u64"], "uint64", 64, 5, 0) 305 | end 306 | if dscrm then 307 | --buf, discriminantOffset, discriminantValue 308 | _M.T1.which(p32, 10, dscrm) 309 | end 310 | 311 | return pos - start + 112 312 | end, 313 | 314 | serialize = function(data, p8, size) 315 | if not p8 then 316 | size = _M.T1.calc_size(data) 317 | 318 | p8 = get_str_buf(size) 319 | end 320 | ffi_fill(p8, size) 321 | local p32 = ffi_cast(puint32, p8) 322 | 323 | -- Because needed size has been calculated, only 1 segment is needed 324 | p32[0] = 0 325 | p32[1] = (size - 8) / 8 326 | 327 | -- skip header 328 | write_structp(p32 + 2, _M.T1, 0) 329 | 330 | -- skip header & struct pointer 331 | _M.T1.flat_serialize(data, p32 + 4) 332 | 333 | return ffi_string(p8, size) 334 | end, 335 | 336 | which = function(buf, offset, n) 337 | if n then 338 | -- set value 339 | write_struct_field(buf, n, "uint16", 16, offset) 340 | else 341 | -- get value 342 | return read_struct_field(buf, "uint16", 16, offset) 343 | end 344 | end, 345 | 346 | parse_struct_data = function(p32, data_word_count, pointer_count, header, 347 | tab) 348 | local s = tab 349 | 350 | local dscrm = _M.T1.which(p32, 10) 351 | 352 | 353 | s["i0"] = read_struct_field(p32, "uint32", 32, 0, 0) 354 | 355 | s["i1"] = read_struct_field(p32, "uint16", 16, 2, 0) 356 | 357 | s["b0"] = read_struct_field(p32, "bool", 1, 48, 0) 358 | 359 | s["i2"] = read_struct_field(p32, "int8", 8, 7, 0) 360 | 361 | s["b1"] = read_struct_field(p32, "bool", 1, 49, 0) 362 | 363 | s["i3"] = read_struct_field(p32, "int32", 32, 2, 0) 364 | -- struct 365 | local p = p32 + (6 + 0) * 2 -- p32, dataWordCount, offset 366 | local off, dw, pw = read_struct_buf(p, header) 367 | if off and dw and pw then 368 | if not s["s0"] then 369 | s["s0"] = new_tab(0, 2) 370 | end 371 | _M.T1.T2.parse_struct_data(p + 2 + off * 2, dw, pw, header, s["s0"]) 372 | else 373 | s["s0"] = nil 374 | end 375 | 376 | 377 | -- enum 378 | local val = read_struct_field(p32, "uint16", 16, 6) 379 | s["e0"] = get_enum_name(val, 0, _M.T1.EnumType1Str) 380 | 381 | -- list 382 | local off, size, num = read_listp_struct(p32, header, _M.T1, 1) 383 | if off and num then 384 | -- dataWordCount + offset + pointerSize + off 385 | s["l0"] = read_list_data(p32 + (6 + 1 + 1 + off) * 2, header, 386 | num, "int8") 387 | else 388 | s["l0"] = nil 389 | end 390 | 391 | -- text 392 | s["t0"] = read_text(p32, header, _M.T1, 2, nil) 393 | --[[ 394 | local off, size, num = read_listp_struct(p32, header, _M.T1, 2) 395 | if off and num then 396 | s["t0"] = ffi.string(p32 + (5 + 2 + 1 + off) * 2, num - 1) -- dataWordCount + offset + pointerSize + off 397 | else 398 | s["t0"] = nil 399 | end 400 | ]] 401 | -- enum 402 | local val = read_struct_field(p32, "uint16", 16, 7) 403 | s["e1"] = get_enum_name(val, 0, _M.EnumType2Str) 404 | 405 | -- data 406 | local off, size, num = read_listp_struct(p32, header, _M.T1, 3) 407 | if off and num then 408 | -- dataWordCount + offset + pointerSize + off 409 | local p8 = ffi_cast(pint8, p32 + (6 + 3 + 1 + off) * 2) 410 | s["d0"] = ffi_string(p8, num) 411 | else 412 | s["d0"] = nil 413 | end 414 | 415 | if dscrm == 0 then 416 | 417 | s["ui0"] = read_struct_field(p32, "int32", 32, 4, 0) 418 | else 419 | s["ui0"] = nil 420 | end 421 | 422 | if dscrm == 1 then 423 | 424 | s["ui1"] = read_struct_field(p32, "int32", 32, 4, 0) 425 | else 426 | s["ui1"] = nil 427 | end 428 | 429 | if dscrm == 2 then 430 | 431 | s["uv0"] = "Void" 432 | else 433 | s["uv0"] = nil 434 | end 435 | 436 | if not s["g0"] then 437 | s["g0"] = new_tab(0, 4) 438 | end 439 | _M.T1["g0"].parse_struct_data(p32, _M.T1.dataWordCount, 440 | _M.T1.pointerCount, header, s["g0"]) 441 | 442 | if not s["u0"] then 443 | s["u0"] = new_tab(0, 4) 444 | end 445 | _M.T1["u0"].parse_struct_data(p32, _M.T1.dataWordCount, 446 | _M.T1.pointerCount, header, s["u0"]) 447 | 448 | -- composite list 449 | local off, size, num = read_listp_struct(p32, header, _M.T1, 4) 450 | if off and num then 451 | -- dataWordCount + offset + pointerSize + off 452 | s["ls0"] = read_list_data(p32 + (6 + 4 + 1 + off) * 2, header, 453 | num, "struct", _M.T1.T2) 454 | else 455 | s["ls0"] = nil 456 | end 457 | s["du0"] = read_struct_field(p32, "uint32", 32, 9, 65535) 458 | s["db0"] = read_struct_field(p32, "bool", 1, 50, 1) 459 | s["end"] = read_struct_field(p32, "bool", 1, 51, 0) 460 | local off, size, num = read_listp_struct(p32, header, _M.T1, 6) 461 | if off and num then 462 | -- dataWordCount + offset + pointerSize + off 463 | s["lt0"] = read_list_data(p32 + (6 + 6 + 1 + off) * 2, header, 464 | num, "text") 465 | else 466 | s["lt0"] = nil 467 | end 468 | 469 | s["u64"] = read_struct_field(p32, "uint64", 64, 5, 0) 470 | return s 471 | end, 472 | 473 | parse = function(bin, tab) 474 | if #bin < 16 then 475 | return nil, "message too short" 476 | end 477 | 478 | local header = new_tab(0, 4) 479 | local p32 = ffi_cast(puint32, bin) 480 | header.base = p32 481 | 482 | local nsegs = p32[0] + 1 483 | header.seg_sizes = {} 484 | for i=1, nsegs do 485 | header.seg_sizes[i] = p32[i] 486 | end 487 | local pos = round8(4 + nsegs * 4) 488 | header.header_size = pos / 8 489 | p32 = p32 + pos / 4 490 | 491 | if not tab then 492 | tab = new_tab(0, 8) 493 | end 494 | local off, dw, pw = read_struct_buf(p32, header) 495 | if off and dw and pw then 496 | return _M.T1.parse_struct_data(p32 + 2 + off * 2, dw, pw, 497 | header, tab) 498 | else 499 | return nil 500 | end 501 | end, 502 | 503 | } 504 | 505 | _M.T1.T2 = { 506 | id = "17202330444354522981", 507 | displayName = "proto/example.capnp:T1.T2", 508 | dataWordCount = 2, 509 | pointerCount = 1, 510 | discriminantCount = 0, 511 | discriminantOffset = 0, 512 | 513 | fields = { 514 | { name = "f0", default = 0, ["type"] = "float32" }, 515 | { name = "f1", default = 0, ["type"] = "float64" }, 516 | { name = "sd0", default = "", ["type"] = "data" }, 517 | }, 518 | calc_size_struct = function(data) 519 | local size = 24 520 | -- data 521 | if data["sd0"] then 522 | size = size + round8(#data["sd0"]) 523 | end 524 | return size 525 | end, 526 | 527 | calc_size = function(data) 528 | local size = 16 -- header + root struct pointer 529 | return size + _M.T1.T2.calc_size_struct(data) 530 | end, 531 | 532 | flat_serialize = function(data, p32, pos) 533 | pos = pos and pos or 24 -- struct size in bytes 534 | local start = pos 535 | local dscrm 536 | local data_type = type(data["f0"]) 537 | if data["f0"] and (data_type == "number" 538 | or data_type == "boolean" ) then 539 | 540 | write_struct_field(p32, data["f0"], "float32", 32, 0, 0) 541 | end 542 | local data_type = type(data["f1"]) 543 | if data["f1"] and (data_type == "number" 544 | or data_type == "boolean" ) then 545 | 546 | write_struct_field(p32, data["f1"], "float64", 64, 1, 0) 547 | end 548 | if data["sd0"] and type(data["sd0"]) == "string" then 549 | local data_off = get_data_off(_M.T1.T2, 0, pos) 550 | 551 | local len = #data["sd0"] 552 | write_listp_buf(p32, _M.T1.T2, 0, 2, len, data_off) 553 | 554 | -- prevent copying trailing '\0' 555 | ffi_copy(p32 + pos / 4, data["sd0"], len) 556 | pos = pos + round8(len) 557 | end 558 | return pos - start + 24 559 | end, 560 | serialize = function(data, p8, size) 561 | if not p8 then 562 | size = _M.T1.T2.calc_size(data) 563 | 564 | p8 = get_str_buf(size) 565 | end 566 | ffi_fill(p8, size) 567 | local p32 = ffi_cast(puint32, p8) 568 | 569 | -- Because needed size has been calculated, only 1 segment is needed 570 | p32[0] = 0 571 | p32[1] = (size - 8) / 8 572 | 573 | -- skip header 574 | write_structp(p32 + 2, _M.T1.T2, 0) 575 | 576 | -- skip header & struct pointer 577 | _M.T1.T2.flat_serialize(data, p32 + 4) 578 | 579 | return ffi_string(p8, size) 580 | end, 581 | 582 | 583 | parse_struct_data = function(p32, data_word_count, pointer_count, header, tab) 584 | local s = tab 585 | 586 | s["f0"] = read_struct_field(p32, "float32", 32, 0, 0) 587 | 588 | s["f1"] = read_struct_field(p32, "float64", 64, 1, 0) 589 | 590 | -- data 591 | local off, size, num = read_listp_struct(p32, header, _M.T1.T2, 0) 592 | if off and num then 593 | -- dataWordCount + offset + pointerSize + off 594 | local p8 = ffi_cast(pint8, p32 + (2 + 0 + 1 + off) * 2) 595 | s["sd0"] = ffi_string(p8, num) 596 | else 597 | s["sd0"] = nil 598 | end 599 | return s 600 | end, 601 | 602 | parse = function(bin, tab) 603 | if #bin < 16 then 604 | return nil, "message too short" 605 | end 606 | 607 | local header = new_tab(0, 4) 608 | local p32 = ffi_cast(puint32, bin) 609 | header.base = p32 610 | local nsegs = p32[0] + 1 611 | header.seg_sizes = {} 612 | for i=1, nsegs do 613 | header.seg_sizes[i] = p32[i] 614 | end 615 | 616 | local pos = round8(4 + nsegs * 4) 617 | 618 | header.header_size = pos / 8 619 | p32 = p32 + pos / 4 620 | 621 | if not tab then 622 | tab = new_tab(0, 8) 623 | end 624 | local off, dw, pw = read_struct_buf(p32, header) 625 | if off and dw and pw then 626 | return _M.T1.T2.parse_struct_data(p32 + 2 + off * 2, dw, pw, 627 | header, tab) 628 | else 629 | return nil 630 | end 631 | end, 632 | 633 | } 634 | _M.T1.EnumType1 = { 635 | ["enum1"] = 0, 636 | ["enum2"] = 1, 637 | ["enum3"] = 2, 638 | ["wEirdENum4"] = 3, 639 | ["UPPER-DASH"] = 4, 640 | } 641 | _M.T1.EnumType1Str = { 642 | [0] = "enum1", 643 | [1] = "enum2", 644 | [2] = "enum3", 645 | [3] = "wEirdENum4", 646 | [4] = "UPPER-DASH", 647 | } 648 | 649 | _M.T1.welcomeText = "Hello" 650 | _M.T1.g0 = { 651 | id = "10312822589529145224", 652 | displayName = "proto/example.capnp:T1.g0", 653 | dataWordCount = 6, 654 | pointerCount = 8, 655 | discriminantCount = 0, 656 | discriminantOffset = 0, 657 | isGroup = true, 658 | 659 | fields = { 660 | { name = "ui2", default = 0, ["type"] = "uint32" }, 661 | }, 662 | calc_size_struct = function(data) 663 | local size = 0 664 | return size 665 | end, 666 | 667 | -- size is included in the parent struct, so no need to calculate size here 668 | calc_size = function(data) 669 | local size = 16 -- header + root struct pointer 670 | return size + _M.T1.g0.calc_size_struct(data) 671 | end, 672 | flat_serialize = function(data, p32, pos) 673 | pos = pos and pos or 112 -- struct size in bytes 674 | local start = pos 675 | local dscrm 676 | local data_type = type(data["ui2"]) 677 | if data["ui2"] and (data_type == "number" 678 | or data_type == "boolean" ) then 679 | 680 | write_struct_field(p32, data["ui2"], "uint32", 32, 6, 0) 681 | end 682 | return pos - start + 112 683 | end, 684 | 685 | parse_struct_data = function(p32, data_word_count, pointer_count, header, 686 | tab) 687 | local s = tab 688 | s["ui2"] = read_struct_field(p32, "uint32", 32, 6, 0) 689 | return s 690 | end, 691 | } 692 | _M.T1.u0 = { 693 | id = "12188145960292142197", 694 | displayName = "proto/example.capnp:T1.u0", 695 | dataWordCount = 6, 696 | pointerCount = 8, 697 | discriminantCount = 4, 698 | discriminantOffset = 14, 699 | isGroup = true, 700 | 701 | fields = { 702 | { name = "ui3", default = 0, ["type"] = "uint16" }, 703 | { name = "uv1", default = "Void", ["type"] = "void" }, 704 | { name = "ug0", default = nil, ["type"] = "group" }, 705 | { name = "ut0", default = "", ["type"] = "text" }, 706 | }, 707 | calc_size_struct = function(data) 708 | local size = 0 709 | -- struct 710 | if data["ug0"] then 711 | size = size + _M.T1.u0.ug0.calc_size_struct(data["ug0"]) 712 | end 713 | -- text 714 | if data["ut0"] then 715 | -- size 1, including trailing NULL 716 | size = size + round8(#data["ut0"] + 1) 717 | end 718 | return size 719 | end, 720 | 721 | calc_size = function(data) 722 | local size = 16 -- header + root struct pointer 723 | return size + _M.T1.u0.calc_size_struct(data) 724 | end, 725 | flat_serialize = function(data, p32, pos) 726 | pos = pos and pos or 112 -- struct size in bytes 727 | local start = pos 728 | local dscrm 729 | if data["ui3"] then 730 | dscrm = 0 731 | end 732 | 733 | local data_type = type(data["ui3"]) 734 | if data["ui3"] and (data_type == "number" 735 | or data_type == "boolean" ) then 736 | 737 | write_struct_field(p32, data["ui3"], "uint16", 16, 11, 0) 738 | end 739 | if data["uv1"] then 740 | dscrm = 1 741 | end 742 | 743 | if data["ug0"] then 744 | dscrm = 2 745 | end 746 | 747 | if data["ug0"] and type(data["ug0"]) == "table" then 748 | -- groups are just namespaces, field offsets are set within parent 749 | -- structs 750 | pos = pos + _M.T1.u0.ug0.flat_serialize(data["ug0"], p32, pos) - 112 751 | end 752 | 753 | if data["ut0"] then 754 | dscrm = 3 755 | end 756 | 757 | if data["ut0"] and type(data["ut0"]) == "string" then 758 | local data_off = get_data_off(_M.T1.u0, 7, pos) 759 | 760 | local len = #data["ut0"] + 1 761 | write_listp_buf(p32, _M.T1.u0, 7, 2, len, data_off) 762 | 763 | ffi_copy(p32 + pos / 4, data["ut0"]) 764 | pos = pos + round8(len) 765 | end 766 | if dscrm then 767 | --buf, discriminantOffset, discriminantValue 768 | _M.T1.u0.which(p32, 14, dscrm) 769 | end 770 | 771 | return pos - start + 112 772 | end, 773 | which = function(buf, offset, n) 774 | if n then 775 | -- set value 776 | write_struct_field(buf, n, "uint16", 16, offset) 777 | else 778 | -- get value 779 | return read_struct_field(buf, "uint16", 16, offset) 780 | end 781 | end, 782 | 783 | parse_struct_data = function(p32, data_word_count, pointer_count, header, 784 | tab) 785 | local s = tab 786 | 787 | local dscrm = _M.T1.u0.which(p32, 14) 788 | -- union 789 | if dscrm == 0 then 790 | 791 | s["ui3"] = read_struct_field(p32, "uint16", 16, 11, 0) 792 | else 793 | s["ui3"] = nil 794 | end 795 | 796 | -- union 797 | if dscrm == 1 then 798 | 799 | s["uv1"] = "Void" 800 | else 801 | s["uv1"] = nil 802 | end 803 | 804 | -- union 805 | if dscrm == 2 then 806 | 807 | -- group 808 | if not s["ug0"] then 809 | s["ug0"] = new_tab(0, 4) 810 | end 811 | _M.T1.u0["ug0"].parse_struct_data(p32, _M.T1.u0.dataWordCount, 812 | _M.T1.u0.pointerCount, header, s["ug0"]) 813 | 814 | else 815 | s["ug0"] = nil 816 | end 817 | 818 | -- union 819 | if dscrm == 3 then 820 | 821 | -- text 822 | local off, size, num = read_listp_struct(p32, header, _M.T1.u0, 7) 823 | if off and num then 824 | -- dataWordCount + offset + pointerSize + off 825 | local p8 = ffi_cast(pint8, p32 + (6 + 7 + 1 + off) * 2) 826 | s["ut0"] = ffi_string(p8, num - 1) 827 | else 828 | s["ut0"] = nil 829 | end 830 | 831 | else 832 | s["ut0"] = nil 833 | end 834 | return s 835 | end, 836 | } 837 | _M.T1.u0.ug0 = { 838 | id = "17270536655881866717", 839 | displayName = "proto/example.capnp:T1.u0.ug0", 840 | dataWordCount = 6, 841 | pointerCount = 8, 842 | discriminantCount = 0, 843 | discriminantOffset = 0, 844 | isGroup = true, 845 | 846 | fields = { 847 | { name = "ugv0", default = "Void", ["type"] = "void" }, 848 | { name = "ugu0", default = 0, ["type"] = "uint32" }, 849 | }, 850 | calc_size_struct = function(data) 851 | local size = 0 852 | return size 853 | end, 854 | 855 | calc_size = function(data) 856 | local size = 16 -- header + root struct pointer 857 | return size + _M.T1.u0.ug0.calc_size_struct(data) 858 | end, 859 | flat_serialize = function(data, p32, pos) 860 | pos = pos and pos or 112 -- struct size in bytes 861 | local start = pos 862 | local dscrm 863 | local data_type = type(data["ugu0"]) 864 | if data["ugu0"] and (data_type == "number" 865 | or data_type == "boolean" ) then 866 | 867 | write_struct_field(p32, data["ugu0"], "uint32", 32, 8, 0) 868 | end 869 | return pos - start + 112 870 | end, 871 | 872 | parse_struct_data = function(p32, data_word_count, pointer_count, header, 873 | tab) 874 | local s = tab 875 | 876 | s["ugv0"] = "Void" 877 | s["ugu0"] = read_struct_field(p32, "uint32", 32, 8, 0) 878 | return s 879 | end, 880 | } 881 | 882 | 883 | _M.EnumType2 = { 884 | ["none"] = 0, 885 | ["enum5"] = 1, 886 | ["enum6"] = 2, 887 | ["enum7"] = 3, 888 | ["UPPER-DASH"] = 4, 889 | ["lower_under_score"] = 5, 890 | ["UPPER_UNDER_SCORE"] = 6, 891 | ["lower space"] = 7, 892 | } 893 | 894 | 895 | _M.EnumType2Str = { 896 | [0] = "none", 897 | [1] = "enum5", 898 | [2] = "enum6", 899 | [3] = "enum7", 900 | [4] = "UPPER-DASH", 901 | [5] = "lower_under_score", 902 | [6] = "UPPER_UNDER_SCORE", 903 | [7] = "lower space", 904 | } 905 | 906 | _M.S1 = { 907 | id = "14559636115419563896", 908 | displayName = "proto/struct.capnp:S1", 909 | dataWordCount = 0, 910 | pointerCount = 1, 911 | discriminantCount = 0, 912 | discriminantOffset = 0, 913 | 914 | fields = { 915 | { name = "name", default = "", ["type"] = "text" }, 916 | }, 917 | 918 | calc_size_struct = function(data) 919 | local size = 8 920 | -- text 921 | if data["name"] then 922 | -- size 1, including trailing NULL 923 | size = size + round8(#data["name"] + 1) 924 | end 925 | return size 926 | end, 927 | 928 | calc_size = function(data) 929 | local size = 16 -- header + root struct pointer 930 | return size + _M.S1.calc_size_struct(data) 931 | end, 932 | 933 | flat_serialize = function(data, p32, pos) 934 | pos = pos and pos or 8 -- struct size in bytes 935 | local start = pos 936 | local dscrm 937 | if data["name"] and type(data["name"]) == "string" then 938 | local data_off = get_data_off(_M.S1, 0, pos) 939 | 940 | local len = #data["name"] + 1 941 | write_listp_buf(p32, _M.S1, 0, 2, len, data_off) 942 | 943 | ffi_copy(p32 + pos / 4, data["name"]) 944 | pos = pos + round8(len) 945 | end 946 | return pos - start + 8 947 | end, 948 | 949 | serialize = function(data, p8, size) 950 | if not p8 then 951 | size = _M.S1.calc_size(data) 952 | 953 | p8 = get_str_buf(size) 954 | end 955 | ffi_fill(p8, size) 956 | local p32 = ffi_cast(puint32, p8) 957 | 958 | -- Because needed size has been calculated, only 1 segment is needed 959 | p32[0] = 0 960 | p32[1] = (size - 8) / 8 961 | 962 | -- skip header 963 | write_structp(p32 + 2, _M.S1, 0) 964 | 965 | -- skip header & struct pointer 966 | _M.S1.flat_serialize(data, p32 + 4) 967 | 968 | return ffi_string(p8, size) 969 | end, 970 | 971 | parse_struct_data = function(p32, data_word_count, pointer_count, header, 972 | tab) 973 | 974 | local s = tab 975 | 976 | -- text 977 | local off, size, num = read_listp_struct(p32, header, _M.S1, 0) 978 | if off and num then 979 | -- dataWordCount + offset + pointerSize + off 980 | local p8 = ffi_cast(pint8, p32 + (0 + 0 + 1 + off) * 2) 981 | s["name"] = ffi_string(p8, num - 1) 982 | else 983 | s["name"] = nil 984 | end 985 | 986 | return s 987 | end, 988 | 989 | parse = function(bin, tab) 990 | if #bin < 16 then 991 | return nil, "message too short" 992 | end 993 | 994 | local header = new_tab(0, 4) 995 | local p32 = ffi_cast(puint32, bin) 996 | header.base = p32 997 | 998 | local nsegs = p32[0] + 1 999 | header.seg_sizes = {} 1000 | for i=1, nsegs do 1001 | header.seg_sizes[i] = p32[i] 1002 | end 1003 | local pos = round8(4 + nsegs * 4) 1004 | header.header_size = pos / 8 1005 | p32 = p32 + pos / 4 1006 | 1007 | if not tab then 1008 | tab = new_tab(0, 8) 1009 | end 1010 | local off, dw, pw = read_struct_buf(p32, header) 1011 | if off and dw and pw then 1012 | return _M.S1.parse_struct_data(p32 + 2 + off * 2, dw, pw, 1013 | header, tab) 1014 | else 1015 | return nil 1016 | end 1017 | end, 1018 | 1019 | } 1020 | 1021 | _M.S1.flag1 = 1 1022 | 1023 | _M.S1.flag2 = 2 1024 | 1025 | _M.S1.flag3 = "Hello" 1026 | 1027 | return _M 1028 | -------------------------------------------------------------------------------- /lua/handwritten_data_generator.lua: -------------------------------------------------------------------------------- 1 | local rand = require("random") 2 | local cjson = require("cjson") 3 | 4 | local ok, new_tab = pcall(require, "table.new") 5 | 6 | if not ok then 7 | new_tab = function (narr, nrec) return {} end 8 | end 9 | 10 | local _M = {} 11 | 12 | function _M.gen() 13 | local r = new_tab(0, 8) 14 | 15 | r.i0 = rand.uint32() 16 | r.i1 = rand.uint16() 17 | r.i2 = rand.int8() 18 | r.b0 = rand.bool() 19 | r.b1 = rand.bool() 20 | r.i3 = rand.int32() 21 | 22 | local s0 = {} 23 | s0.f0 = rand.float32() 24 | s0.f1 = rand.float64() 25 | r.s0 = s0 26 | 27 | -- r.e0 28 | r.l0 = rand.list(rand.uint8(), rand.int8) 29 | r.t0 = rand.text() 30 | -- r.e1 31 | return r 32 | end 33 | 34 | print(cjson.encode(_M.gen())) 35 | return _M 36 | -------------------------------------------------------------------------------- /lua/random.lua: -------------------------------------------------------------------------------- 1 | 2 | local random = math.random 3 | local modf = math.modf 4 | local char = string.char 5 | local concat = table.concat 6 | local print = print 7 | local byte = string.byte 8 | local error = error 9 | 10 | math.randomseed(os.time()) 11 | 12 | module(...) 13 | 14 | function random_nil() 15 | return (random(1, 5) % 5 == 5) 16 | end 17 | 18 | function bool() 19 | if random_nil() then 20 | return nil 21 | end 22 | if random(1, 2) % 2 then 23 | return true 24 | else 25 | return false 26 | end 27 | end 28 | 29 | function uint8() 30 | if random_nil() then 31 | return nil 32 | end 33 | return random(2^8) - 1 34 | end 35 | 36 | function uint16() 37 | if random_nil() then 38 | return nil 39 | end 40 | return random(2^16) - 1 41 | end 42 | 43 | function uint32() 44 | if random_nil() then 45 | return nil 46 | end 47 | return random(2^32) - 1 48 | end 49 | 50 | function uint64() 51 | if random_nil() then 52 | return nil 53 | end 54 | return random(2^64) - 1 55 | --error("64 bit number is not precisely represented by lua") 56 | end 57 | 58 | function int8() 59 | if random_nil() then 60 | return nil 61 | end 62 | return random(2^8) - 2^7 - 1 63 | end 64 | 65 | function int16() 66 | if random_nil() then 67 | return nil 68 | end 69 | return random(2^16) - 2^15 - 1 70 | end 71 | 72 | function int32() 73 | if random_nil() then 74 | return nil 75 | end 76 | return random(2^32) - 2^31 - 1 77 | end 78 | 79 | function int64() 80 | if random_nil() then 81 | return nil 82 | end 83 | return random(2^64) - 2^63 - 1 84 | --error("64 bit number is not precisely represented by lua") 85 | end 86 | 87 | function float32() 88 | if random_nil() then 89 | return nil 90 | end 91 | local i, f = modf(random() * 10^8) 92 | return f 93 | end 94 | 95 | function float64() 96 | if random_nil() then 97 | return nil 98 | end 99 | return random() 100 | end 101 | 102 | function data() 103 | if random_nil() then 104 | return nil 105 | end 106 | local n = uint8() 107 | 108 | local t = {} 109 | for i=1, n do 110 | t[i] = char(random(35, 122)) 111 | while t[i] == 92 do 112 | t[i] = char(random(35, 122)) 113 | end 114 | end 115 | return concat(t) 116 | end 117 | 118 | function text() 119 | if random_nil() then 120 | return nil 121 | end 122 | local n = uint8() 123 | 124 | local t = {} 125 | for i=1, n do 126 | t[i] = char(random(35, 122)) 127 | while t[i] == 92 do 128 | t[i] = char(random(35, 122)) 129 | end 130 | end 131 | return concat(t) 132 | end 133 | 134 | function list(n, rand_func) 135 | if random_nil() then 136 | return nil 137 | end 138 | local t = {} 139 | for i=1, n do 140 | t[i] = rand_func() 141 | end 142 | return t 143 | end 144 | -------------------------------------------------------------------------------- /lua/test.lua: -------------------------------------------------------------------------------- 1 | --local test_capnp = require "handwritten_capnp" 2 | --package.path = "lua/?.lua;proto/?.lua;" .. package.path 3 | 4 | local data_generator = require "data_generator" 5 | local example_capnp = require "example_capnp" 6 | local handwritten_capnp = require "handwritten_capnp" 7 | -- local log_capnp = require "log_capnp" 8 | local capnp = require "capnp" 9 | local cjson = require "cjson" 10 | local util = require "capnp.util" 11 | 12 | 13 | 14 | local data = { 15 | i0 = 32, 16 | i1 = 16, 17 | i2 = 127, 18 | b0 = true, 19 | b1 = true, 20 | i3 = 65536, 21 | e0 = "enum3", 22 | s0 = { 23 | f0 = 3.14, 24 | f1 = 3.14159265358979, 25 | }, 26 | l0 = { 28, 29 }, 27 | t0 = "hello", 28 | d0 = "\1\2\3\4\5\6\7", 29 | e1 = "enum7", 30 | ui1 = 0x0f0f, 31 | g0 = { 32 | ui2 = 0x12345678, 33 | }, 34 | 35 | u0 = { 36 | ug0 = { 37 | ugu0 = 32, 38 | } 39 | }, 40 | 41 | ls0 = { 42 | { 43 | f0 = 0.61803398875, 44 | f1 = 1.61803398875, 45 | }, 46 | { 47 | f0 = 2.71828182846, 48 | f1 = 2.71828182846, 49 | }, 50 | }, 51 | 52 | lt0 = { 53 | "foo", "bar", 54 | }, 55 | } 56 | 57 | local file = arg[1] 58 | local f = io.open(file, "w") 59 | local bin = handwritten_capnp.T1.serialize(data) 60 | 61 | local decoded = handwritten_capnp.T1.parse(bin) 62 | 63 | util.table_diff(data, decoded) 64 | 65 | f:write(bin) 66 | f:close() 67 | 68 | 69 | 70 | function write_file(name, content) 71 | local f = assert(io.open(name, "a")) 72 | f:write(content) 73 | f:close() 74 | end 75 | 76 | --[[ 77 | function random_test() 78 | local generated_data = data_generator.gen_log() 79 | 80 | local bin = log_capnp.Log.serialize(generated_data) 81 | 82 | local outfile = "/tmp/Log.txt" 83 | os.execute("rm " .. outfile) 84 | local fh = assert(io.popen("capnp decode /home/calio/code/dollar-store-fork/proto/log.capnp Log > " 85 | .. outfile, "w")) 86 | fh:write(bin) 87 | fh:close() 88 | 89 | write_file("Log.capnp.bin", bin) 90 | 91 | local decoded = util.parse_capnp_decode(outfile, "debug.txt") 92 | 93 | print(cjson.encode(generated_data)) 94 | print(cjson.encode(decoded)) 95 | 96 | util.table_diff(generated_data, decoded, "") 97 | end 98 | 99 | random_test() 100 | ]] 101 | print("Done") 102 | -------------------------------------------------------------------------------- /lua/tool.lua: -------------------------------------------------------------------------------- 1 | local bit = require("bit") 2 | local band = bit.band 3 | local rshift = bit.rshift 4 | local lshift = bit.lshift 5 | 6 | local i = 1 7 | local A, B, C, D 8 | local num = {} 9 | for d in string.gmatch(arg[1], "%x%x") do 10 | d = tonumber("0x" .. d) 11 | num[#num + 1] = d 12 | end 13 | 14 | local sig = band(3, num[1]) 15 | A = sig 16 | 17 | 18 | B = rshift(num[1], 2) 19 | for i = 2, 4 do 20 | B = B + lshift(num[i], 6 * (i - 1)) 21 | end 22 | 23 | if A == 0 then 24 | C = num[5] + lshift(num[6], 8) 25 | D = num[7] + lshift(num[8], 8) 26 | print(string.format("struct:%d, offset: %d, dw: %d, pw: %d", A, B, C, D)) 27 | elseif A == 1 then 28 | C = band(7, num[5]) 29 | D = rshift(num[5], 3) 30 | for i = 6, 8 do 31 | D = D + lshift(num[i], 5 * (i - 1)) 32 | end 33 | print(string.format("list:%d, offset: %d, size_type: %d, num: %d", A, B, C, D)) 34 | else 35 | print("not supported sig:", A) 36 | end 37 | 38 | -------------------------------------------------------------------------------- /proto/c++.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Kenton Varda 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | @0xbdf87d7bb8304e81; 25 | $namespace("capnp::annotations"); 26 | 27 | annotation namespace(file): Text; 28 | -------------------------------------------------------------------------------- /proto/enums.capnp: -------------------------------------------------------------------------------- 1 | @0xa7e0ba9e3ca0988d; 2 | 3 | using Lua = import "lua.capnp"; 4 | 5 | enum EnumType2 $Lua.naming("lower_underscore") { 6 | none @0; 7 | enum5 @1; 8 | enum6 @2; 9 | enum7 @3; 10 | upperDash @4 $Lua.naming("upper_dash"); 11 | lowerUnderScore @5 $Lua.naming("lower_underscore"); 12 | upperUnderScore @6 $Lua.naming("upper_underscore"); 13 | lowerSpace @7 $Lua.naming("lower_space"); 14 | } 15 | -------------------------------------------------------------------------------- /proto/example.capnp: -------------------------------------------------------------------------------- 1 | @0xa0d78d3689d48a0b; 2 | 3 | using import "enums.capnp".EnumType2; 4 | using import "struct.capnp".S1; 5 | using Lua = import "lua.capnp"; 6 | 7 | const pi :Float32 = 3.14159; 8 | 9 | struct T1 { 10 | struct T2 { 11 | f0 @0 :Float32; 12 | f1 @1 :Float64; 13 | sd0 @2 :Data; 14 | } 15 | 16 | i0 @0 :UInt32; 17 | i1 @1 :UInt16; 18 | i2 @3 :Int8; 19 | b0 @2 :Bool; 20 | b1 @4 :Bool; 21 | i3 @5 :Int32; 22 | s0 @6 :T2; 23 | e0 @7 :EnumType1; 24 | l0 @8 :List(Int8); 25 | t0 @9 :Text; 26 | d0 @11 :Data; 27 | e1 @10 :EnumType2; 28 | 29 | enum EnumType1 $Lua.naming("lower_underscore") { 30 | enum1 @0; 31 | enum2 @1; 32 | enum3 @2; 33 | enum4 @3 $Lua.literal("wEirdENum4"); 34 | upperDash @4 $Lua.naming("upper_dash"); 35 | } 36 | 37 | # unnamed union 38 | union { 39 | ui0 @12 :Int32; 40 | ui1 @13 :Int32; 41 | uv0 @14 :Void; 42 | } 43 | 44 | # group 45 | g0 :group { 46 | ui2 @15 :UInt32; 47 | } 48 | 49 | # named union = unamed union in a group 50 | u0 :union { 51 | ui3 @16 :UInt16; 52 | uv1 @17 :Void; 53 | ug0 :group { 54 | ugv0 @18 :Void; 55 | ugu0 @19 :UInt32; 56 | } 57 | ut0 @26 :Text; 58 | } 59 | 60 | ls0 @20 :List(T2); 61 | 62 | du0 @21 :UInt32 = 65535; 63 | db0 @22 :Bool = true; 64 | end @23 :Bool; # "end" is lua's reserved word 65 | o0 @24 :AnyPointer; 66 | lt0 @25 :List(Text); 67 | ld0 @27 :List(Data); 68 | # u1: union { 69 | # g1 :group { 70 | # v1 @17 :Void; 71 | # ui4 @18 :UInt32; 72 | # } 73 | # g2 :group { 74 | # b2 @19 :Bool; 75 | # } 76 | # } 77 | const welcomeText :Text = "Hello"; 78 | # Doesn't work for now 79 | # const constT2 :T2 = (f0 = 12345.67, f1 = 9876.54, sd0 = "\0\1\2\3"); 80 | u64 @28 :UInt64; 81 | } 82 | 83 | struct T3 { 84 | lsU16 @0 :List(UInt16); 85 | lsU32 @1 :List(UInt32); 86 | lsU64 @2 :List(UInt64); 87 | lsI16 @3 :List(Int16); 88 | lsI32 @4 :List(Int32); 89 | lsI64 @5 :List(Int64); 90 | } 91 | 92 | struct T4 { 93 | } 94 | 95 | struct T5 { 96 | llt0 @0 :List(List(Text)); 97 | lllt0 @1 :List(List(List(Text))); 98 | } 99 | -------------------------------------------------------------------------------- /proto/lua.capnp: -------------------------------------------------------------------------------- 1 | @0xb1ce27b8f28b5e4d; 2 | 3 | #naming style 4 | annotation naming(enum, enumerant) : Text; 5 | 6 | #literal enumerant value 7 | annotation literal(enumerant) : Text; 8 | -------------------------------------------------------------------------------- /proto/struct.capnp: -------------------------------------------------------------------------------- 1 | @0x9d3083cc2fcde74b; 2 | 3 | struct S1 { 4 | const flag1 :UInt32 = 0x1; 5 | const flag2 :UInt32 = 0x2; 6 | const flag3 :Text = "Hello"; 7 | 8 | name @0 :Text; 9 | } 10 | -------------------------------------------------------------------------------- /rockspec/lua-capnproto-0.1.3-3.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-capnproto" 2 | version = "0.1.3-3" 3 | source = { 4 | url = "git://github.com/cloudflare/lua-capnproto", 5 | tag = "v0.1.3-3", 6 | } 7 | description = { 8 | summary = "Lua-capnproto is a pure lua implementation of capnproto based on LuaJIT.", 9 | detailed = [[ 10 | Lua-capnproto is a pure lua implementation of capnproto based on LuaJIT. 11 | ]], 12 | homepage = "https://github.com/cloudflare/lua-capnproto", 13 | license = "BSD", 14 | } 15 | dependencies = { 16 | "lua ~> 5.1", -- in fact, this should be "luajit >= 2.1.0" 17 | } 18 | build = { 19 | -- We'll start here. 20 | type = "builtin", 21 | modules = { 22 | capnp = "capnp.lua", 23 | ['capnp.compile'] = "capnp/compile.lua", 24 | ['capnp.util'] = "capnp/util.lua", 25 | }, 26 | install = { 27 | bin = { 28 | ['capnpc-lua'] = "bin/capnpc-lua", 29 | ['capnpc-echo'] = "bin/capnpc-echo", 30 | ['schema.capnp'] = "bin/schema.capnp", 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rockspec/lua-capnproto-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-capnproto" 2 | version = "scm-1" 3 | source = { 4 | url = "git://github.com/cloudflare/lua-capnproto", 5 | branch = "master", 6 | } 7 | description = { 8 | summary = "Lua-capnproto is a pure lua implementation of capnproto based on LuaJIT.", 9 | detailed = [[ 10 | Lua-capnproto is a pure lua implementation of capnproto based on LuaJIT. 11 | ]], 12 | homepage = "https://github.com/cloudflare/lua-capnproto", 13 | license = "BSD", 14 | } 15 | dependencies = { 16 | "lua ~> 5.1", -- in fact, this should be "luajit >= 2.1.0" 17 | } 18 | build = { 19 | -- We'll start here. 20 | type = "builtin", 21 | modules = { 22 | capnp = "capnp.lua", 23 | ['capnp.compile'] = "capnp/compile.lua", 24 | ['capnp.util'] = "capnp/util.lua", 25 | }, 26 | install = { 27 | bin = { 28 | ['capnpc-lua'] = "bin/capnpc-lua", 29 | ['capnpc-echo'] = "bin/capnpc-echo", 30 | ['schema.capnp'] = "bin/schema.capnp", 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PATH=$(pwd)/bin:$PATH 4 | export LUA_PATH="?.lua;lua/?.lua;proto/?.lua;tests/?.lua;$LUA_PATH;;" 5 | 6 | echo "[Compile example.capnp]" 7 | capnp --verbose compile -olua proto/example.capnp proto/enums.capnp proto/lua.capnp proto/struct.capnp || exit 8 | 9 | echo "[Unit test...]" 10 | make test || exit 11 | 12 | #Disabled for now 13 | #echo "[capnp_test...]" 14 | #make test1 || exit 15 | 16 | #echo 17 | #echo "[Serialization test...]" 18 | #if [ $(uname) != "Linux" ]; then 19 | # make all || exit 20 | #else 21 | # CXX=g++-4.7 make all || exit 22 | # 23 | #fi 24 | #cpp/main > a.data || exit 25 | #luajit test.lua c.data || exit 26 | #echo 27 | #echo "capnp c++ result:" 28 | #xxd -g 1 a.data || exit 29 | #echo "capnp lua result:" 30 | #xxd -g 1 c.data || exit 31 | #diff a.data c.data 32 | 33 | echo "[Done]" 34 | -------------------------------------------------------------------------------- /tests/01-sanity.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | local lunit = require "lunitx" 3 | local capnp = require "capnp" 4 | local util = require "capnp.util" 5 | local handwritten = require "handwritten_capnp" 6 | local example = require "example_capnp" 7 | 8 | local mod = example 9 | 10 | local format = string.format 11 | 12 | 13 | local T1 = mod.T1 14 | local T2 = mod.T1.T2 15 | 16 | if _VERSION >= 'Lua 5.2' then 17 | _ENV = lunit.module('simple','seeall') 18 | else 19 | module( "simple", package.seeall, lunit.testcase ) 20 | end 21 | 22 | function assert_equalf(expected, actual) 23 | if math.abs(expected - actual) < 0.000001 then 24 | assert_true(true) 25 | return 26 | end 27 | error(format("expected %s, got %s", expected, actual)) 28 | end 29 | 30 | local to_hex_string = function(seg) 31 | local t = {} 32 | for i = 1, seg.pos do 33 | table.insert(t, bit.tohex(seg.data[i - 1], 2)) 34 | end 35 | return table.concat(t, " ") 36 | end 37 | 38 | local assert_hex = function (expected, actual) 39 | assert_equal(expected, to_hex_string(actual)) 40 | end 41 | 42 | local function assert_hex_string(expected, actual) 43 | local t = {} 44 | for i = 1, #actual do 45 | table.insert(t, bit.tohex(string.byte(actual, i), 2)) 46 | end 47 | assert_equal(expected, table.concat(t, " ")) 48 | end 49 | 50 | ----------------------------------------------------------------- 51 | function test_write_plain_val() 52 | local seg = { len = 32, pos = 0 } 53 | seg.data = ffi.new("char[?]", 32) -- 32 bytes 54 | 55 | capnp.write_struct_field(seg.data, true, "bool", 1, 0) 56 | capnp.write_struct_field(seg.data, 8, "uint8", 8, 1) 57 | capnp.write_struct_field(seg.data, 65535, "uint16", 16, 1) 58 | capnp.write_struct_field(seg.data, 1048576, "uint32", 32, 1) 59 | capnp.write_struct_field(seg.data, 4294967296, "uint64", 64, 1) 60 | capnp.write_struct_field(seg.data, 3.14, "float32", 32, 4) 61 | capnp.write_struct_field(seg.data, 1.41421, "float32", 32, 5) 62 | capnp.write_struct_field(seg.data, 3.14159265358979, "float64", 64, 3) 63 | 64 | seg.pos = seg.pos + 32 65 | assert_hex("01 08 ff ff 00 00 10 00 00 00 00 00 01 00 00 00 c3 f5 48 40 d5 04 b5 3f 11 2d 44 54 fb 21 09 40", seg) 66 | end 67 | 68 | function test_fix_float_default() 69 | assert_equal(0, capnp.fix_float32_default(3.14, 3.14)) 70 | assert_equal(0, capnp.fix_float64_default(3.1415926, 3.1415926)) 71 | assert_equalf(3.141592, capnp.fix_float32_default(3.141592, 0)) 72 | end 73 | 74 | function test_write_plain_val_with_default() 75 | local seg = { len = 32, pos = 0 } 76 | seg.data = ffi.new("char[?]", 32) -- 32 bytes 77 | 78 | capnp.write_struct_field(seg.data, true, "bool", 1, 0, 1) 79 | capnp.write_struct_field(seg.data, 8, "uint8", 8, 1, 8) 80 | capnp.write_struct_field(seg.data, 65535, "uint16", 16, 1, 65535) 81 | capnp.write_struct_field(seg.data, 1048576, "uint32", 32, 1, 1048576) 82 | capnp.write_struct_field(seg.data, 4294967296, "uint64", 64, 1, 4294967296) 83 | capnp.write_struct_field(seg.data, 3.14, "float32", 32, 4, 3.14) 84 | capnp.write_struct_field(seg.data, 1.41421, "float32", 32, 5, 1.41421) 85 | capnp.write_struct_field(seg.data, 3.14159265358979, "float64", 64, 3, 3.14159265358979) 86 | 87 | seg.pos = seg.pos + 32 88 | assert_hex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", seg) 89 | end 90 | 91 | function test_write_plain_val1() 92 | local seg = { len = 32, pos = 0 } 93 | seg.data = ffi.new("char[?]", 32) -- 32 bytes 94 | 95 | capnp.write_struct_field(seg.data, -1, "int8", 8, 0) 96 | capnp.write_struct_field(seg.data, 3.14159, "float32", 32, 1, 0) 97 | 98 | seg.pos = seg.pos + 32 99 | assert_hex("ff 00 00 00 d0 0f 49 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", seg) 100 | end 101 | 102 | function test_read_struct_field() 103 | local seg = { len = 32, pos = 0 } 104 | seg.data = ffi.new("char[?]", 32) -- 32 bytes 105 | 106 | -- write_struct_field(buf, val, size, off) 107 | capnp.write_struct_field(seg.data, true, "bool", 1, 0) 108 | capnp.write_struct_field(seg.data, 8, "uint8", 8, 1) 109 | capnp.write_struct_field(seg.data, 65535, "uint16", 16, 1) 110 | capnp.write_struct_field(seg.data, 1048576, "uint32", 32, 1) 111 | capnp.write_struct_field(seg.data, 4294967296ULL, "uint64", 64, 1) 112 | capnp.write_struct_field(seg.data, 3.14, "float32", 32, 4) 113 | capnp.write_struct_field(seg.data, 1.41421, "float32", 32, 5) 114 | capnp.write_struct_field(seg.data, 3.14159265358979, "float64", 64, 3) 115 | 116 | assert_equal(true, capnp.read_struct_field(seg.data, "bool", 1, 0)) 117 | assert_equal(8, capnp.read_struct_field(seg.data, "int8", 8, 1)) 118 | assert_equal(65535, capnp.read_struct_field(seg.data, "uint16", 16, 1)) 119 | assert_equal(1048576, capnp.read_struct_field(seg.data, "int32", 32, 1)) 120 | assert_equal(4294967296ULL, capnp.read_struct_field(seg.data, "uint64", 64, 1)) 121 | assert_equalf(3.14, capnp.read_struct_field(seg.data, "float32", 32, 4)) 122 | assert_equalf(1.41421, capnp.read_struct_field(seg.data, "float32", 32, 5)) 123 | assert_equalf(3.14159265358979, capnp.read_struct_field(seg.data, "float64", 64, 3)) 124 | end 125 | 126 | function test_read_struct_field_with_default() 127 | local seg = { len = 32, pos = 0 } 128 | seg.data = ffi.new("char[?]", 32) -- 32 bytes 129 | 130 | assert_equal(true, capnp.read_struct_field(seg.data, "bool", 1, 0, 1)) 131 | assert_equal(8, capnp.read_struct_field(seg.data, "int8", 8, 1, 8)) 132 | assert_equal(65535, capnp.read_struct_field(seg.data, "uint16", 16, 1, 65535)) 133 | assert_equal(1048576, capnp.read_struct_field(seg.data, "int32", 32, 1, 1048576)) 134 | assert_equal(3456789012, capnp.read_struct_field(seg.data, "uint32", 32, 1, 3456789012)) 135 | assert_equal(-123456789012345LL, capnp.read_struct_field(seg.data, "int64", 64, 1, -123456789012345LL)) 136 | assert_equal(345678901234567890ULL, capnp.read_struct_field(seg.data, "uint64", 64, 1, 345678901234567890ULL)) 137 | assert_equalf(3.14, capnp.read_struct_field(seg.data, "float32", 32, 4, 3.14)) 138 | assert_equalf(1.41421, capnp.read_struct_field(seg.data, "float32", 32, 5, 1.41421)) 139 | assert_equal(3.14159265358, capnp.read_struct_field(seg.data, "float64", 64, 3, 3.14159265358)) 140 | end 141 | 142 | --[[ 143 | function test_write_structp() 144 | local seg = capnp.new_segment() -- 32 bytes 145 | 146 | capnp.write_structp(seg.data, T1, 0) 147 | 148 | seg.pos = seg.pos + 8 149 | assert_hex("00 00 00 00 02 00 03 00", seg) 150 | end 151 | 152 | function test_write_structp1() 153 | local seg = capnp.new_segment() 154 | 155 | capnp.write_structp(seg.data + 8, T1, 2) 156 | seg.pos = seg.pos + 16 157 | 158 | assert_hex("00 00 00 00 00 00 00 00 08 00 00 00 02 00 03 00", seg) 159 | end 160 | 161 | function test_write_listp() 162 | local seg = capnp.new_segment(8) 163 | 164 | -- write_listp = function (buf, size, num, data_off) 165 | capnp.write_listp(seg.data, 2, 1, 0) 166 | 167 | seg.pos = seg.pos + 8 168 | assert_hex("01 00 00 00 0a 00 00 00", seg) 169 | end 170 | ]] 171 | 172 | 173 | function test_read_struct_buf() 174 | local buf = util.new_buf({ 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00 }) 175 | local off, dw, pw = capnp.read_struct_buf(buf) 176 | assert_equal(4, off) 177 | assert_equal(2, dw) 178 | assert_equal(4, pw) 179 | end 180 | 181 | function test_far_pointer_to_struct() 182 | local buf = util.new_buf({ 183 | 01, 00, 00, 00, 01, 00, 00, 00, -- 2 segs seg1: 1 word 184 | 02, 00, 00, 00, 00, 00, 00, 00, -- seg2: 4 word padding 185 | 0x0a, 00, 00, 00, 01, 00, 00, 00, -- far pointer seg:1, offset:1 186 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -- start of seg 2 187 | 08, 00, 00, 00, 02, 00, 04, 00, -- struct pointer 188 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -- start of seg 2 189 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -- start of seg 2 190 | }) 191 | local header = { 192 | base = buf, 193 | header_size = 2, 194 | seg_sizes = { 1, 2, }, 195 | } 196 | 197 | local off, dw, pw = capnp.read_struct_buf(buf + 4, header) 198 | assert_equal(4, off) 199 | assert_equal(2, dw) 200 | assert_equal(4, pw) 201 | end 202 | 203 | function test_far_pointer_to_list() 204 | local buf = util.new_buf({ 205 | 01, 00, 00, 00, 01, 00, 00, 00, -- 2 segs seg1: 1 word 206 | 03, 00, 00, 00, 00, 00, 00, 00, -- seg2: 3 word padding 207 | 02, 00, 00, 00, 01, 00, 00, 00, -- far pointer seg 1 208 | 05, 00, 00, 00, 0x27, 00, 00, 00, -- list pointer 209 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 210 | 00, 00, 00, 00, 00, 00, 00, 00, 211 | }) 212 | local header = { 213 | base = buf, 214 | header_size = 2, 215 | seg_sizes = { 1, 2, }, 216 | } 217 | local T = { 218 | dataWordCount = 0 219 | } 220 | local off, size_type, num = capnp.read_listp_struct(buf + 4, header, T, 0) 221 | assert_equal(2, off) 222 | assert_equal(7, size_type) 223 | assert_equal(4, num) 224 | end 225 | 226 | function test_write_text() 227 | local buf = ffi.new("char[?]", 8 * 4) 228 | local p32 = ffi.cast("int32_t *", buf) 229 | local n = capnp.write_text(p32, "12345678", 1) 230 | 231 | assert_equal(16, n) 232 | assert_equal("05 00 00 00 4a 00 00 00 00 00 00 00 00 00 00 00 31 32 33 34 35 36 37 38 00 00 00 00 00 00 00 00", util.hex_buf_str(buf, 32)) 233 | end 234 | 235 | function test_write_list_data_num() 236 | local size = 8 237 | local buf = ffi.new("char[?]", size) 238 | local p32 = ffi.cast("int32_t *", buf) 239 | 240 | local n = capnp.write_list_data(p32, { -1, -2, -3, -4, 0, 1, 2, 3 }, 0, "int8") 241 | assert_equal("ff fe fd fc 00 01 02 03", util.hex_buf_str(buf, size)) 242 | assert_equal(size, n) 243 | end 244 | 245 | function test_write_list_data_bool() 246 | local size = 8 247 | local buf = ffi.new("char[?]", size) 248 | local p32 = ffi.cast("int32_t *", buf) 249 | 250 | local n = capnp.write_list_data(p32, { true, false, true }, 0, "bool") 251 | assert_equal("05 00 00 00 00 00 00 00", util.hex_buf_str(buf, size)) 252 | assert_equal(size, n) 253 | end 254 | 255 | function test_write_list_data_data() 256 | local size = 8 * 7 257 | local buf = ffi.new("char[?]", size) 258 | local p32 = ffi.cast("int32_t *", buf) 259 | 260 | local n = capnp.write_list_data(p32, { "\1\2\3", "\4\5\6\7", "\9\10\11\12\13\14\15\16\17" }, 0, "data") -- writes 3 list pointer first, then list data 261 | assert_equal("09 00 00 00 1a 00 00 00 09 00 00 00 22 00 00 00 09 00 00 00 4a 00 00 00 01 02 03 00 00 00 00 00 04 05 06 07 00 00 00 00 09 0a 0b 0c 0d 0e 0f 10 11 00 00 00 00 00 00 00", util.hex_buf_str(buf, size)) 262 | assert_equal(size, n) 263 | end 264 | 265 | function test_write_list_data_text() 266 | local size = 8 * 7 267 | local buf = ffi.new("char[?]", size) 268 | local p32 = ffi.cast("int32_t *", buf) 269 | 270 | local n = capnp.write_list_data(p32, { "ab", "def", "ijklmnop" }, 0, "text") -- writes 3 list pointer first, then list data 271 | assert_equal(size, n) 272 | assert_equal("09 00 00 00 1a 00 00 00 09 00 00 00 22 00 00 00 09 00 00 00 4a 00 00 00 61 62 00 00 00 00 00 00 64 65 66 00 00 00 00 00 69 6a 6b 6c 6d 6e 6f 70 00 00 00 00 00 00 00 00", util.hex_buf_str(buf, size)) 273 | end 274 | 275 | function test_write_list_data_list() 276 | local size = 8 *9 277 | local buf = ffi.new("char[?]", size) 278 | local p32 = ffi.cast("int32_t *", buf) 279 | 280 | -- list of text 281 | local n = capnp.write_list_data(p32, { {"ab", "def"}, {"ijklmnop"} }, 0, "list", "text") -- writes 3 list pointer first, then list data 282 | assert_equal(size, n) 283 | assert_equal("05 00 00 00 16 00 00 00 11 00 00 00 0e 00 00 00 05 00 00 00 1a 00 00 00 05 00 00 00 22 00 00 00 61 62 00 00 00 00 00 00 64 65 66 00 00 00 00 00 01 00 00 00 4a 00 00 00 69 6a 6b 6c 6d 6e 6f 70 00 00 00 00 00 00 00 00", util.hex_buf_str(buf, size)) 284 | end 285 | 286 | function test_write_list_data_struct() 287 | local size = 8 * (1 + 3 * 2) 288 | local buf = ffi.new("char[?]", size) 289 | local p32 = ffi.cast("int32_t *", buf) 290 | local n = capnp.write_list_data(p32, { { f0 = 1.1, f1 = 1.1111 }, {f0 = 1.2, f1 = 1.2222 } }, 0, "struct", T2) -- writes 3 list pointer first, then list data 291 | assert_equal(size, n) 292 | assert_equal("08 00 00 00 02 00 01 00 cd cc 8c 3f 00 00 00 00 9e 5e 29 cb 10 c7 f1 3f 00 00 00 00 00 00 00 00 9a 99 99 3f 00 00 00 00 3c bd 52 96 21 8e f3 3f 00 00 00 00 00 00 00 00", util.hex_buf_str(buf, size)) 293 | end 294 | 295 | function test_write_list() 296 | local size = 8 * (1 + 1 + 3 * 2) -- listp + tag + struct_size * 2 297 | local buf = ffi.new("char[?]", size) 298 | local p32 = ffi.cast("int32_t *", buf) 299 | local data = { { f0 = 1.1, f1 = 1.1111 }, {f0 = 1.2, f1 = 1.2222 } } 300 | 301 | local n = capnp.write_list(p32, data, 8, "list", "struct", T2) -- writes 3 list pointer first, then list data 302 | assert_equal(size, n + 8) 303 | assert_equal("01 00 00 00 37 00 00 00 08 00 00 00 02 00 01 00 cd cc 8c 3f 00 00 00 00 9e 5e 29 cb 10 c7 f1 3f 00 00 00 00 00 00 00 00 9a 99 99 3f 00 00 00 00 3c bd 52 96 21 8e f3 3f 00 00 00 00 00 00 00 00", util.hex_buf_str(buf, size)) 304 | end 305 | 306 | function test_write_list() 307 | local size = 8 * (1 + 1 + 3 * 2 + 1 + 1) -- listp + tag + struct_size * 2 + sd0 data + sd0 data 308 | local buf = ffi.new("char[?]", size) 309 | local p32 = ffi.cast("int32_t *", buf) 310 | 311 | --ffi.fill(buf, size, 0xff) 312 | local data = { { f0 = 1.1, f1 = 1.1111, sd0 = "\1\2\3\4", }, {f0 = 1.2, f1 = 1.2222, sd0 = "\5\6\7\8\9\10\11\12" } } 313 | 314 | local n = capnp.write_list(p32, data, 8, "list", "struct", T2) 315 | assert_equal(size, n + 8) -- write_list return size doesn't count list pointer size 316 | assert_equal("01 00 00 00 37 00 00 00 08 00 00 00 02 00 01 00 cd cc 8c 3f 00 00 00 00 9e 5e 29 cb 10 c7 f1 3f 0d 00 00 00 22 00 00 00 9a 99 99 3f 00 00 00 00 3c bd 52 96 21 8e f3 3f 05 00 00 00 42 00 00 00 01 02 03 04 00 00 00 00 05 06 07 08 09 0a 0b 0c", util.hex_buf_str(buf, size)) 317 | end 318 | 319 | function test_write_list() 320 | local size = 8 * (1 + 1 * 2 + 1 * 2 + 3 * 3) -- listp + listp * 2 + tag * 2 + struct_size * 3 321 | local buf = ffi.new("char[?]", size) 322 | local p32 = ffi.cast("int32_t *", buf) 323 | --print("test_write_list", p32) 324 | local data = { 325 | { 326 | { f0 = 1.1, f1 = 1.1111 }, 327 | { f0 = 1.2, f1 = 1.2222 }, 328 | }, 329 | { 330 | { f0 = 1.3, f1 = 1.3333 }, 331 | } 332 | } 333 | 334 | local n = capnp.write_list(p32, data, 8, "list", "list", "struct", T2) -- writes 3 list pointer first, then list data 335 | 336 | assert_equal(size, n + 8) -- write_list return size doesn't count list pointer size 337 | assert_equal("01 00 00 00 16 00 00 00 05 00 00 00 37 00 00 00 1d 00 00 00 1f 00 00 00 08 00 00 00 02 00 01 00 cd cc 8c 3f 00 00 00 00 9e 5e 29 cb 10 c7 f1 3f 00 00 00 00 00 00 00 00 9a 99 99 3f 00 00 00 00 3c bd 52 96 21 8e f3 3f 00 00 00 00 00 00 00 00 04 00 00 00 02 00 01 00 66 66 a6 3f 00 00 00 00 da 1b 7c 61 32 55 f5 3f 00 00 00 00 00 00 00 00", util.hex_buf_str(buf, size)) 338 | end 339 | 340 | function test_read_list_data() 341 | local buf = ffi.new("char[?]", 8 * 9) 342 | local p32 = ffi.cast("int32_t *", buf) 343 | 344 | local data = { {"ab", "def"}, {"ijklmnop"} } 345 | -- list of text 346 | capnp.write_list_data(p32, data, 0, "list", "text") 347 | local copy = capnp.read_list_data(p32, {}, 2, "list", "text") 348 | 349 | assert_not_nil(copy) 350 | assert_not_nil(copy[1]) 351 | assert_not_nil(copy[2]) 352 | assert_equal(2, #copy) 353 | assert_equal(data[1][1], copy[1][1]) 354 | assert_equal(data[1][2], copy[1][2]) 355 | assert_equal(copy[2][1], copy[2][1]) 356 | end 357 | 358 | function test_read_list_data_struct() 359 | local size = (1 + 3 * 2) * 8 360 | local buf = ffi.new("char[?]", size) 361 | local p32 = ffi.cast("int32_t *", buf) 362 | local data = { { f0 = 1.1, f1 = 1.1111 }, {f0 = 1.2, f1 = 1.2222 } } 363 | 364 | capnp.write_list_data(p32, data, 0, "struct", T2) 365 | local copy = capnp.read_list_data(p32, {}, 4, "struct", T2) 366 | assert_not_nil(copy) 367 | assert_equal(2, #copy) 368 | assert_equalf(data[1].f0, copy[1].f0) 369 | assert_equalf(data[1].f1, copy[1].f1) 370 | assert_equalf(data[2].f0, copy[2].f0) 371 | assert_equalf(data[2].f1, copy[2].f1) 372 | end 373 | 374 | -- 64 bit number in cdata 375 | function test_write_read_plain_uint64() 376 | local seg = { len = 32, pos = 0 } 377 | seg.data = ffi.new("char[?]", 32) -- 32 bytes 378 | local p32 = ffi.cast("uint32_t *", seg.data) 379 | 380 | local uint64p = ffi.new("uint64_t[?]", 1) 381 | local uint32p = ffi.cast("uint32_t *", uint64p) 382 | uint32p[0] = 1 383 | uint32p[1] = 2 384 | 385 | capnp.write_struct_field(p32, 1393891543746000128, "int64", 64, 0, 0) 386 | capnp.write_struct_field(p32, uint64p[0], "uint64", 64, 1, 0) 387 | --capnp.write_struct_field(seg.data, 3.14159, "float32", 32, 1, 0) 388 | 389 | seg.pos = seg.pos + 32 390 | assert_hex("00 b5 66 50 f9 18 58 13 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", seg) 391 | 392 | assert_equal(1393891543746000128, capnp.read_struct_field(p32, "int64", 64, 0)) 393 | local uint64 = capnp.read_struct_field(p32, "uint64", 64, 1) 394 | local uint64_str = tostring(uint64) 395 | assert_equal("cdata", type(uint64)) 396 | assert_equal("8589934593ULL", uint64_str) 397 | end 398 | 399 | -------------------------------------------------------------------------------- /tests/02-util.lua: -------------------------------------------------------------------------------- 1 | local util = require "capnp.util" 2 | local lunit = require "lunitx" 3 | local hw_capnp = require "handwritten_capnp" 4 | 5 | if _VERSION >= 'Lua 5.2' then 6 | _ENV = lunit.module('simple','seeall') 7 | else 8 | module( "simple", package.seeall, lunit.testcase ) 9 | end 10 | 11 | function test_underscore_naming() 12 | assert_equal("request_uri", util.lower_underscore_naming("requestURI")) 13 | assert_equal("REQUEST_URI", util.upper_underscore_naming("requestURI")) 14 | assert_equal("REQUEST", util.upper_underscore_naming("request")) 15 | assert_equal("TEST_RES", util.upper_underscore_naming("testRes")) 16 | assert_equal("request_uri", util.lower_underscore_naming("requestURI")) 17 | assert_equal("REQUEST_URI", util.upper_underscore_naming("requestURI")) 18 | assert_equal("REQUEST", util.upper_underscore_naming("request")) 19 | assert_equal("TEST_RES", util.upper_underscore_naming("testRes")) 20 | assert_equal("request_uri", util.lower_underscore_naming("requestURI")) 21 | assert_equal("VERSION-CONTROL", util.upper_dash_naming("versionControl")) 22 | assert_equal("VERSION-CONTROL", util.upper_dash_naming("versionControl")) 23 | assert_equal("foo bar", util.lower_space_naming("fooBar")) 24 | assert_equal("foo", util.lower_space_naming("foo")) 25 | end 26 | 27 | function test_hex_utils() 28 | local buf = util.new_buf({ 01, 02, 03, 04, 10, 11, 12, 13 }) 29 | assert_equal("01 02 03 04 0a 0b 0c 0d", util.hex_buf_str(buf, 8)) 30 | end 31 | 32 | 33 | function test_to_text() 34 | local T1 = hw_capnp.T1 35 | local val = { 36 | b0 = true, -- default 0 37 | db0 = true, -- default 1 38 | } 39 | 40 | assert_equal("(b0 = true, db0 = true)", util.to_text(val, T1)) 41 | end 42 | 43 | function test_to_text1() 44 | local T1 = hw_capnp.T1 45 | local val = { 46 | b0 = false, -- default 0 47 | db0 = false, -- default 1 48 | } 49 | 50 | assert_equal("(b0 = false, db0 = false)", util.to_text(val, T1)) 51 | end 52 | 53 | function test_get_field_type(field) 54 | local field = { 55 | name = "lt0", 56 | codeOrder = 22, 57 | discriminantValue = 65535, 58 | slot = { 59 | offset = 6, 60 | ["type"] = { 61 | list = { 62 | elementType = {text = "void"} } 63 | }, 64 | defaultValue = {list = 'opaque pointer'}, 65 | hadExplicitDefault = false 66 | }, 67 | ordinal = {explicit = 25} 68 | } 69 | 70 | local typ = util.get_field_type(field) 71 | assert_equal(2, #typ) 72 | assert_equal("list", typ[1]) 73 | assert_equal("text", typ[2]) 74 | end 75 | 76 | function test_get_field_type1(field) 77 | local field = { 78 | name = "o0", 79 | codeOrder = 21, 80 | discriminantValue = 65535, 81 | slot = { 82 | offset = 5, 83 | ["type"] = {anyPointer = "void"}, 84 | defaultValue = { 85 | anyPointer = 'opaque pointer' }, 86 | hadExplicitDefault = false }, 87 | ordinal = {explicit = 24} 88 | } 89 | 90 | local typ = util.get_field_type(field) 91 | assert_equal(1, #typ) 92 | assert_equal("anyPointer", typ[1]) 93 | end 94 | 95 | function test_get_field_type2(field) 96 | local field = { name = "ls0", 97 | codeOrder = 17, 98 | discriminantValue = 65535, 99 | slot = { 100 | offset = 4, 101 | ["type"] = { 102 | list = { 103 | elementType = { 104 | struct = { 105 | typeId = "17202330444354522981" } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | local typ = util.get_field_type(field) 112 | assert_equal(3, #typ) 113 | assert_equal("list", typ[1]) 114 | assert_equal("struct", typ[2]) 115 | assert_equal("17202330444354522981", typ[3]) 116 | end 117 | -------------------------------------------------------------------------------- /tests/03-compile.lua: -------------------------------------------------------------------------------- 1 | local util = require "capnp.util" 2 | local lunit = require "lunitx" 3 | local compile = require "capnp.compile" 4 | 5 | if _VERSION >= 'Lua 5.2' then 6 | _ENV = lunit.module('simple','seeall') 7 | else 8 | module( "simple", package.seeall, lunit.testcase ) 9 | end 10 | 11 | function test_comp_calc_list_size() 12 | local res = {} 13 | local field = { 14 | name = "lt0", 15 | codeOrder = 22, 16 | discriminantValue = 65535, 17 | slot = { 18 | offset = 6, 19 | ["type"] = { 20 | list = { 21 | elementType = {text = "void"} } }, 22 | defaultValue = {list = 'opaque pointer'}, 23 | hadExplicitDefault = false }, 24 | ordinal = {explicit = 25} } 25 | local name = string.format("data[\"%s\"]", field.name) 26 | local list_type = util.get_field_type(field) 27 | 28 | compile.comp_calc_list_size(res, field, {}, name, 1, select(2, unpack(list_type))) 29 | print(table.concat(res)) 30 | end 31 | -------------------------------------------------------------------------------- /tests/10-encode-decode.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | local lunit = require "lunitx" 3 | local capnp = require "capnp" 4 | local util = require "capnp.util" 5 | 6 | hw_capnp = require "example_capnp" 7 | --local format = string.format 8 | 9 | local tdiff = util.table_diff 10 | 11 | 12 | if _VERSION >= 'Lua 5.2' then 13 | _ENV = lunit.module('simple','seeall') 14 | else 15 | module( "simple", package.seeall, lunit.testcase ) 16 | end 17 | 18 | local copy = {} 19 | 20 | function assert_equalf(expected, actual) 21 | assert_true(math.abs(expected - actual) < 0.000001) 22 | end 23 | 24 | function test_basic_value() 25 | local data = { 26 | i0 = 32, 27 | } 28 | 29 | assert_equal(136, hw_capnp.T1.calc_size(data)) 30 | local bin = hw_capnp.T1.serialize(data) 31 | copy = hw_capnp.T1.parse(bin, copy) 32 | assert_equal(data.i0, copy.i0) 33 | assert_equal(0, copy.i1) 34 | assert_equal(0, copy.i2) 35 | assert_equal(false, copy.b0) 36 | assert_equal(false, copy.b1) 37 | assert_equal(0, copy.i3) 38 | assert_equal("enum1", copy.e0) 39 | assert_equal("none", copy.e1) 40 | assert_nil(copy.s0) 41 | assert_nil(copy.l0) 42 | assert_nil(copy.t0) 43 | end 44 | 45 | function test_basic_value1() 46 | local data = { 47 | b0 = true, 48 | } 49 | 50 | assert_equal(136, hw_capnp.T1.calc_size(data)) 51 | local bin = hw_capnp.T1.serialize(data) 52 | copy = hw_capnp.T1.parse(bin, copy) 53 | assert_equal(0, copy.i0) 54 | assert_equal(0, copy.i1) 55 | assert_equal(0, copy.i2) 56 | assert_equal(true, copy.b0) 57 | assert_equal(false, copy.b1) 58 | assert_equal(0, copy.i3) 59 | assert_equal("enum1", copy.e0) 60 | assert_equal("none", copy.e1) 61 | assert_nil(copy.s0) 62 | assert_nil(copy.l0) 63 | assert_nil(copy.t0) 64 | end 65 | 66 | function test_basic_value2() 67 | local data = { 68 | i2 = -8, 69 | } 70 | 71 | assert_equal(136, hw_capnp.T1.calc_size(data)) 72 | local bin = hw_capnp.T1.serialize(data) 73 | copy = hw_capnp.T1.parse(bin, copy) 74 | assert_equal(0, copy.i0) 75 | assert_equal(0, copy.i1) 76 | assert_equal(-8, copy.i2) 77 | assert_equal(false, copy.b0) 78 | assert_equal(false, copy.b1) 79 | assert_equal(0, copy.i3) 80 | assert_equal("enum1", copy.e0) 81 | assert_equal("none", copy.e1) 82 | assert_nil(copy.s0) 83 | assert_nil(copy.l0) 84 | assert_nil(copy.t0) 85 | end 86 | 87 | function test_basic_value3() 88 | local data = { 89 | s0 = {}, 90 | } 91 | 92 | assert_equal(136 + 24, hw_capnp.T1.calc_size(data)) 93 | local bin = hw_capnp.T1.serialize(data) 94 | copy = hw_capnp.T1.parse(bin, copy) 95 | assert_equal(0, copy.i0) 96 | assert_equal(0, copy.i1) 97 | assert_equal(0, copy.i2) 98 | assert_equal(false, copy.b0) 99 | assert_equal(false, copy.b1) 100 | assert_equal(0, copy.i3) 101 | assert_equal("enum1", copy.e0) 102 | assert_equal("none", copy.e1) 103 | assert_not_nil(copy.s0) 104 | assert_equal(0, copy.s0.f0) 105 | assert_equal(0, copy.s0.f1) 106 | assert_nil(copy.l0) 107 | assert_nil(copy.t0) 108 | end 109 | 110 | function test_basic_value4() 111 | local data = { 112 | s0 = { 113 | f0 = 3.14, 114 | f1 = 3.1415926535 115 | }, 116 | } 117 | 118 | assert_equal(136, hw_capnp.T1.calc_size(data)) 119 | local bin = hw_capnp.T1.serialize(data) 120 | copy = hw_capnp.T1.parse(bin, copy) 121 | assert_equal(0, copy.i0) 122 | assert_equal(0, copy.i1) 123 | assert_equal(0, copy.i2) 124 | assert_equal(false, copy.b0) 125 | assert_equal(false, copy.b1) 126 | assert_equal(0, copy.i3) 127 | assert_equal("enum1", copy.e0) 128 | assert_equal("none", copy.e1) 129 | assert_not_nil(copy.s0) 130 | assert_equalf(3.1400001049042, copy.s0.f0) 131 | assert_equal(3.1415926535, copy.s0.f1) 132 | assert_nil(copy.l0) 133 | assert_nil(copy.t0) 134 | end 135 | 136 | function test_basic_value4() 137 | local data = { 138 | l0 = { 1, -1, 127 } 139 | } 140 | 141 | assert_equal(136 + 8, hw_capnp.T1.calc_size(data)) 142 | local bin = hw_capnp.T1.serialize(data) 143 | copy = hw_capnp.T1.parse(bin, copy) 144 | assert_equal(0, copy.i0) 145 | assert_equal(0, copy.i1) 146 | assert_equal(0, copy.i2) 147 | assert_equal(false, copy.b0) 148 | assert_equal(false, copy.b1) 149 | assert_equal(0, copy.i3) 150 | assert_equal("enum1", copy.e0) 151 | assert_equal("none", copy.e1) 152 | assert_nil(copy.s0) 153 | assert_equal(3, #copy.l0) 154 | assert_equal(1, copy.l0[1]) 155 | assert_equal(-1, copy.l0[2]) 156 | assert_equal(127, copy.l0[3]) 157 | assert_nil(copy.t0) 158 | end 159 | 160 | function test_basic_value5() 161 | local data = { 162 | t0 = "1234567890~!#$%^&*()-=_+[]{};':|,.<>/?" 163 | } 164 | 165 | assert_equal(136 + 40, hw_capnp.T1.calc_size(data)) 166 | local bin = hw_capnp.T1.serialize(data) 167 | copy = hw_capnp.T1.parse(bin, copy) 168 | assert_equal(0, copy.i0) 169 | assert_equal(0, copy.i1) 170 | assert_equal(0, copy.i2) 171 | assert_equal(false, copy.b0) 172 | assert_equal(false, copy.b1) 173 | assert_equal(0, copy.i3) 174 | assert_equal("enum1", copy.e0) 175 | assert_equal("none", copy.e1) 176 | assert_nil(copy.s0) 177 | assert_nil(copy.l0) 178 | assert_equal(38, #copy.t0) 179 | assert_equal("1234567890~!#$%^&*()-=_+[]{};':|,.<>/?", copy.t0) 180 | end 181 | 182 | function test_basic_value6() 183 | local data = { 184 | i0 = 32, 185 | i1 = 16, 186 | i2 = 127, 187 | b0 = true, 188 | b1 = true, 189 | i3 = 65536, 190 | e0 = "enum3", 191 | s0 = { 192 | f0 = 3.14, 193 | f1 = 3.14159265358979, 194 | }, 195 | l0 = { 28, 29 }, 196 | t0 = "hello", 197 | e1 = "enum7", 198 | } 199 | 200 | -- header + T1.size + T2.size + l0 + t0 201 | assert_equal(16 + 120 + 24 + 8 + 8, hw_capnp.T1.calc_size(data)) 202 | local bin = hw_capnp.T1.serialize(data) 203 | util.write_file("dump", bin) 204 | copy = hw_capnp.T1.parse(bin, copy) 205 | assert_equal(32, copy.i0) 206 | assert_equal(16, copy.i1) 207 | assert_equal(127, copy.i2) 208 | assert_equal(true, copy.b0) 209 | assert_equal(true, copy.b1) 210 | assert_equal(65536, copy.i3) 211 | assert_equal("enum3", copy.e0) 212 | assert_equal("enum7", copy.e1) 213 | assert_not_nil(copy.s0) 214 | assert_equalf(3.14, copy.s0.f0) 215 | assert_equal(3.14159265358979, copy.s0.f1) 216 | assert_not_nil(copy.l0) 217 | assert_equal(2, #copy.l0) 218 | assert_equal(28, copy.l0[1]) 219 | assert_equal(29, copy.l0[2]) 220 | assert_equal(5, #copy.t0) 221 | assert_equal("hello", copy.t0) 222 | end 223 | function test_union_value() 224 | local data = { 225 | ui1 = 32, 226 | } 227 | 228 | assert_equal(136, hw_capnp.T1.calc_size(data)) 229 | local bin = hw_capnp.T1.serialize(data) 230 | copy = hw_capnp.T1.parse(bin, copy) 231 | assert_equal(0, copy.i0) 232 | assert_equal(0, copy.i1) 233 | assert_equal(0, copy.i2) 234 | assert_equal(false, copy.b0) 235 | assert_equal(false, copy.b1) 236 | assert_equal(0, copy.i3) 237 | assert_equal("enum1", copy.e0) 238 | assert_equal("none", copy.e1) 239 | assert_nil(copy.s0) 240 | assert_nil(copy.l0) 241 | assert_nil(copy.t0) 242 | assert_equal(data.ui1, copy.ui1) 243 | assert_nil(copy.ui0) 244 | assert_nil(copy.uv0) 245 | end 246 | 247 | function test_union_value() 248 | local data = { 249 | uv0 = "Void", 250 | } 251 | 252 | assert_equal(136, hw_capnp.T1.calc_size(data)) 253 | local bin = hw_capnp.T1.serialize(data) 254 | copy = hw_capnp.T1.parse(bin, copy) 255 | assert_equal(0, copy.i0) 256 | assert_equal(0, copy.i1) 257 | assert_equal(0, copy.i2) 258 | assert_equal(false, copy.b0) 259 | assert_equal(false, copy.b1) 260 | assert_equal(0, copy.i3) 261 | assert_equal("enum1", copy.e0) 262 | assert_equal("none", copy.e1) 263 | assert_nil(copy.s0) 264 | assert_nil(copy.l0) 265 | assert_nil(copy.t0) 266 | assert_nil(copy.ui1) 267 | assert_nil(copy.ui0) 268 | assert_equal(data.uv0, copy.uv0) 269 | end 270 | 271 | function test_union_value() 272 | local data = { 273 | g0 = { 274 | ui2 = 48, 275 | }, 276 | } 277 | 278 | assert_equal(136, hw_capnp.T1.calc_size(data)) 279 | local bin = hw_capnp.T1.serialize(data) 280 | copy = hw_capnp.T1.parse(bin, copy) 281 | assert_equal(0, copy.i0) 282 | assert_equal(0, copy.i1) 283 | assert_equal(0, copy.i2) 284 | assert_equal(false, copy.b0) 285 | assert_equal(false, copy.b1) 286 | assert_equal(0, copy.i3) 287 | assert_equal("enum1", copy.e0) 288 | assert_equal("none", copy.e1) 289 | assert_nil(copy.s0) 290 | assert_nil(copy.l0) 291 | assert_nil(copy.t0) 292 | assert_equal(0, copy.ui0) -- ui0 is set by default 293 | assert_nil(copy.ui1) 294 | assert_nil(copy.uv0) 295 | assert_equal(data.g0.ui2, copy.g0.ui2) 296 | end 297 | 298 | function test_union_group() 299 | local data = { 300 | u0 = { 301 | ui3 = 48, 302 | }, 303 | } 304 | 305 | assert_equal(136, hw_capnp.T1.calc_size(data)) 306 | local bin = hw_capnp.T1.serialize(data) 307 | copy = hw_capnp.T1.parse(bin, copy) 308 | assert_equal(0, copy.i0) 309 | assert_equal(0, copy.i1) 310 | assert_equal(0, copy.i2) 311 | assert_equal(false, copy.b0) 312 | assert_equal(false, copy.b1) 313 | assert_equal(0, copy.i3) 314 | assert_equal("enum1", copy.e0) 315 | assert_equal("none", copy.e1) 316 | assert_nil(copy.s0) 317 | assert_nil(copy.l0) 318 | assert_nil(copy.t0) 319 | assert_equal(0, copy.ui0) -- ui0 is set by default 320 | assert_nil(copy.ui1) 321 | assert_nil(copy.uv0) 322 | assert_equal(0, copy.g0.ui2) 323 | assert_equal(data.u0.ui3, copy.u0.ui3) 324 | assert_nil(copy.u0.uv1) 325 | assert_nil(copy.u0.ug0) 326 | assert_nil(copy.ls0) 327 | end 328 | 329 | function test_union_group1() 330 | local data = { 331 | u0 = { 332 | uv1 = "Void", 333 | }, 334 | } 335 | 336 | assert_equal(136, hw_capnp.T1.calc_size(data)) 337 | local bin = hw_capnp.T1.serialize(data) 338 | copy = hw_capnp.T1.parse(bin, copy) 339 | assert_equal(0, copy.i0) 340 | assert_equal(0, copy.i1) 341 | assert_equal(0, copy.i2) 342 | assert_equal(false, copy.b0) 343 | assert_equal(false, copy.b1) 344 | assert_equal(0, copy.i3) 345 | assert_equal("enum1", copy.e0) 346 | assert_equal("none", copy.e1) 347 | assert_nil(copy.s0) 348 | assert_nil(copy.l0) 349 | assert_nil(copy.t0) 350 | assert_equal(0, copy.ui0) -- ui0 is set by default 351 | assert_nil(copy.ui1) 352 | assert_nil(copy.uv0) 353 | assert_equal(0, copy.g0.ui2) 354 | assert_nil(copy.u0.ui3) 355 | assert_equal("Void", copy.u0.uv1) 356 | assert_nil(copy.u0.ug0) 357 | assert_nil(copy.ls0) 358 | end 359 | 360 | function test_union_group2() 361 | local data = { 362 | u0 = { 363 | ug0 = { 364 | ugv0 = "Void", 365 | }, 366 | }, 367 | } 368 | 369 | assert_equal(136, hw_capnp.T1.calc_size(data)) 370 | local bin = hw_capnp.T1.serialize(data) 371 | copy = hw_capnp.T1.parse(bin, copy) 372 | assert_equal(0, copy.i0) 373 | assert_equal(0, copy.i1) 374 | assert_equal(0, copy.i2) 375 | assert_equal(false, copy.b0) 376 | assert_equal(false, copy.b1) 377 | assert_equal(0, copy.i3) 378 | assert_equal("enum1", copy.e0) 379 | assert_equal("none", copy.e1) 380 | assert_nil(copy.s0) 381 | assert_nil(copy.l0) 382 | assert_nil(copy.t0) 383 | assert_equal(0, copy.ui0) -- ui0 is set by default 384 | assert_nil(copy.ui1) 385 | assert_nil(copy.uv0) 386 | assert_equal(0, copy.g0.ui2) 387 | assert_nil(copy.u0.ui3) 388 | assert_nil(copy.u0.uv1) 389 | assert_equal("Void", copy.u0.ug0.ugv0) 390 | assert_equal(0, copy.u0.ug0.ugu0) 391 | assert_nil(copy.ls0) 392 | end 393 | 394 | function test_struct_list() 395 | local data = { 396 | ls0 = { 397 | { 398 | f0 = 3.14, 399 | f1 = 3.141592653589, 400 | }, 401 | { 402 | f0 = 3.14, 403 | f1 = 3.141592653589, 404 | }, 405 | }, 406 | } 407 | 408 | assert_equal(136 + 8 + 24 * 2, hw_capnp.T1.calc_size(data)) 409 | local bin = hw_capnp.T1.serialize(data) 410 | copy = hw_capnp.T1.parse(bin, copy) 411 | assert_equal(0, copy.i0) 412 | assert_equal(0, copy.i1) 413 | assert_equal(0, copy.i2) 414 | assert_equal(false, copy.b0) 415 | assert_equal(false, copy.b1) 416 | assert_equal(0, copy.i3) 417 | assert_equal("enum1", copy.e0) 418 | assert_equal("none", copy.e1) 419 | assert_nil(copy.s0) 420 | assert_nil(copy.l0) 421 | assert_nil(copy.t0) 422 | assert_equal(0, copy.ui0) -- ui0 is set by default 423 | assert_nil(copy.ui1) 424 | assert_nil(copy.uv0) 425 | assert_equal(0, copy.g0.ui2) 426 | assert_equal(0, copy.u0.ui3) 427 | assert_nil(copy.u0.uv1) 428 | assert_nil(copy.u0.ug0) 429 | assert_not_nil(copy.ls0[1]) 430 | assert_equalf(data.ls0[1].f0, copy.ls0[1].f0) 431 | assert_equal(data.ls0[1].f1, copy.ls0[1].f1) 432 | assert_equalf(data.ls0[2].f0, copy.ls0[2].f0) 433 | assert_equal(data.ls0[2].f1, copy.ls0[2].f1) 434 | end 435 | 436 | function test_default_value() 437 | local data = { 438 | } 439 | 440 | assert_equal(136, hw_capnp.T1.calc_size(data)) 441 | local bin = hw_capnp.T1.serialize(data) 442 | copy = hw_capnp.T1.parse(bin, copy) 443 | assert_equal(0, copy.i0) 444 | assert_equal(0, copy.i1) 445 | assert_equal(0, copy.i2) 446 | assert_equal(false, copy.b0) 447 | assert_equal(false, copy.b1) 448 | assert_equal(0, copy.i3) 449 | assert_equal("enum1", copy.e0) 450 | assert_equal("none", copy.e1) 451 | assert_nil(copy.s0) 452 | assert_nil(copy.l0) 453 | assert_nil(copy.t0) 454 | assert_equal(0, copy.ui0) -- ui0 is set by default 455 | assert_nil(copy.ui1) 456 | assert_nil(copy.uv0) 457 | assert_equal(0, copy.g0.ui2) 458 | assert_equal(0, copy.u0.ui3) 459 | assert_nil(copy.u0.uv1) 460 | assert_nil(copy.u0.ug0) 461 | assert_nil(copy.ls0) 462 | assert_equal(65535, copy.du0) -- default = 65535 463 | end 464 | 465 | function test_default_value1() 466 | local data = { 467 | du0 = 630, 468 | } 469 | 470 | assert_equal(136, hw_capnp.T1.calc_size(data)) 471 | local bin = hw_capnp.T1.serialize(data) 472 | copy = hw_capnp.T1.parse(bin, copy) 473 | assert_equal(0, copy.i0) 474 | assert_equal(0, copy.i1) 475 | assert_equal(0, copy.i2) 476 | assert_equal(false, copy.b0) 477 | assert_equal(false, copy.b1) 478 | assert_equal(0, copy.i3) 479 | assert_equal("enum1", copy.e0) 480 | assert_equal("none", copy.e1) 481 | assert_nil(copy.s0) 482 | assert_nil(copy.l0) 483 | assert_nil(copy.t0) 484 | assert_equal(0, copy.ui0) -- ui0 is set by default 485 | assert_nil(copy.ui1) 486 | assert_nil(copy.uv0) 487 | assert_equal(0, copy.g0.ui2) 488 | assert_equal(0, copy.u0.ui3) 489 | assert_nil(copy.u0.uv1) 490 | assert_nil(copy.u0.ug0) 491 | assert_nil(copy.ls0) 492 | assert_equal(630, copy.du0) -- default = 65535 493 | end 494 | function test_default_value2() 495 | local data = { 496 | db0 = true 497 | } 498 | 499 | assert_equal(136, hw_capnp.T1.calc_size(data)) 500 | local bin = hw_capnp.T1.serialize(data) 501 | copy = hw_capnp.T1.parse(bin, copy) 502 | assert_equal(0, copy.i0) 503 | assert_equal(0, copy.i1) 504 | assert_equal(0, copy.i2) 505 | assert_equal(false, copy.b0) 506 | assert_equal(false, copy.b1) 507 | assert_equal(0, copy.i3) 508 | assert_equal("enum1", copy.e0) 509 | assert_equal("none", copy.e1) 510 | assert_nil(copy.s0) 511 | assert_nil(copy.l0) 512 | assert_nil(copy.t0) 513 | assert_equal(0, copy.ui0) -- ui0 is set by default 514 | assert_nil(copy.ui1) 515 | assert_nil(copy.uv0) 516 | assert_equal(0, copy.g0.ui2) 517 | assert_equal(0, copy.u0.ui3) 518 | assert_nil(copy.u0.uv1) 519 | assert_nil(copy.u0.ug0) 520 | assert_nil(copy.ls0) 521 | assert_equal(65535, copy.du0) -- default = 65535 522 | assert_equal(true, copy.db0) -- default = true 523 | end 524 | function test_reserved_word() 525 | local data = { 526 | ["end"] = true 527 | } 528 | 529 | assert_equal(136, hw_capnp.T1.calc_size(data)) 530 | local bin = hw_capnp.T1.serialize(data) 531 | copy = hw_capnp.T1.parse(bin, copy) 532 | assert_equal(0, copy.i0) 533 | assert_equal(0, copy.i1) 534 | assert_equal(0, copy.i2) 535 | assert_equal(false, copy.b0) 536 | assert_equal(false, copy.b1) 537 | assert_equal(0, copy.i3) 538 | assert_equal("enum1", copy.e0) 539 | assert_equal("none", copy.e1) 540 | assert_nil(copy.s0) 541 | assert_nil(copy.l0) 542 | assert_nil(copy.t0) 543 | assert_equal(0, copy.ui0) -- ui0 is set by default 544 | assert_nil(copy.ui1) 545 | assert_nil(copy.uv0) 546 | assert_equal(0, copy.g0.ui2) 547 | assert_equal(0, copy.u0.ui3) 548 | assert_nil(copy.u0.uv1) 549 | assert_nil(copy.u0.ug0) 550 | assert_nil(copy.ls0) 551 | assert_equal(65535, copy.du0) -- default = 65535 552 | assert_equal(true, copy.db0) -- default = true 553 | assert_equal(true, copy["end"]) 554 | end 555 | 556 | function test_list_of_text() 557 | local data = { 558 | lt0 = { 559 | "Bach", "Mozart", "Beethoven", "Tchaikovsky", 560 | } 561 | } 562 | 563 | assert_equal(136 + 4 * 8 + 8 + 8 + 16 + 16, hw_capnp.T1.calc_size(data)) 564 | local bin = hw_capnp.T1.serialize(data) 565 | copy = hw_capnp.T1.parse(bin, copy) 566 | assert_equal(0, copy.i0) 567 | assert_equal(0, copy.i1) 568 | assert_equal(0, copy.i2) 569 | assert_equal(false, copy.b0) 570 | assert_equal(false, copy.b1) 571 | assert_equal(0, copy.i3) 572 | assert_equal("enum1", copy.e0) 573 | assert_equal("none", copy.e1) 574 | assert_nil(copy.s0) 575 | assert_nil(copy.l0) 576 | assert_nil(copy.t0) 577 | assert_equal(0, copy.ui0) -- ui0 is set by default 578 | assert_nil(copy.ui1) 579 | assert_nil(copy.uv0) 580 | assert_equal(0, copy.g0.ui2) 581 | assert_equal(0, copy.u0.ui3) 582 | assert_nil(copy.u0.uv1) 583 | assert_nil(copy.u0.ug0) 584 | assert_nil(copy.ls0) 585 | assert_equal(65535, copy.du0) -- default = 65535 586 | assert_equal(true, copy.db0) -- default = true 587 | assert_equal(false, copy["end"]) 588 | assert_not_nil(copy.lt0) 589 | assert_equal(4, #copy.lt0) 590 | assert_not_nil(copy.lt0[1]) 591 | assert_not_nil(copy.lt0[2]) 592 | assert_not_nil(copy.lt0[3]) 593 | assert_not_nil(copy.lt0[4]) 594 | assert_equal(data.lt0[1], copy.lt0[1]) 595 | assert_equal(data.lt0[2], copy.lt0[2]) 596 | assert_equal(data.lt0[3], copy.lt0[3]) 597 | assert_equal(data.lt0[4], copy.lt0[4]) 598 | end 599 | 600 | function test_list_of_data() 601 | local data = { 602 | ld0 = { 603 | "B\x0A\x0Ch", "M\x00z\x0Art", "B\xEEthoven", "Tch\x0Aik\x00vsky", 604 | } 605 | } 606 | 607 | assert_equal(128 + 4 * 8 + 8 + 8 + 16 + 16, hw_capnp.T1.calc_size(data)) 608 | local bin = hw_capnp.T1.serialize(data) 609 | copy = hw_capnp.T1.parse(bin, copy) 610 | assert_not_nil(copy.ld0) 611 | assert_equal(4, #copy.ld0) 612 | assert_not_nil(copy.ld0[1]) 613 | assert_not_nil(copy.ld0[2]) 614 | assert_not_nil(copy.ld0[3]) 615 | assert_not_nil(copy.ld0[4]) 616 | assert_equal(data.ld0[1], copy.ld0[1]) 617 | assert_equal(data.ld0[2], copy.ld0[2]) 618 | assert_equal(data.ld0[3], copy.ld0[3]) 619 | assert_equal(data.ld0[4], copy.ld0[4]) 620 | end 621 | 622 | 623 | function test_const() 624 | assert_equal(3.14159, hw_capnp.pi) 625 | assert_equal("Hello", hw_capnp.T1.welcomeText) 626 | end 627 | 628 | function test_enum_literal() 629 | assert_equal(0, hw_capnp.T1.EnumType1["enum1"]) 630 | assert_equal("enum1", hw_capnp.T1.EnumType1Str[0]) 631 | 632 | assert_equal(3, hw_capnp.T1.EnumType1["wEirdENum4"]) 633 | assert_equal("wEirdENum4", hw_capnp.T1.EnumType1Str[3]) 634 | 635 | assert_equal(4, hw_capnp.T1.EnumType1["UPPER-DASH"]) 636 | assert_equal("UPPER-DASH", hw_capnp.T1.EnumType1Str[4]) 637 | end 638 | 639 | function test_imported_constant() 640 | assert_equal(1, hw_capnp.S1.flag1) 641 | assert_equal(2, hw_capnp.S1.flag2) 642 | assert_equal("Hello", hw_capnp.S1.flag3) 643 | end 644 | 645 | function test_uint64() 646 | local uint64p = ffi.new("uint64_t[?]", 1) 647 | local uint32p = ffi.cast("uint32_t *", uint64p) 648 | uint32p[0] = 1 649 | uint32p[1] = 2 650 | 651 | local data = { 652 | u64 = uint64p[0], 653 | } 654 | 655 | assert_equal(136, hw_capnp.T1.calc_size(data)) 656 | local bin = hw_capnp.T1.serialize(data) 657 | copy = hw_capnp.T1.parse(bin, copy) 658 | assert_equal(0, copy.i0) 659 | assert_equal(0, copy.i1) 660 | assert_equal(0, copy.i2) 661 | assert_equal(false, copy.b0) 662 | assert_equal(false, copy.b1) 663 | assert_equal(0, copy.i3) 664 | assert_equal("enum1", copy.e0) 665 | assert_equal("none", copy.e1) 666 | assert_nil(copy.s0) 667 | assert_nil(copy.l0) 668 | assert_nil(copy.t0) 669 | 670 | assert_equal("cdata", type(copy.u64)) 671 | assert_equal("8589934593ULL", tostring(copy.u64)) 672 | 673 | end 674 | 675 | function test_lower_space_naming() 676 | local data = { 677 | e1 = "lower space" 678 | } 679 | 680 | assert_equal(136, hw_capnp.T1.calc_size(data)) 681 | local bin = hw_capnp.T1.serialize(data) 682 | copy = hw_capnp.T1.parse(bin, copy) 683 | assert_equal("lower space", copy.e1) 684 | end 685 | 686 | function test_type_check_when_calc_size() 687 | -- data type should be checked when calculating size 688 | local data = { 689 | s0 = "I should be a lua table, not a string", 690 | } 691 | 692 | assert_equal(136, hw_capnp.T1.calc_size(data)) 693 | local bin = hw_capnp.T1.serialize(data) 694 | copy = hw_capnp.T1.parse(bin, copy) 695 | assert_equal(0, copy.i0) 696 | assert_equal(0, copy.i1) 697 | assert_equal(0, copy.i2) 698 | assert_equal(false, copy.b0) 699 | assert_equal(false, copy.b1) 700 | assert_equal(0, copy.i3) 701 | assert_equal("enum1", copy.e0) 702 | assert_equal("none", copy.e1) 703 | assert_nil(copy.s0) 704 | assert_nil(copy.l0) 705 | assert_nil(copy.t0) 706 | end 707 | 708 | function test_get_enum_from_number() 709 | local data = { 710 | e1 = 7, -- "lower space" 711 | } 712 | 713 | assert_equal(136, hw_capnp.T1.calc_size(data)) 714 | local bin = hw_capnp.T1.serialize(data) 715 | copy = hw_capnp.T1.parse(bin, copy) 716 | assert_equal("lower space", copy.e1) 717 | end 718 | 719 | function test_unknown_enum_value() 720 | local data = { 721 | e1 = "I AM AN UNKNOWN ENUM", 722 | } 723 | 724 | assert_equal(136, hw_capnp.T1.calc_size(data)) 725 | local bin = hw_capnp.T1.serialize(data) 726 | copy = hw_capnp.T1.parse(bin, copy) 727 | assert_equal("none", copy.e1) 728 | end 729 | 730 | function test_empty_enum_value() 731 | local data = { 732 | e1 = "", 733 | } 734 | 735 | assert_equal(136, hw_capnp.T1.calc_size(data)) 736 | local bin = hw_capnp.T1.serialize(data) 737 | copy = hw_capnp.T1.parse(bin, copy) 738 | assert_equal("none", copy.e1) 739 | end 740 | 741 | function test_list_uint16_size() 742 | local data = { ls_u16 = {1, 2, 3, 4, 5, 6, 7, 8, 9} } 743 | -- header (and root struct pointer) + 6 list pointers + round8(2 * 9) 744 | assert_equal(16 + 8 * 6 + 24, hw_capnp.T3.calc_size(data)) 745 | 746 | local data = { ls_u32 = {1, 2, 3, 4, 5, 6, 7, 8, 9} } 747 | -- header (and root struct pointer) + 6 list pointers + round8(4 * 9) 748 | assert_equal(16 + 8 * 6 + 40, hw_capnp.T3.calc_size(data)) 749 | 750 | local data = { ls_u64 = {1, 2, 3, 4, 5, 6, 7, 8, 9} } 751 | -- header (and root struct pointer) + 6 list pointers + round8(8 * 9) 752 | assert_equal(16 + 8 * 6 + 72, hw_capnp.T3.calc_size(data)) 753 | 754 | local data = { ls_i16 = {1, 2, 3, 4, 5, 6, 7, 8, 9} } 755 | -- header (and root struct pointer) + 6 list pointers + round8(2 * 9) 756 | assert_equal(16 + 8 * 6 + 24, hw_capnp.T3.calc_size(data)) 757 | 758 | local data = { ls_i32 = {1, 2, 3, 4, 5, 6, 7, 8, 9} } 759 | -- header (and root struct pointer) + 6 list pointers + round8(4 * 9) 760 | assert_equal(16 + 8 * 6 + 40, hw_capnp.T3.calc_size(data)) 761 | 762 | local data = { ls_i64 = {1, 2, 3, 4, 5, 6, 7, 8, 9} } 763 | -- header (and root struct pointer) + 6 list pointers + round8(8 * 9) 764 | assert_equal(16 + 8 * 6 + 72, hw_capnp.T3.calc_size(data)) 765 | end 766 | 767 | function test_serialize_cdata() 768 | local data = { 769 | i0 = 32, 770 | } 771 | 772 | assert_equal(136, hw_capnp.T1.calc_size(data)) 773 | local bin = hw_capnp.T1.serialize(data) 774 | local arr, len = hw_capnp.T1.serialize_cdata(data) 775 | assert_equal(#bin, len) 776 | assert_equal(bin, ffi.string(arr, len)) 777 | end 778 | 779 | function test_list_of_list_of_text() 780 | local data = { 781 | llt0 = { 782 | {"a"}, 783 | {"b", "c"}, 784 | } 785 | } 786 | 787 | assert_equal(16 + 2 * 8 + 2 * 8 + 3 * 16, hw_capnp.T5.calc_size(data)) 788 | local bin = hw_capnp.T5.serialize(data) 789 | copy = hw_capnp.T5.parse(bin, copy) 790 | assert_not_nil(copy.llt0) 791 | assert_equal(2, #copy.llt0) 792 | assert_not_nil(copy.llt0[1]) 793 | assert_not_nil(copy.llt0[2]) 794 | 795 | assert_equal(1, #copy.llt0[1]) 796 | assert_not_nil(copy.llt0[1][1]) 797 | assert_equal(data.llt0[1][1], copy.llt0[1][1]) 798 | 799 | assert_equal(2, #copy.llt0[2]) 800 | assert_not_nil(copy.llt0[2][1]) 801 | assert_not_nil(copy.llt0[2][2]) 802 | assert_equal(data.llt0[2][1], copy.llt0[2][1]) 803 | assert_equal(data.llt0[2][2], copy.llt0[2][2]) 804 | end 805 | 806 | function test_list_of_list_of_list_text() 807 | local data = { 808 | lllt0 = { 809 | { 810 | {"a"}, 811 | }, 812 | { 813 | {"b"}, 814 | {"c", "d"}, 815 | }, 816 | } 817 | } 818 | 819 | assert_equal(16 + 2 * 8 + 2 * 8 + 3 * 8 + 4 * 16, hw_capnp.T5.calc_size(data)) 820 | local bin = hw_capnp.T5.serialize(data) 821 | copy = hw_capnp.T5.parse(bin, copy) 822 | assert_not_nil(copy.lllt0) 823 | assert_equal(2, #copy.lllt0) 824 | assert_not_nil(copy.lllt0[1]) 825 | 826 | assert_equal(1, #copy.lllt0[1]) 827 | assert_not_nil(copy.lllt0[1][1]) 828 | 829 | assert_equal(1, #copy.lllt0[1][1]) 830 | assert_not_nil(copy.lllt0[1][1][1]) 831 | assert_equal(data.lllt0[1][1][1], copy.lllt0[1][1][1]) 832 | 833 | assert_equal(2, #copy.lllt0[2]) 834 | assert_not_nil(copy.lllt0[2][1]) 835 | 836 | assert_equal(1, #copy.lllt0[2][1]) 837 | assert_not_nil(copy.lllt0[2][1][1]) 838 | assert_equal(data.lllt0[2][1][1], copy.lllt0[2][1][1]) 839 | 840 | assert_equal(2, #copy.lllt0[2][2]) 841 | assert_not_nil(copy.lllt0[2][2][1]) 842 | assert_equal(data.lllt0[2][2][1], copy.lllt0[2][2][1]) 843 | assert_equal(data.lllt0[2][2][2], copy.lllt0[2][2][2]) 844 | end 845 | 846 | return _G 847 | -------------------------------------------------------------------------------- /tests/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for file in $(ls tests/*.lua) 4 | do 5 | echo 6 | echo "Running $file..." 7 | #valgrind --tool=memcheck --vgdb=yes --vgdb-error=0 /opt/luajit-dbg/bin/luajit-2.1.0-alpha $file || exit 1 8 | luajit $file || exit 1 9 | done 10 | --------------------------------------------------------------------------------