--  -----------------------
--  -- simple proto parser
--  -----------------------

--  local lpeg = require "lpeg"
--  local P = lpeg.P
--  local S = lpeg.S
--  local R = lpeg.R
--  local C = lpeg.C
--  local Ct = lpeg.Ct
--  local Cg = lpeg.Cg
--  local Cc = lpeg.Cc
--  local V = lpeg.V

--  local next = next
--  local error = error
--  local tonumber = tonumber
--  local pairs = pairs
--  local ipairs = ipairs
--  local rawset = rawset
--  local tinsert = table.insert
--  local smatch = string.match
--  local sbyte = string.byte

--  local internal_type = {
--  	double = "TYPE_DOUBLE",
--  	float = "TYPE_FLOAT",
--  	uint64 = "TYPE_UINT64",
--  	int = "TYPE_INT32",
--  	int32 = "TYPE_INT32",
--  	int64 = "TYPE_INT64",
--  	fixed64 = "TYPE_FIXED64",
--  	fixed32 = "TYPE_FIXED32",
--  	bool = "TYPE_BOOL",
--  	string = "TYPE_STRING",
--  	bytes = "TYPE_BYTES",
--  	uint32 = "TYPE_UINT32",
--  	sfixed32 = "TYPE_SFIXED32",
--  	sfixed64 = "TYPE_SFIXED64",
--  	sint32 = "TYPE_SINT32",
--  	sint64 = "TYPE_SINT64",
--  }

--  local function count_lines(_,pos, parser_state)
--  	if parser_state.pos < pos then
--  		parser_state.line = parser_state.line + 1
--  		parser_state.pos = pos
--  	end
--  	return pos
--  end

--  local exception = lpeg.Cmt( lpeg.Carg(1) , function ( _ , pos, parser_state)
--  	error( "syntax error at [" .. (parser_state.file or "") .."] (" .. parser_state.line ..")" )
--  	return pos
--  end)

--  local eof = P(-1)
--  local newline = lpeg.Cmt((P"\n" + "\r\n") * lpeg.Carg(1) ,count_lines)
--  local line_comment = "//" * (1 - newline) ^0 * (newline + eof)
--  local blank = S" \t" + newline + line_comment
--  local blank0 = blank ^ 0
--  local blanks = blank ^ 1
--  local alpha = R"az" + R"AZ" + "_"
--  local alnum = alpha + R"09"
--  local str_c = (1 - S("\\\"")) + P("\\") * 1
--  local str = P"\"" * C(str_c^0) * "\""
--  local dotname = ("." * alpha * alnum ^ 0) ^ 0
--  local typename = C(alpha * alnum ^ 0 * dotname)
--  local name = C(alpha * alnum ^ 0)
--  local filename = P"\"" * C((alnum + "/" + "." + "-")^1) * "\""
--  local id = R"09" ^ 1 / tonumber + "max" * Cc(-1)
--  local bool = "true" * Cc(true) + "false" * Cc(false)
--  local value = str + bool + name + id
--  local patterns = {}

--  local enum_item = Cg(name * blank0 * "=" * blank0 * id * blank0 * ";" * blank0)

--  local function insert(tbl, k,v)
--  	tinsert(tbl, { name = k , number = v })
--  	return tbl
--  end

--  patterns.ENUM = Ct(Cg("enum","type") * blanks * Cg(typename,"name") * blank0 *
--  	"{" * blank0 *
--  		Cg(lpeg.Cf(Ct"" * enum_item^1 , insert),"value")
--  	* "}" * blank0)

--  local prefix_field = P"required" * Cc"LABEL_REQUIRED" +
--  	P"optional" * Cc"LABEL_OPTIONAL" +
--  	P"repeated" * Cc"LABEL_REPEATED"
--  local postfix_pair = blank0 * Cg(name * blank0 * "=" * blank0 * value * blank0)
--  local postfix_pair_2 = blank0 * "," * postfix_pair
--  local postfix_field = "[" * postfix_pair * postfix_pair_2^0 * blank0 * "]"
--  local options = lpeg.Cf(Ct"" * postfix_field , rawset) ^ -1

--  local function setoption(t, options)
--  	if next(options) then
--  		t.options = options
--  	end
--  	return t
--  end

