Files
OpenRA/tools/ffitoapi.lua
2014-09-23 20:28:49 -07:00

314 lines
8.2 KiB
Lua

-- author: Christoph Kubisch
---------------------------------------------------------
local StripCommentsC = StripCommentsC
if not StripCommentsC then
StripCommentsC = function(tx)
local out = ""
local lastc = ""
local skip
local skipline
local skipmulti
local tx = string.gsub(tx, "\r\n", "\n")
for c in tx:gmatch(".") do
local oc = c
local tu = lastc..c
skip = c == '/'
if ( not (skipmulti or skipline)) then
if (tu == "//") then
skipline = true
elseif (tu == "/*") then
skipmulti = true
c = ""
elseif (lastc == '/') then
oc = tu
end
elseif (skipmulti and tu == "*/") then
skipmulti = false
c = ""
elseif (skipline and lastc == "\n") then
out = out.."\n"
skipline = false
end
lastc = c
if (not (skip or skipline or skipmulti)) then
out = out..oc
end
end
return out..lastc
end
end
local function ffiToApi(ffidef)
local str = ffidef
str = ffidef:match("(%-%-%[%[.+%]%])")
local header = ffidef:match("[^\r\n]+")
ffidef = StripCommentsC(ffidef)
local description = header:match("|%s*(.*)")
local descrprefixes = header:match("(.-)%s*|")
if not descrprefixes then return end
local prefixes = {}
for prefix in descrprefixes:gmatch("([_%w]+)") do
table.insert(prefixes,prefix)
end
local ns = prefixes[1]
if not ns then return end
local lktypes = {
["string"] = "string",
}
local function gencontent(tx)
local enums = {}
local funcs = {}
local values = {}
local classes = {}
-- extract function names
local curfunc
local function registerfunc()
local fn = curfunc
-- parse args
local args = fn.ARGS:match("%(%s*(.-)%s*%)%s*;") or ""
fn.ARGS = "("..args..")"
-- skip return void types
local what = fn.RET == "void" and "" or fn.RET
what = what:match("%s*(.-)%s*$")
fn.RET = "("..what..")"
fn.DESCR = ""
if (what ~= "") then
fn.TYPE = what
end
table.insert(funcs,curfunc)
curfunc = nil
end
local outer = tx:gsub("(%b{})","{}")
-- FIXME pattern doesnt recognize multiline defs
for l in outer:gmatch("[^\r\n]+") do
-- extern void func(blubb);
-- extern void ( * func )(blubb);
-- void func(blubb);
-- void ( * func )(blubb);
-- void * ( * func )(blubb);
local typedef = l:match("typedef")
local ret,name,args = string.match(typedef and "" or l,
"%s*([_%w%*%s]+)%s+%(?[%s%*]*([_%w]+)%s*%)?%s*(%([^%(]*;)")
if (not (curfunc or typedef) and (ret and name and args)) then
ret = ret:gsub("^%s*extern%s*","")
curfunc = {RET=ret,NAME=name,ARGS=args}
registerfunc()
elseif (not typedef) then
local typ,names,val = l:match("%s*([_%w%s%*]+)%s+([_%w%[%]]+)[\r\n%s]*=[\r\n%s]*([_%w]+)[\r\n%s]*;")
if (not (typ and names and val)) then
typ,names = l:match("%s*([_%w%s%*]+)%s+([_%w%[%]%:%s,]+)[\r\n%s]*;")
end
if (typ and names) then
for name,rest in names:gmatch("([_%w]+)([^,]*)") do
rest = rest and rest:gsub("%s","") or ""
local what = typ..(rest:gsub("%b[]","*"))
table.insert(values,{NAME=name, DESCR=(typ..rest..(val and (" = "..val) or "")), TYPE = what,})
end
end
elseif(typedef) then
-- typedef struct lxgTextureUpdate_s * lxgTextureUpdatePTR;
-- typedef float lxVector2[2];
local what,name = l:match("typedef%s+([_%w%s%*]-)%s+([_%w%[%]]+)%s*;")
if (what and name) then
what = what:gsub("const%s","")
what = what:gsub("static%s","")
what = what:gsub("%s+"," ")
what = what:gsub("%s+%*","*")
local name,rest = name:match("([_%w]+)(.*)")
rest = rest and rest:gsub("%s","") or ""
if (what and name) then
lktypes[name] = what..(rest:gsub("%b[]","*"))
end
end
end
end
-- search for enums
for def in tx:gmatch("enum[_%w%s\r\n]*(%b{})[_%w%s\r\n]*;") do
for enum in def:gmatch("([_%w]+).-[,}]") do
table.insert(enums,{NAME=enum})
end
end
-- search for classes
for class,def,final in tx:gmatch("struct%s+([_%w]*)[%s\r\n]*(%b{})([_%w%s\r\n]*);") do
final = final:match("[_%w]+")
if (final) then
lktypes["struct "..class] = ns.."."..final
lktypes[final] = ns.."."..final
lktypes[ns.."."..final] = ns.."."..final
else
lktypes["struct "..class] = ns.."."..class
lktypes[ns.."."..class] = ns.."."..class
end
table.insert(classes,{NAME= final or class,DESCR = "",content = gencontent(def:sub(2,-2))})
end
return (#classes > 0 or #funcs > 0 or #enums > 0 or #values > 0) and
{classes=classes,funcs=funcs, enums=enums, values=values}
end
local content = gencontent(ffidef)
local function fixtypes(tab)
for i,v in ipairs(tab) do
local vt = v.TYPE
if (vt) then
local nt = vt
repeat
nt = nt:match("%s*(.-)%s*$")
nt = nt:gsub("%s+"," ")
nt = nt:gsub("%s%*","*")
nt = nt == "const char*" and "string" or nt
nt = nt:gsub("%*","")
nt = nt:gsub("const%s","")
nt = nt:gsub("static%s","")
vt = nt
local typ,qual = nt:match("([_%w%.%s]+)(%**)")
nt = (lktypes[typ] or "")..(qual or "")
until nt==vt
v.TYPE = nt ~= "" and '"'..nt..'"' or "nil"
else
v.TYPE = "nil"
end
end
end
local function fixcontent(tab)
fixtypes(tab.values)
fixtypes(tab.funcs)
for i,v in ipairs(tab.classes) do
fixcontent(v.content)
end
end
fixcontent(content)
str = str..[[
--auto-generated api from ffi headers
local api =
]]
-- serialize api string
local function serialize(str,id,tab,lvl)
lvl = string.rep(" ",lvl or 1)
for i,k in ipairs(tab) do
str = str..string.gsub(id,"%$([%w]+)%$",k):gsub("##",lvl)
end
return str
end
local function genapi(str,content,lvl)
lvl = lvl or 1
str = str..string.gsub([[
##{
]],"##",string.rep(" ",lvl))
local value =
[[##["$NAME$"] = { type ='value', description = "$DESCR$", valuetype = $TYPE$, },
]]
local enum =
[[##["$NAME$"] = { type ='value', },
]]
local funcdef =
[[##["$NAME$"] = { type ='function',
## description = "$DESCR$",
## returns = "$RET$",
## valuetype = $TYPE$,
## args = "$ARGS$", },
]]
str = serialize(str,value,content.values or {},lvl)
str = serialize(str,enum,content.enums or {},lvl)
str = serialize(str,funcdef,content.funcs or {},lvl)
local classdef =
[[##["$NAME$"] = { type ='class',
## description = "$DESCR$",
## $CHILDS$
##},
]]
for i,v in pairs(content.classes or {}) do
v.CHILDS = v.content and genapi("childs = ",v.content,lvl+1) or ""
end
str = serialize(str,classdef,content.classes or {},lvl)
str = str..string.gsub([[
##}]],"##",string.rep(" ",lvl))
return str
end
str = genapi(str,content)
str = str..[[
return {
]]
local lib =
[[
$NAME$ = {
type = 'lib',
description = "$DESCR$",
childs = $API$,
},
]]
local libs = {}
for i,prefix in ipairs(prefixes) do
local p = {NAME=prefix, DESCR = description, API="api"}
table.insert(libs,p)
end
str = serialize(str,lib,libs)
str = str..[[
}
]]
return str
end
local function exec(wxfname,projectdir)
-- get cur editor text
local editor = GetEditor()
if (not editor) then return end
local tx = editor:GetText()
tx = ffiToApi(tx)
-- replace text
if tx then editor:SetText(tx) end
end
if (not ide) then
ffitoapi = function(fname)
local f = io.open(fname,"rb")
local tx = f:read("*a")
f:close()
tx = ffiToApi(tx)
local f = io.open(fname,"wb")
f:write(tx)
f:close()
end
end
return {
exec = {
name = "luajit ffi string to editor api",
description = "converts current file to api, for autocompletion inside this editor",
fn = exec,
},
}