You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
962 lines
30 KiB
Lua
962 lines
30 KiB
Lua
-- LuaJIT-based binding to libclang, modelled after
|
|
-- https://github.com/mkottman/luaclang-parser
|
|
--
|
|
-- See COPYRIGHT.TXT for the Copyright Notice of LJClang.
|
|
-- LICENSE_LLVM.TXT is the license for libclang.
|
|
|
|
local assert = assert
|
|
local error = error
|
|
local print = print
|
|
local require = require
|
|
local select = select
|
|
local setmetatable = setmetatable
|
|
local table = table
|
|
local tonumber = tonumber
|
|
local tostring = tostring
|
|
local type = type
|
|
local unpack = unpack
|
|
|
|
local ffi = require("ffi")
|
|
local C = ffi.C
|
|
|
|
local bit = require("bit")
|
|
local io = require("io")
|
|
|
|
local function lib(basename)
|
|
return (ffi.os=="Windows" and "lib" or "")..basename
|
|
end
|
|
|
|
local clang = ffi.load(lib"clang")
|
|
require("ljclang_Index_h")
|
|
local support = ffi.load(lib"ljclang_support")
|
|
local g_CursorKindName = require("ljclang_cursor_kind").name
|
|
|
|
-------------------------------------------------------------------------
|
|
|
|
-- The table of externally exposed elements, returned at the end.
|
|
local api = {}
|
|
|
|
--[[
|
|
local function debugf(fmt, ...)
|
|
print(string.format("ljclang: "..fmt, ...))
|
|
end
|
|
--[=[]]
|
|
local function debugf() end
|
|
--]=]
|
|
|
|
-- Wrap 'error' in assert-like call to write type checks in one line instead of
|
|
-- three.
|
|
local function check(pred, msg, level)
|
|
if (not pred) then
|
|
error(msg, level+1)
|
|
end
|
|
end
|
|
|
|
-- CXIndex is a pointer type, wrap it to be able to define a metatable.
|
|
local Index_t = ffi.typeof "struct { CXIndex _idx; }"
|
|
local TranslationUnit_t_ = ffi.typeof "struct { CXTranslationUnit _tu; }"
|
|
-- NOTE: CXCursor is a struct type by itself, but we wrap it to e.g. provide a
|
|
-- kind() *method* (CXCursor contains a member of the same name).
|
|
local Cursor_t = ffi.typeof "struct { CXCursor _cur; }"
|
|
local Type_t = ffi.typeof "struct { CXType _typ; }"
|
|
|
|
-- CompilationDatabase types
|
|
local CompilationDatabase_t = ffi.typeof "struct { CXCompilationDatabase _ptr; }"
|
|
local CompileCommands_t = ffi.typeof "struct { CXCompileCommands _ptr; const double numCommands; }"
|
|
local CompileCommand_t = ffi.typeof[[
|
|
struct {
|
|
CXCompileCommand _ptr;
|
|
const double numArgs, numSources;
|
|
}
|
|
]]
|
|
|
|
-- [<address of CXTranslationUnit as string>] = count
|
|
local TUCount = {}
|
|
|
|
local function getCXTUaddr(cxtu)
|
|
return tostring(cxtu):gsub(".*: 0x", "")
|
|
end
|
|
|
|
local function TranslationUnit_t(cxtu)
|
|
local addr = getCXTUaddr(cxtu)
|
|
TUCount[addr] = (TUCount[addr] or 0) + 1
|
|
return TranslationUnit_t_(cxtu)
|
|
end
|
|
|
|
-- Our wrapping type Cursor_t is seen as raw CXCursor on the C side.
|
|
assert(ffi.sizeof("CXCursor") == ffi.sizeof(Cursor_t))
|
|
|
|
ffi.cdef([[
|
|
typedef enum CXChildVisitResult (*LJCX_CursorVisitor)(
|
|
$ *cursor, $ *parent, CXClientData client_data);
|
|
]], Cursor_t, Cursor_t)
|
|
|
|
ffi.cdef[[
|
|
int ljclang_regCursorVisitor(LJCX_CursorVisitor visitor, enum CXCursorKind *kinds, int numkinds);
|
|
int ljclang_visitChildren(CXCursor parent, int visitoridx);
|
|
]]
|
|
|
|
-------------------------------------------------------------------------
|
|
-------------------------------- CXString -------------------------------
|
|
-------------------------------------------------------------------------
|
|
|
|
local CXString = ffi.typeof("CXString")
|
|
|
|
-- Convert from a libclang's encapsulated CXString to a plain Lua string and
|
|
-- dispose of the CXString afterwards.
|
|
local function getString(cxstr)
|
|
assert(ffi.istype(CXString, cxstr))
|
|
local cstr = clang.clang_getCString(cxstr)
|
|
assert(cstr ~= nil)
|
|
local str = ffi.string(cstr)
|
|
clang.clang_disposeString(cxstr)
|
|
return str
|
|
end
|
|
|
|
-------------------------------------------------------------------------
|
|
-------------------------- CompilationDatabase --------------------------
|
|
-------------------------------------------------------------------------
|
|
|
|
-- newargs = api.stripArgs(args, pattern, num)
|
|
function api.stripArgs(args, pattern, num)
|
|
assert(args[0] == nil)
|
|
local numArgs = #args
|
|
|
|
for i=1,numArgs do
|
|
if (args[i] and args[i]:find(pattern)) then
|
|
for j=0,num-1 do
|
|
args[i+j] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local newargs = {}
|
|
for i=1,numArgs do
|
|
newargs[#newargs+1] = args[i]
|
|
end
|
|
return newargs
|
|
end
|
|
|
|
local CompDB_Error_Ar = ffi.typeof[[
|
|
CXCompilationDatabase_Error [1]
|
|
]]
|
|
|
|
local CompileCommand_mt = {
|
|
__index = {
|
|
-- args = cmd:getArgs([alsoCompilerExe])
|
|
-- args: sequence table. If <alsoCompilerExe> is true, args[0] is the
|
|
-- compiler executable.
|
|
getArgs = function(self, alsoCompilerExe)
|
|
local args = {}
|
|
for i = (alsoCompilerExe and 0 or 1), self.numArgs-1 do
|
|
local cxstr = clang.clang_CompileCommand_getArg(self._ptr, i)
|
|
args[i] = getString(cxstr)
|
|
end
|
|
|
|
return args
|
|
end,
|
|
|
|
-- dir = cmd:getDirectory()
|
|
getDirectory = function(self)
|
|
local cxstr = clang.clang_CompileCommand_getDirectory(self._ptr)
|
|
return getString(cxstr)
|
|
end,
|
|
|
|
-- paths = cmd:getSourcePaths()
|
|
-- paths: sequence table.
|
|
getSourcePaths = function(self)
|
|
-- XXX: This is a workaround implementation due to a missing
|
|
-- clang_CompileCommand_getMappedSourcePath symbol in libclang.so,
|
|
-- see commented out code below.
|
|
local args = self:getArgs()
|
|
for i=1,#args do
|
|
if (args[i] == '-c' and args[i+1]) then
|
|
local sourceFile = args[i+1]
|
|
if (sourceFile:sub(1,1) ~= "/") then -- XXX: Windows
|
|
sourceFile = self:getDirectory() .. "/" .. sourceFile
|
|
end
|
|
return { sourceFile }
|
|
end
|
|
end
|
|
|
|
print(table.concat(args, ' '))
|
|
check(false, "Did not find -c option (workaround for missing "..
|
|
"clang_CompileCommand_getMappedSourcePath symbol)")
|
|
--[[
|
|
local paths = {}
|
|
for i=0,self.numSources-1 do
|
|
-- XXX: for me, the symbol is missing in libclang.so:
|
|
local cxstr = clang.clang_CompileCommand_getMappedSourcePath(self._ptr, i)
|
|
paths[i] = getString(cxstr)
|
|
end
|
|
return paths
|
|
--]]
|
|
end,
|
|
},
|
|
}
|
|
|
|
local CompileCommands_mt = {
|
|
-- #commands: number of commands
|
|
__len = function(self)
|
|
return self.numCommands
|
|
end,
|
|
|
|
-- commands[i]: get the i'th CompileCommand object (i is 1-based)
|
|
__index = function(self, i)
|
|
check(type(i) == "number", "<i> must be a number", 2)
|
|
check(i >= 1 and i <= self.numCommands, "<i> must be in [1, numCommands]", 2)
|
|
local cmdPtr = clang.clang_CompileCommands_getCommand(self._ptr, i-1)
|
|
local numArgs = clang.clang_CompileCommand_getNumArgs(cmdPtr)
|
|
local numSources = 0 --clang.clang_CompileCommand_getNumMappedSources(cmdPtr)
|
|
return CompileCommand_t(cmdPtr, numArgs, numSources)
|
|
end,
|
|
|
|
__gc = function(self)
|
|
clang.clang_CompileCommands_dispose(self._ptr)
|
|
end,
|
|
}
|
|
|
|
local CompilationDatabase_mt = {
|
|
__index = {
|
|
getCompileCommands = function(self, completeFileName)
|
|
check(type(completeFileName) == "string", "<completeFileName> must be a string", 2)
|
|
local cmdsPtr = clang.clang_CompilationDatabase_getCompileCommands(
|
|
self._ptr, completeFileName)
|
|
local numCommands = clang.clang_CompileCommands_getSize(cmdsPtr)
|
|
return CompileCommands_t(cmdsPtr, numCommands)
|
|
end,
|
|
|
|
getAllCompileCommands = function(self)
|
|
local cmdsPtr = clang.clang_CompilationDatabase_getAllCompileCommands(self._ptr)
|
|
local numCommands = clang.clang_CompileCommands_getSize(cmdsPtr)
|
|
return CompileCommands_t(cmdsPtr, numCommands)
|
|
end,
|
|
},
|
|
|
|
__gc = function(self)
|
|
clang.clang_CompilationDatabase_dispose(self._ptr)
|
|
end,
|
|
}
|
|
|
|
-- compDB = api.CompilationDatabase(buildDir)
|
|
function api.CompilationDatabase(buildDir)
|
|
check(type(buildDir) == "string", "<buildDir> must be a string", 2)
|
|
|
|
local errAr = CompDB_Error_Ar()
|
|
local ptr = clang.clang_CompilationDatabase_fromDirectory(buildDir, errAr)
|
|
assert(ptr ~= nil or errAr[0] ~= 'CXCompilationDatabase_NoError')
|
|
|
|
return ptr ~= nil and CompilationDatabase_t(ptr) or nil
|
|
end
|
|
|
|
-------------------------------------------------------------------------
|
|
--------------------------------- Index ---------------------------------
|
|
-------------------------------------------------------------------------
|
|
|
|
-- Metatable for our Index_t.
|
|
local Index_mt = {
|
|
__index = {},
|
|
|
|
__gc = function(self)
|
|
-- "The index must not be destroyed until all of the translation units created
|
|
-- within that index have been destroyed."
|
|
for i=1,#self._tus do
|
|
self._tus[i]:_cleanup()
|
|
end
|
|
clang.clang_disposeIndex(self._idx)
|
|
end,
|
|
}
|
|
|
|
local function NewIndex(cxidx)
|
|
assert(ffi.istype("CXIndex", cxidx))
|
|
-- _tus is a list of the Index_t's TranslationUnit_t objects.
|
|
local index = { _idx=cxidx, _tus={} }
|
|
return setmetatable(index, Index_mt)
|
|
end
|
|
|
|
-------------------------------------------------------------------------
|
|
---------------------------- TranslationUnit ----------------------------
|
|
-------------------------------------------------------------------------
|
|
|
|
local function check_tu_valid(self)
|
|
if (self._tu == nil) then
|
|
error("Attempt to access freed TranslationUnit", 3)
|
|
end
|
|
end
|
|
|
|
-- Construct a Cursor_t from a libclang's CXCursor <cxcur>. If <cxcur> is the
|
|
-- NULL cursor, return nil.
|
|
local function getCursor(cxcur)
|
|
return (clang.clang_Cursor_isNull(cxcur) == 0) and Cursor_t(cxcur) or nil
|
|
end
|
|
|
|
-- Metatable for our TranslationUnit_t.
|
|
local TranslationUnit_mt = {
|
|
__index = {
|
|
_cleanup = function(self)
|
|
if (self._tu ~= nil) then
|
|
local addr = getCXTUaddr(self._tu)
|
|
TUCount[addr] = TUCount[addr]-1
|
|
if (TUCount[addr] == 0) then
|
|
clang.clang_disposeTranslationUnit(self._tu)
|
|
TUCount[addr] = nil
|
|
end
|
|
self._tu = nil
|
|
end
|
|
end,
|
|
|
|
cursor = function(self)
|
|
check_tu_valid(self)
|
|
local cxcur = clang.clang_getTranslationUnitCursor(self._tu)
|
|
return getCursor(cxcur)
|
|
end,
|
|
|
|
file = function(self, filename)
|
|
check_tu_valid(self)
|
|
if (type(filename) ~= "string") then
|
|
error("<filename> must be a string", 2)
|
|
end
|
|
local cxfile = clang.clang_getFile(self._tu, filename)
|
|
return getString(clang.clang_getFileName(cxfile)) -- NYI: modification time
|
|
end,
|
|
|
|
diagnostics = function(self)
|
|
check_tu_valid(self)
|
|
|
|
local numdiags = clang.clang_getNumDiagnostics(self._tu)
|
|
local tab = {}
|
|
|
|
for i=0,numdiags-1 do
|
|
local diag = clang.clang_getDiagnostic(self._tu, i)
|
|
tab[i+1] = {
|
|
category = getString(clang.clang_getDiagnosticCategoryText(diag)),
|
|
text = getString(clang.clang_formatDiagnostic(
|
|
diag, clang.clang_defaultDiagnosticDisplayOptions())),
|
|
severity = clang.clang_getDiagnosticSeverity(diag),
|
|
}
|
|
clang.clang_disposeDiagnostic(diag)
|
|
end
|
|
|
|
return tab
|
|
end,
|
|
},
|
|
}
|
|
|
|
TranslationUnit_mt.__gc = TranslationUnit_mt.__index._cleanup
|
|
|
|
-------------------------------------------------------------------------
|
|
--------------------------------- Cursor --------------------------------
|
|
-------------------------------------------------------------------------
|
|
|
|
local function getType(cxtyp)
|
|
return cxtyp.kind ~= 'CXType_Invalid' and Type_t(cxtyp) or nil
|
|
end
|
|
|
|
local CXFileAr = ffi.typeof("CXFile [1]")
|
|
|
|
local LineCol = ffi.typeof[[
|
|
union {
|
|
struct { unsigned line, col; };
|
|
unsigned ar[2];
|
|
}
|
|
]]
|
|
|
|
local LineColOfs = ffi.typeof[[
|
|
union {
|
|
struct { unsigned line, col, offset; };
|
|
unsigned ar[3];
|
|
}
|
|
]]
|
|
|
|
-- Get line number, column number and offset for a given CXSourceRange
|
|
local function getLineColOfs(cxsrcrange, cxfile, clang_rangefunc)
|
|
local cxsrcloc = clang[clang_rangefunc](cxsrcrange)
|
|
local lco = LineColOfs()
|
|
clang.clang_getSpellingLocation(cxsrcloc, cxfile, lco.ar, lco.ar+1, lco.ar+2)
|
|
return lco
|
|
end
|
|
|
|
-- Returns a LineColOfs for the beginning and end of a range. Also, the file name.
|
|
local function getBegEndFilename(cxsrcrange)
|
|
local cxfilear = CXFileAr()
|
|
local Beg = getLineColOfs(cxsrcrange, cxfilear, "clang_getRangeStart")
|
|
local filename = getString(clang.clang_getFileName(cxfilear[0]))
|
|
local End = getLineColOfs(cxsrcrange, cxfilear, "clang_getRangeEnd")
|
|
return Beg, End, filename
|
|
end
|
|
|
|
local function getPresumedLineCol(cxsrcrange, clang_rangefunc)
|
|
local cxsrcloc = clang[clang_rangefunc](cxsrcrange)
|
|
local linecol = LineCol()
|
|
local file = CXString()
|
|
clang.clang_getPresumedLocation(cxsrcloc, file, linecol.ar, linecol.ar+1)
|
|
return linecol, getString(file)
|
|
end
|
|
|
|
local function getSourceRange(cxcur)
|
|
local cxsrcrange = clang.clang_getCursorExtent(cxcur)
|
|
return (clang.clang_Range_isNull(cxsrcrange) == 0) and cxsrcrange or nil
|
|
end
|
|
|
|
-- Metatable for our Cursor_t.
|
|
local Cursor_mt = {
|
|
__eq = function(cur1, cur2)
|
|
if (ffi.istype(Cursor_t, cur1) and ffi.istype(Cursor_t, cur2)) then
|
|
return (clang.clang_equalCursors(cur1._cur, cur2._cur) ~= 0)
|
|
else
|
|
return false
|
|
end
|
|
end,
|
|
|
|
__index = {
|
|
parent = function(self)
|
|
return getCursor(clang.clang_getCursorSemanticParent(self._cur))
|
|
end,
|
|
|
|
name = function(self)
|
|
return getString(clang.clang_getCursorSpelling(self._cur))
|
|
end,
|
|
|
|
displayName = function(self)
|
|
return getString(clang.clang_getCursorDisplayName(self._cur))
|
|
end,
|
|
|
|
kind = function(self)
|
|
local kindnum = tonumber(self:kindnum())
|
|
local kindstr = g_CursorKindName[kindnum]
|
|
return kindstr or "Unknown"
|
|
end,
|
|
|
|
templateKind = function(self)
|
|
local kindnum = tonumber(clang.clang_getTemplateCursorKind(self._cur))
|
|
local kindstr = g_CursorKindName[kindnum]
|
|
return kindstr or "Unknown"
|
|
end,
|
|
|
|
arguments = function(self)
|
|
local tab = {}
|
|
local numargs = clang.clang_Cursor_getNumArguments(self._cur)
|
|
for i=1,numargs do
|
|
tab[i] = getCursor(clang.clang_Cursor_getArgument(self._cur, i-1))
|
|
end
|
|
return tab
|
|
end,
|
|
|
|
overloadedDecls = function(self)
|
|
local tab = {}
|
|
local numdecls = clang.clang_getNumOverloadedDecls(self._cur)
|
|
for i=1,numdecls do
|
|
tab[i] = getCursor(clang.clang_getOverloadedDecl(self._cur, i-1))
|
|
end
|
|
return tab
|
|
end,
|
|
|
|
location = function(self, linesfirst)
|
|
local cxsrcrange = getSourceRange(self._cur)
|
|
if (cxsrcrange == nil) then
|
|
return nil
|
|
end
|
|
|
|
local Beg, End, filename = getBegEndFilename(cxsrcrange)
|
|
|
|
if (linesfirst == 'offset') then
|
|
return filename, Beg.offset, End.offset
|
|
elseif (linesfirst) then
|
|
-- LJClang order -- IMO you're usually more interested in the
|
|
-- line number
|
|
return filename, Beg.line, End.line, Beg.col, End.col, Beg.offset, End.offset
|
|
else
|
|
-- luaclang-parser order (offset: XXX)
|
|
return filename, Beg.line, Beg.col, End.line, End.col, Beg.offset, End.offset
|
|
end
|
|
end,
|
|
|
|
presumedLocation = function(self, linesfirst)
|
|
local cxsrcrange = getSourceRange(self._cur)
|
|
if (cxsrcrange == nil) then
|
|
return nil
|
|
end
|
|
|
|
local Beg, filename = getPresumedLineCol(cxsrcrange, "clang_getRangeStart")
|
|
local End = getPresumedLineCol(cxsrcrange, "clang_getRangeEnd")
|
|
|
|
if (linesfirst) then
|
|
return filename, Beg.line, End.line, Beg.col, End.col
|
|
else
|
|
return filename, Beg.line, Beg.col, End.line, End.col
|
|
end
|
|
end,
|
|
|
|
referenced = function(self)
|
|
return getCursor(clang.clang_getCursorReferenced(self._cur))
|
|
end,
|
|
|
|
definition = function(self)
|
|
return getCursor(clang.clang_getCursorDefinition(self._cur))
|
|
end,
|
|
|
|
isDeleted = function(self)
|
|
return clang.clang_CXX_isDeleted(self._cur) ~= 0
|
|
end,
|
|
|
|
isMutable = function(self)
|
|
return clang.clang_CXXField_isMutable(self._cur) ~= 0
|
|
end,
|
|
|
|
isDefaulted = function(self)
|
|
return clang.clang_CXXMethod_isDefaulted(self._cur) ~= 0
|
|
end,
|
|
|
|
isPureVirtual = function(self)
|
|
return clang.clang_CXXMethod_isPureVirtual(self._cur) ~= 0
|
|
end,
|
|
|
|
isVirtual = function(self)
|
|
return clang.clang_CXXMethod_isVirtual(self._cur) ~= 0
|
|
end,
|
|
|
|
isOverride = function(self)
|
|
return clang.clang_CXXMethod_isOverride(self._cur) ~= 0
|
|
end,
|
|
|
|
isStatic = function(self)
|
|
return clang.clang_CXXMethod_isStatic(self._cur) ~= 0
|
|
end,
|
|
|
|
isConst = function(self)
|
|
return clang.clang_CXXMethod_isConst(self._cur) ~= 0
|
|
end,
|
|
|
|
type = function(self)
|
|
return getType(clang.clang_getCursorType(self._cur))
|
|
end,
|
|
|
|
resultType = function(self)
|
|
return getType(clang.clang_getCursorResultType(self._cur))
|
|
end,
|
|
|
|
access = function(self)
|
|
local spec = clang.clang_getCXXAccessSpecifier(self._cur);
|
|
|
|
if (spec == 'CX_CXXPublic') then
|
|
return "public"
|
|
elseif (spec == 'CX_CXXProtected') then
|
|
return "protected"
|
|
elseif (spec == 'CX_CXXPrivate') then
|
|
return "private"
|
|
else
|
|
assert(spec == 'CX_CXXInvalidAccessSpecifier')
|
|
return nil
|
|
end
|
|
end,
|
|
|
|
--== LJClang-specific ==--
|
|
|
|
translationUnit = function(self)
|
|
return TranslationUnit_t(clang.clang_Cursor_getTranslationUnit(self._cur))
|
|
end,
|
|
|
|
-- XXX: Should be a TranslationUnit_t method instead.
|
|
--
|
|
-- NOTE: *Sometimes* returns one token too much, see
|
|
-- http://clang-developers.42468.n3.nabble.com/querying-information-about-preprocessing-directives-in-libclang-td2740612.html
|
|
-- Related bug report:
|
|
-- http://llvm.org/bugs/show_bug.cgi?id=9069
|
|
-- Also, see TOKENIZE_WORKAROUND in extractdecls.lua
|
|
_tokens = function(self)
|
|
local tu = self:translationUnit()
|
|
local cxtu = tu._tu
|
|
|
|
local _, b, e = self:location('offset')
|
|
local cxsrcrange = getSourceRange(self._cur)
|
|
if (cxsrcrange == nil) then
|
|
return nil
|
|
end
|
|
|
|
local ntoksar = ffi.new("unsigned [1]")
|
|
local tokensar = ffi.new("CXToken *[1]")
|
|
clang.clang_tokenize(cxtu, cxsrcrange, tokensar, ntoksar)
|
|
local numtoks = ntoksar[0]
|
|
local tokens = tokensar[0]
|
|
|
|
local tab = {}
|
|
local tabextra = {}
|
|
|
|
local kinds = {
|
|
[tonumber(C.CXToken_Punctuation)] = 'Punctuation',
|
|
[tonumber(C.CXToken_Keyword)] = 'Keyword',
|
|
[tonumber(C.CXToken_Identifier)] = 'Identifier',
|
|
[tonumber(C.CXToken_Literal)] = 'Literal',
|
|
[tonumber(C.CXToken_Comment)] = 'Comment',
|
|
}
|
|
|
|
for i=0,numtoks-1 do
|
|
if (clang.clang_getTokenKind(tokens[i]) ~= 'CXToken_Comment') then
|
|
local sourcerange = clang.clang_getTokenExtent(cxtu, tokens[i])
|
|
local Beg, End, filename = getBegEndFilename(sourcerange)
|
|
local tb, te = Beg.offset, End.offset
|
|
|
|
if (tb >= b and te <= e) then
|
|
local kind = clang.clang_getTokenKind(tokens[i])
|
|
local extent = getString(clang.clang_getTokenSpelling(cxtu, tokens[i]))
|
|
tab[#tab+1] = extent
|
|
tabextra[#tabextra+1] = {
|
|
extent = extent,
|
|
kind = kinds[tonumber(kind)],
|
|
b = b, e = e,
|
|
tb = tb, te = te
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
clang.clang_disposeTokens(cxtu, tokens, numtoks)
|
|
|
|
return tab, tabextra
|
|
end,
|
|
|
|
lexicalParent = function(self)
|
|
return getCursor(clang.clang_getCursorLexicalParent(self._cur))
|
|
end,
|
|
|
|
baseTemplate = function(self)
|
|
return getCursor(clang.clang_getSpecializedCursorTemplate(self._cur))
|
|
end,
|
|
|
|
-- Returns an enumeration constant, which in LuaJIT can be compared
|
|
-- against a *string*, too.
|
|
-- XXX: Should we split into 'kindenum' (giving the enum) and 'kindnum'?
|
|
kindnum = function(self)
|
|
return clang.clang_getCursorKind(self._cur)
|
|
end,
|
|
|
|
haskind = function(self, kind)
|
|
if (type(kind) == "string") then
|
|
return self:kindnum() == "CXCursor_"..kind
|
|
else
|
|
return self:kindnum() == kind
|
|
end
|
|
end,
|
|
|
|
enumValue = function(self, unsigned)
|
|
if (not self:haskind("EnumConstantDecl")) then
|
|
error("cursor must have kind EnumConstantDecl", 2)
|
|
end
|
|
|
|
if (unsigned) then
|
|
return clang.clang_getEnumConstantDeclUnsignedValue(self._cur)
|
|
else
|
|
return clang.clang_getEnumConstantDeclValue(self._cur)
|
|
end
|
|
end,
|
|
|
|
enumval = function(self, unsigned)
|
|
return tonumber(self:enumValue(unsigned))
|
|
end,
|
|
|
|
isDefinition = function(self)
|
|
return (clang.clang_isCursorDefinition(self._cur) ~= 0)
|
|
end,
|
|
--[=[
|
|
--| tab = cur:argtypes([alsoret])
|
|
argtypes = function(self, alsoret)
|
|
if (clang.clang_Cursor_getNumArguments(self._cur) == -1) then
|
|
return nil
|
|
end
|
|
|
|
local tab = self:arguments()
|
|
|
|
if (alsoret) then
|
|
tab[0] = self:resultType()
|
|
end
|
|
|
|
for i=1,#tab do
|
|
tab[i] = tab[i]:type()
|
|
end
|
|
end,
|
|
--]=]
|
|
typedefType = function(self)
|
|
return getType(clang.clang_getTypedefDeclUnderlyingType(self._cur))
|
|
end,
|
|
},
|
|
}
|
|
|
|
Cursor_mt.__tostring = Cursor_mt.__index.name
|
|
|
|
-------------------------------------------------------------------------
|
|
---------------------------------- Type ---------------------------------
|
|
-------------------------------------------------------------------------
|
|
|
|
-- Metatable for our Type_t.
|
|
local Type_mt = {
|
|
__eq = function(typ1, typ2)
|
|
if (ffi.istype(Type_t, typ1) and ffi.istype(Type_t, typ2)) then
|
|
return (clang.clang_equalTypes(typ1._typ, typ2._typ) ~= 0)
|
|
else
|
|
return false
|
|
end
|
|
end,
|
|
|
|
__index = {
|
|
name = function(self)
|
|
return getString(clang.clang_getTypeSpelling(self._typ))
|
|
end,
|
|
|
|
canonical = function(self)
|
|
-- NOTE: no dispatching to getPointeeType() for pointer types like
|
|
-- luaclang-parser.
|
|
return getType(clang.clang_getCanonicalType(self._typ))
|
|
end,
|
|
|
|
pointee = function(self)
|
|
return getType(clang.clang_getPointeeType(self._typ))
|
|
end,
|
|
|
|
resultType = function(self)
|
|
return getType(clang.clang_getResultType(self._typ))
|
|
end,
|
|
|
|
arrayElementType = function(self)
|
|
return getType(clang.clang_getArrayElementType(self._typ))
|
|
end,
|
|
|
|
arraySize = function(self)
|
|
return tonumber(clang.clang_getArraySize(self._typ))
|
|
end,
|
|
|
|
isConst = function(self)
|
|
return (clang.clang_isConstQualifiedType(self._typ) ~= 0);
|
|
end,
|
|
|
|
isPod = function(self)
|
|
return (clang.clang_isPODType(self._typ) ~= 0);
|
|
end,
|
|
|
|
isFinal = function(self)
|
|
return (clang.clang_isFinalType(self._typ) ~= 0);
|
|
end,
|
|
|
|
isAbstract = function(self)
|
|
return (clang.clang_isAbstractType(self._typ) ~= 0);
|
|
end,
|
|
|
|
isNoexcept = function(self)
|
|
return (clang.clang_isNoexcept(self._typ) ~= 0);
|
|
end,
|
|
|
|
declaration = function(self)
|
|
return getCursor(clang.clang_getTypeDeclaration(self._typ))
|
|
end,
|
|
|
|
--== LJClang-specific ==--
|
|
-- Returns an enumeration constant.
|
|
kindnum = function(self)
|
|
return self._typ.kind
|
|
end,
|
|
|
|
haskind = function(self, kind)
|
|
if (type(kind) == "string") then
|
|
return self:kindnum() == "CXType_"..kind
|
|
else
|
|
return self:kindnum() == kind
|
|
end
|
|
end,
|
|
|
|
templateArguments = function(self)
|
|
local tab = {}
|
|
local numargs = clang.clang_Type_getNumTemplateArguments(self._typ)
|
|
for i=1,numargs do
|
|
tab[i] = getType(clang.clang_Type_getTemplateArgumentAsType(self._typ, i-1))
|
|
end
|
|
return tab
|
|
end,
|
|
},
|
|
}
|
|
|
|
-- Aliases
|
|
local Type__index = Type_mt.__index
|
|
Type__index.isConstQualified = Type__index.isConst
|
|
|
|
Type_mt.__tostring = Type__index.name
|
|
|
|
-------------------------------------------------------------------------
|
|
|
|
function api.clangVersion()
|
|
return getString(clang.clang_getClangVersion())
|
|
end
|
|
|
|
--| index = clang.createIndex([excludeDeclarationsFromPCH [, displayDiagnostics]])
|
|
function api.createIndex(excludeDeclarationsFromPCH, displayDiagnostics)
|
|
local cxidx = clang.clang_createIndex(excludeDeclarationsFromPCH or false,
|
|
displayDiagnostics or false)
|
|
if (cxidx == nil) then
|
|
return nil
|
|
end
|
|
|
|
return NewIndex(cxidx)
|
|
end
|
|
|
|
-- argstab = clang.splitAtWhitespace(args)
|
|
function api.splitAtWhitespace(args)
|
|
assert(type(args) == "string")
|
|
local argstab = {}
|
|
-- Split delimited by whitespace.
|
|
for str in args:gmatch("[^%s]+") do
|
|
argstab[#argstab+1] = str
|
|
end
|
|
return argstab
|
|
end
|
|
|
|
-- Is <tab> a sequence of strings?
|
|
local function iscellstr(tab)
|
|
for i=1,#tab do
|
|
if (type(tab[i]) ~= "string") then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- We require this because in ARGS_FROM_TAB below, an index 0 would be
|
|
-- interpreted as the starting index.
|
|
return (tab[0] == nil)
|
|
end
|
|
|
|
local function check_iftab_iscellstr(tab, name)
|
|
if (type(tab)=="table") then
|
|
if (not iscellstr(tab)) then
|
|
error(name.." must be a string sequence when a table with no element at [0]", 3)
|
|
end
|
|
end
|
|
end
|
|
|
|
--| tunit = index:parse(srcfile, args [, opts])
|
|
--|
|
|
--| <args>: string or sequence of strings
|
|
--| <opts>: number or sequence of strings (CXTranslationUnit_* enum members,
|
|
--| without the prefix)
|
|
function Index_mt.__index.parse(self, srcfile, args, opts)
|
|
check(type(srcfile)=="string", "<srcfile> must be a string", 2)
|
|
check(type(args)=="string" or type(args)=="table", "<args> must be a string or table", 2)
|
|
check_iftab_iscellstr(args, "<args>")
|
|
|
|
if (srcfile == "") then
|
|
srcfile = nil
|
|
end
|
|
|
|
if (opts == nil) then
|
|
opts = 0
|
|
else
|
|
check(type(opts)=="number" or type(opts)=="table", 2)
|
|
check_iftab_iscellstr(args, "<opts>")
|
|
end
|
|
|
|
-- Input argument handling.
|
|
|
|
if (type(args)=="string") then
|
|
args = api.splitAtWhitespace(args)
|
|
end
|
|
|
|
if (type(opts)=="table") then
|
|
local optflags = {}
|
|
for i=1,#opts do
|
|
optflags[i] = clang["CXTranslationUnit_"..opts[i]] -- look up the enum
|
|
end
|
|
opts = bit.bor(unpack(optflags))
|
|
end
|
|
|
|
local argsptrs = ffi.new("const char * [?]", #args, args) -- ARGS_FROM_TAB
|
|
|
|
-- Create the CXTranslationUnit.
|
|
local tunitptr = clang.clang_parseTranslationUnit(
|
|
self._idx, srcfile, argsptrs, #args, nil, 0, opts)
|
|
|
|
if (tunitptr == nil) then
|
|
return nil
|
|
end
|
|
|
|
-- Wrap it in a TranslationUnit_t.
|
|
local tunit = TranslationUnit_t(tunitptr)
|
|
|
|
-- Add this TranslationUnit_t to the list of its Index_t's TUs.
|
|
self._tus[#self._tus+1] = tunit
|
|
|
|
return tunit
|
|
end
|
|
|
|
|
|
-- enum CXChildVisitResult constants
|
|
api.ChildVisitResult = ffi.new[[struct{
|
|
static const int Break = 0;
|
|
static const int Continue = 1;
|
|
static const int Recurse = 2;
|
|
}]]
|
|
|
|
function api.regCursorVisitor(visitorfunc)
|
|
check(type(visitorfunc)=="function", "<visitorfunc> must be a Lua function", 2)
|
|
|
|
local ret = support.ljclang_regCursorVisitor(visitorfunc, nil, 0)
|
|
if (ret < 0) then
|
|
error("failed registering visitor function, code "..ret, 2)
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
local Cursor_ptr_t = ffi.typeof("$ *", Cursor_t)
|
|
|
|
function api.Cursor(cur)
|
|
check(ffi.istype(Cursor_ptr_t, cur), "<cur> must be a cursor as passed to the visitor callback", 2)
|
|
return Cursor_t(cur[0])
|
|
end
|
|
|
|
-- Support for legacy luaclang-parser API collecting direct descendants of a
|
|
-- cursor: this will be the table where they go.
|
|
local collectTab
|
|
|
|
local function collectDirectChildren(cur)
|
|
debugf("collectDirectChildren: %s, child cursor kind: %s", tostring(collectTab), cur:kind())
|
|
collectTab[#collectTab+1] = Cursor_t(cur[0])
|
|
return 1 -- Continue
|
|
end
|
|
|
|
local cdc_visitoridx = api.regCursorVisitor(collectDirectChildren)
|
|
|
|
function Cursor_mt.__index.children(self, visitoridx)
|
|
if (visitoridx ~= nil) then
|
|
-- LJClang way of visiting
|
|
local ret = support.ljclang_visitChildren(self._cur, visitoridx)
|
|
return (ret ~= 0)
|
|
else
|
|
-- luaclang-parser way
|
|
if (collectTab ~= nil) then
|
|
error("children() must not be called while another invocation is active", 2)
|
|
collectTab = nil
|
|
end
|
|
|
|
collectTab = {}
|
|
-- XXX: We'll be blocked if the visitor callback errors.
|
|
support.ljclang_visitChildren(self._cur, cdc_visitoridx)
|
|
local tab = collectTab
|
|
collectTab = nil
|
|
return tab
|
|
end
|
|
end
|
|
|
|
|
|
-- Register the metatables for the custom ctypes.
|
|
ffi.metatype(Index_t, Index_mt)
|
|
ffi.metatype(TranslationUnit_t_, TranslationUnit_mt)
|
|
ffi.metatype(Cursor_t, Cursor_mt)
|
|
ffi.metatype(Type_t, Type_mt)
|
|
|
|
ffi.metatype(CompileCommand_t, CompileCommand_mt)
|
|
ffi.metatype(CompileCommands_t, CompileCommands_mt)
|
|
ffi.metatype(CompilationDatabase_t, CompilationDatabase_mt)
|
|
|
|
api.Index_t = Index_t
|
|
api.TranslationUnit_t = TranslationUnit_t_
|
|
api.Cursor_t = Cursor_t
|
|
api.Type_t = Type_t
|
|
|
|
-- Done!
|
|
return api
|