--  local message_field = lpeg.Cf (
--  	Ct(	Cg(prefix_field,"label") * blanks *
--  		Cg(typename,"type_name") * blanks *
--  		Cg(name,"name") * blank0 * "=" * blank0 *
--  		Cg(id,"number")
--  		) * blank0 * options ,
--  		setoption) * blank0 * ";" * blank0

--  local extensions = Ct(
--  	Cg("extensions" , "type") * blanks *
--  	Cg(id,"start") * blanks * "to" * blanks *
--  	Cg(id,"end") * blank0 * ";" * blank0
--  	)

--  patterns.EXTEND = Ct(
--  	Cg("extend", "type") * blanks *
--  	Cg(typename, "name") * blank0 * "{" * blank0 *
--  	Cg(Ct((message_field) ^ 1),"extension") * "}" * blank0
--  	)

--  patterns.MESSAGE = P { Ct(
--  	Cg("message","type") * blanks *
--  	Cg(typename,"name") * blank0 * "{" * blank0 *
--  	Cg(Ct((message_field + patterns.ENUM + extensions + patterns.EXTEND + V(1)) ^ 0),"items") * "}" * blank0
--  	) }

--  patterns.OPTION = Ct(
--  	Cg("option" , "type") * blanks *
--  	Cg(name, "name") * blank0 * "=" * blank0 *
--  	Cg(value, "value")
--  	) * blank0 * ";" * blank0

--  patterns.IMPORT = Ct( Cg("import" , "type") * blanks * Cg(filename, "name") ) * blank0 * ";" * blank0

--  patterns.PACKAGE = Ct( Cg("package", "type") * blanks * Cg(typename, "name") ) * blank0 * ";" * blank0

--  local proto_tbl = { "PROTO" }

--  do
--  	local k, v = next(patterns)
--  	local p = V(k)
--  	proto_tbl[k] = v
--  	for k,v in next , patterns , k do
--  		proto_tbl[k] = v
--  		p = p + V(k)
--  	end
--  	proto_tbl.PROTO = Ct(blank0 * p ^ 1)
--  end

--  local proto = P(proto_tbl)

--  local deal = {}

--  function deal:import(v)
--  	self.dependency = self.dependency or {}
--  	tinsert(self.dependency , v.name)
--  end

--  function deal:package(v)
--  	self.package = v.name
--  end

--  function deal:enum(v)
--  	self.enum_type = self.enum_type or {}
--  	tinsert(self.enum_type , v)
--  end

--  function deal:option(v)
--  	self.options = self.options or {}
--  	self.options[v.name] = v.value
--  end

--  function deal:extend(v)
--  	self.extension = self.extension or {}
--  	local extendee = v.name
--  	for _,v in ipairs(v.extension) do
--  		v.extendee = extendee
--  		v.type = internal_type[v.type_name]
--  		if v.type then
--  			v.type_name = nil
--  		end
--  		tinsert(self.extension , v)
--  	end
--  end

--  function deal:extensions(v)
--  	self.extension_range = self.extension_range or {}
--  	tinsert(self.extension_range, v)
--  end

--  local function _add_nested_message(self, item)
--  	if item.type == nil then
--  		item.type = internal_type[item.type_name]
--  		if item.type then
--  			item.type_name = nil
--  		end
--  		self.field = self.field or {}
--  		tinsert(self.field, item)
--  	else
--  		local f = deal[item.type]
--  		item.type = nil
--  		f(self , item)
--  	end
--  end

--  function deal:message(v)
--  	self.nested_type = self.nested_type or {}
--  	local m = { name = v.name }
--  	tinsert(self.nested_type , m)
--  	for _,v in ipairs(v.items) do
--  		_add_nested_message(m, v)
--  	end
--  end

--  local function fix(r)
--  	local p = {}
--  	for _,v in ipairs(r) do
--  		local f = deal[v.type]
--  		v.type = nil
--  		f(p , v)
--  	end

--  	p.message_type = p.nested_type
--  	p.nested_type = nil

--  	return p
--  end

--  --- fix message name

--  local NULL = {}

--  local function _match_name(namespace , n , all)
--  	if sbyte(n) == 46 then
--  		return n
--  	end

--  	repeat
--  		local name = namespace .. "." .. n
--  		if all[name] then
--  			return name
--  		end
--  		namespace = smatch(namespace,"(.*)%.[%w_]+$")
--  	until namespace == nil
--  end

