From ead7cd840d10e401c2b989e2547f85fab403d822 Mon Sep 17 00:00:00 2001 From: hqwrong Date: Fri, 8 Nov 2019 22:03:34 +0800 Subject: [PATCH] add namespace --- .gitignore | 2 + README.md | 33 ++++++++++++++++ sprotoparser.lua | 88 +++++++++++++++++++++++++++-------------- testns.lua | 100 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 29 deletions(-) create mode 100644 .gitignore create mode 100644 testns.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc78368 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.so +*.o diff --git a/README.md b/README.md index 237e4b5..6c24d3e 100644 --- a/README.md +++ b/README.md @@ -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 ======== diff --git a/sprotoparser.lua b/sprotoparser.lua index a6c7359..38c573a 100644 --- a/sprotoparser.lua +++ b/sprotoparser.lua @@ -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 * ")" @@ -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] @@ -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 = {} @@ -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 @@ -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 --[[ @@ -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 diff --git a/testns.lua b/testns.lua new file mode 100644 index 0000000..2c4e0ce --- /dev/null +++ b/testns.lua @@ -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))