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