--  local function _fix_field(namespace , field, all)
--  	local type_name = field.type_name
--  	if type_name == "" then
--  		field.type_name = nil
--  		return
--  	elseif type_name == nil then
--  		return
--  	end

--  	local full_name = assert(_match_name(namespace, field.type_name, all) , field.type_name , all)

--  	field.type_name = full_name
--  	field.type = all[full_name]

--  	local options = field.options
--  	if options then
--  		if options.default then
--  			field.default_value = tostring(options.default)
--  			options.default = nil
--  		end
--  		if next(options) == nil then
--  			field.options = nil
--  		end
--  	end
--  end

--  local function _fix_extension(namespace, ext, all)
--  	for _,field in ipairs(ext or NULL) do
--  		field.extendee = assert(_match_name(namespace, field.extendee,all),field.extendee)
--  		_fix_field(namespace , field , all)
--  	end
--  end

--  local function _fix_message(msg , all)
--  	for _,field in ipairs(msg.field or NULL) do
--  		_fix_field(assert(all[msg],msg.name) , field , all)
--  	end
--  	for _,nest in ipairs(msg.nested_type or NULL) do
--  		_fix_message(nest , all)
--  	end
--  	_fix_extension(all[msg] , msg.extension , all)
--  end

--  local function _fix_typename(file , all)
--  	for _,message in ipairs(file.message_type or NULL) do
--  		_fix_message(message , all)
--  	end
--  	_fix_extension(file.package , file.extension , all)
--  end

--  --- merge messages

--  local function _enum_fullname(prefix, enum , all)
--  	local fullname
--  	if sbyte(enum.name) == 46 then
--  		fullname = enum.name
--  	else
--  		fullname = prefix .. "." .. enum.name
--  	end
--  	all[fullname] = "TYPE_ENUM"
--  	all[enum] = fullname
--  end

--  local function _message_fullname(prefix , msg , all)
--  	local fullname
--  	if sbyte(msg.name) == 46 then
--  		fullname = msg.name
--  	else
--  		fullname = prefix .. "." .. msg.name
--  	end
--  	all[fullname] = "TYPE_MESSAGE"
--  	all[msg] = fullname
--  	for _,nest in ipairs(msg.nested_type or NULL) do
--  		_message_fullname(fullname , nest , all)
--  	end
--  	for _,enum in ipairs(msg.enum_type or NULL) do
--  		_enum_fullname(fullname , enum , all)
--  	end
--  end

--  local function _gen_fullname(file , all)
--  	local prefix = ""
--  	if file.package then
--  		prefix = "." .. file.package
--  	end
--  	for _,message in ipairs(file.message_type or NULL) do
--  		_message_fullname(prefix , message , all)
--  	end
--  	for _,enum in ipairs(file.enum_type or NULL) do
--  		_enum_fullname(prefix , enum , all)
--  	end
--  end

--  --- parser

--  local parser = {}

--  local function parser_one(text,filename)
--  	local state = { file = filename, pos = 0, line = 1 }
--  	local r = lpeg.match(proto * -1 + exception , text , 1, state )
--  	local t = fix(r)
--  	return t
--  end

--  function parser.parser(text,filename)
--  	local t = parser_one(text,filename)
--  	local all = {}
--  	_gen_fullname(t,all)
--  	_fix_typename(t , all)
--  	return t
--  end

--  local pb = require "protobuf"

--  function parser.register(fileset , path)
--  	local all = {}
--  	local files = {}
--  	if type(fileset) == "string" then
--  		fileset = { fileset }
--  	end
--  	for _, filename in ipairs(fileset) do
--  		local fullname
--  		if path then
--  			fullname = path .. "/" .. filename
--  		else
--  			fullname = filename
--  		end
--  		local f = assert(io.open(fullname , "r"))
--  		local buffer = f:read "*a"
--  		f:close()
--  		local t = parser_one(buffer,filename)
--  		_gen_fullname(t,all)
--  		t.name = filename
--  		tinsert(files , t)
--  	end
--  	for _,file in ipairs(files) do
--  		_fix_typename(file,all)
--  	end

--  	local pbencode = pb.encode("google.protobuf.FileDescriptorSet" , { file = files })

--  	if pbencode == nil then
--  		error(pb.lasterror())
--  	end
--  	pb.register(pbencode)
--  	return files
--  end

--  return parser