Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add namespace #88

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.so
*.o
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,39 @@ I have been using Google protocol buffers for many years in many projects, and I

In lua, enum types are not very useful. You can use integer to define an enum table in lua.

Namespace
========

`Namespace` is optional. If you declare namespace in one file, everything defined in it would be suffixed with namespace in format `name@namespace`.

To declare namespace, just put the declaration in the first line, like

```
namespace foobar

.foo {
what 0 : integer
}
```

To refer a name in another namespace, use format `name@namespace`, like

```
namespace deadbeaf

.pork {
weight 0 : integer
}

.beaf {
moo 0 : foo@foobar
p 1 : pork
}

```

Note: check `testns.lua` to see full example.

Wire protocol
========

Expand Down
88 changes: 59 additions & 29 deletions sprotoparser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ local alpha = R"az" + R"AZ" + "_"
local alnum = alpha + R"09"
local word = alpha * alnum ^ 0
local name = C(word)
local typename = C(word * ("." * word) ^ 0)
local typename = C( word * (("." * word) ^ 0) * ("@" * word)^-1)
local tag = R"09" ^ 1 / tonumber
local mainkey = "(" * blank0 * name * blank0 * ")"
local decimal = "(" * blank0 * C(tag) * blank0 * ")"
Expand All @@ -92,9 +92,26 @@ local typedef = P {

local proto = blank0 * typedef * blank0

local buildin_types = {
integer = 0,
boolean = 1,
string = 2,
binary = 2, -- binary is a sub type of string
}

local function nsname(ns, name)
if buildin_types[name] then
return name
end
if not ns or name:find("@") then
return name
end
return name .. "@" .. ns
end

local convert = {}

function convert.protocol(all, obj)
function convert.protocol(all, obj, ns)
local result = { tag = obj[2] }
for _, p in ipairs(obj[3]) do
local pt = p[1]
Expand All @@ -104,21 +121,21 @@ function convert.protocol(all, obj)
local typename = p[2]
if type(typename) == "table" then
local struct = typename
typename = obj[1] .. "." .. p[1]
all.type[typename] = convert.type(all, { typename, struct })
typename = nsname(ns, obj[1] .. "." .. p[1])
all.type[typename] = convert.type(all, { typename, struct },ns)
end
if typename == "nil" then
if p[1] == "response" then
result.confirm = true
end
else
result[p[1]] = typename
result[p[1]] = nsname(ns, typename)
end
end
return result
end

function convert.type(all, obj)
function convert.type(all, obj, ns)
local result = {}
local typename = obj[1]
local tags = {}
Expand Down Expand Up @@ -151,48 +168,44 @@ function convert.type(all, obj)
field.key = mainkey
end
end
field.typename = fieldtype
field.typename = nsname(ns,fieldtype)
else
assert(f.type == "type") -- nest type
local nesttypename = typename .. "." .. f[1]
local nesttypename = nsname(ns, typename .. "." .. f[1])
f[1] = nesttypename
assert(all.type[nesttypename] == nil, "redefined " .. nesttypename)
all.type[nesttypename] = convert.type(all, f)
all.type[nesttypename] = convert.type(all, f, ns)
end
end
table.sort(result, function(a,b) return a.tag < b.tag end)
return result
end

local function adjust(r)
local result = { type = {} , protocol = {} }

local function adjust(r, ns, result)
for _, obj in ipairs(r) do
local set = result[obj.type]
local name = obj[1]
local name = nsname(ns, obj[1])
assert(set[name] == nil , "redefined " .. name)
set[name] = convert[obj.type](result,obj)
set[name] = convert[obj.type](result,obj,ns)
end

return result
end

local buildin_types = {
integer = 0,
boolean = 1,
string = 2,
binary = 2, -- binary is a sub type of string
}

local function checktype(types, ptype, t)
if buildin_types[t] then
return t
end
local fullname = ptype .. "." .. t
local ns = ptype:match("@(.*)")
local fullname
if ns then
fullname = ptype:match("(.*)@") .. "." .. t:match("(.*)@") .. "@" .. ns
else
fullname = ptype .. "." .. t
end
if types[fullname] then
return fullname
else
ptype = ptype:match "(.+)%..+$"
ptype = ptype:match "(.+)%..+$"
if ptype then
return checktype(types, ptype, t)
elseif types[t] then
Expand Down Expand Up @@ -242,10 +255,10 @@ local function flattypename(r)
return r
end

local function parser(text,filename)
local function parser(text,filename,ns,result)
local state = { file = filename, pos = 0, line = 1 }
local r = lpeg.match(proto * -1 + exception , text , 1, state )
return flattypename(check_protocol(adjust(r)))
adjust(r,ns,result)
end

--[[
Expand Down Expand Up @@ -483,9 +496,26 @@ function sparser.dump(str)
end

function sparser.parse(text, name)
local r = parser(text, name or "=text")
local data = encodeall(r)
return data
local result = { type = {} , protocol = {} }
if type(text) == "string" then
parser(text, name or "=text", nil, result)
else
local t = {}
for name,txt in pairs(text) do
local _,length,namespace = txt:find("^namespace[ \t]+([%w_]+)\n")
if length then
if t[namespace] then
error(string.format("duplicate defined namespace [%s] in file: %s",namespace,name))
end
t[namespace] = true
txt = txt:sub(length+1) -- rm namespace declaration
end
parser(txt,name,namespace,result)
end
end

local r = flattypename(check_protocol(result))
return encodeall(r)
end

return sparser
100 changes: 100 additions & 0 deletions testns.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
local sproto = require "sproto"
local print_r = require "print_r"
local core = require "sproto.core"

local schemas = {
["addressBook"] = [[
namespace addressbook

.AddressBook {
person 0 : *Person(id)
others 1 : *Person(id)
}

.Person {
name 0 : string
id 1 : integer
email 2 : string

.PhoneNumber {
number 0 : string
type 1 : integer
}

phone 3 : *PhoneNumber
}
]],

["telephone"] = [[
namespace telephone

.Phone {
is_mobile 0 : boolean
number 1 : Person.PhoneNumber@addressbook
}

call 1 {
request {
who 0 : Person@addressbook
what 1 : Phone
}
response {
ok 0 : boolean
}
}
]],
}

local sp = sproto.parse(schemas)
-- core.dumpproto only for debug use
core.dumpproto(sp.__cobj)

local def = sp:default "Person@addressbook"
print("default table for Person")
print_r(def)
print("--------------")

local person = {
[10000] = {
name = "Alice",
id = 10000,
phone = {
{ number = "123456789" , type = 1 },
{ number = "87654321" , type = 2 },
}
},
[20000] = {
name = "Bob",
id = 20000,
phone = {
{ number = "01234567890" , type = 3 },
}
}
}

local ab = {
person = setmetatable({}, { __index = person, __pairs = function() return next, person, nil end }),
others = {
{
name = "Carol",
id = 30000,
phone = {
{ number = "9876543210" },
}
},
}
}

collectgarbage "stop"

local code = sp:encode("AddressBook@addressbook", ab)
local addr = sp:decode("AddressBook@addressbook", code)
print_r(addr)

print("#### test rpc request")
local req = sp:request_encode("call@telephone", {who = {name="deadbeaf",id=3},what={number={number="1234545"}, is_mobile=true}})
print_r(sp:request_decode("call@telephone",req))

print("#### test rpc response")
local resp =sp:response_encode("call@telephone",{ok=true})
print_r(sp:response_decode("call@telephone",resp))