ljclang

FORK: A LuaJIT-based interface to libclang
git clone https://git.neptards.moe/neptards/ljclang.git
Log | Files | Refs

ljclang.lua (31082B)


      1 -- LuaJIT-based binding to libclang, modelled after
      2 -- https://github.com/mkottman/luaclang-parser
      3 --
      4 -- See COPYRIGHT.TXT for the Copyright Notice of LJClang.
      5 -- LICENSE_LLVM.TXT is the license for libclang.
      6 
      7 local assert = assert
      8 local error = error
      9 local print = print
     10 local require = require
     11 local select = select
     12 local setmetatable = setmetatable
     13 local table = table
     14 local tonumber = tonumber
     15 local tostring = tostring
     16 local type = type
     17 local unpack = unpack
     18 
     19 local ffi = require("ffi")
     20 local C = ffi.C
     21 
     22 local bit = require("bit")
     23 local io = require("io")
     24 
     25 local function lib(basename)
     26     return (ffi.os=="Windows" and "lib" or "")..basename
     27 end
     28 
     29 local clang = ffi.load(lib"clang")
     30 require("ljclang_Index_h")
     31 local support = ffi.load(lib"ljclang_support")
     32 local g_CursorKindName = require("ljclang_cursor_kind").name
     33 
     34 -------------------------------------------------------------------------
     35 
     36 -- The table of externally exposed elements, returned at the end.
     37 local api = {}
     38 
     39 --[[
     40 local function debugf(fmt, ...)
     41     print(string.format("ljclang: "..fmt, ...))
     42 end
     43 --[=[]]
     44 local function debugf() end
     45 --]=]
     46 
     47 -- Wrap 'error' in assert-like call to write type checks in one line instead of
     48 -- three.
     49 local function check(pred, msg, level)
     50     if (not pred) then
     51         error(msg, level+1)
     52     end
     53 end
     54 
     55 -- CXIndex is a pointer type, wrap it to be able to define a metatable.
     56 local Index_t = ffi.typeof "struct { CXIndex _idx; }"
     57 local TranslationUnit_t_ = ffi.typeof "struct { CXTranslationUnit _tu; }"
     58 -- NOTE: CXCursor is a struct type by itself, but we wrap it to e.g. provide a
     59 -- kind() *method* (CXCursor contains a member of the same name).
     60 local Cursor_t = ffi.typeof "struct { CXCursor _cur; }"
     61 local Type_t = ffi.typeof "struct { CXType _typ; }"
     62 
     63 -- CompilationDatabase types
     64 local CompilationDatabase_t = ffi.typeof "struct { CXCompilationDatabase _ptr; }"
     65 local CompileCommands_t = ffi.typeof "struct { CXCompileCommands _ptr; const double numCommands; }"
     66 local CompileCommand_t = ffi.typeof[[
     67 struct {
     68     CXCompileCommand _ptr;
     69     const double numArgs, numSources;
     70 }
     71 ]]
     72 
     73 -- [<address of CXTranslationUnit as string>] = count
     74 local TUCount = {}
     75 
     76 local function getCXTUaddr(cxtu)
     77     return tostring(cxtu):gsub(".*: 0x", "")
     78 end
     79 
     80 local function TranslationUnit_t(cxtu)
     81     local addr = getCXTUaddr(cxtu)
     82     TUCount[addr] = (TUCount[addr] or 0) + 1
     83     return TranslationUnit_t_(cxtu)
     84 end
     85 
     86 -- Our wrapping type Cursor_t is seen as raw CXCursor on the C side.
     87 assert(ffi.sizeof("CXCursor") == ffi.sizeof(Cursor_t))
     88 
     89 ffi.cdef([[
     90 typedef enum CXChildVisitResult (*LJCX_CursorVisitor)(
     91     $ *cursor, $ *parent, CXClientData client_data);
     92 ]], Cursor_t, Cursor_t)
     93 
     94 ffi.cdef[[
     95 int ljclang_regCursorVisitor(LJCX_CursorVisitor visitor, enum CXCursorKind *kinds, int numkinds);
     96 int ljclang_visitChildren(CXCursor parent, int visitoridx);
     97 ]]
     98 
     99 -------------------------------------------------------------------------
    100 -------------------------------- CXString -------------------------------
    101 -------------------------------------------------------------------------
    102 
    103 local CXString = ffi.typeof("CXString")
    104 
    105 -- Convert from a libclang's encapsulated CXString to a plain Lua string and
    106 -- dispose of the CXString afterwards.
    107 local function getString(cxstr)
    108     assert(ffi.istype(CXString, cxstr))
    109     local cstr = clang.clang_getCString(cxstr)
    110     assert(cstr ~= nil)
    111     local str = ffi.string(cstr)
    112     clang.clang_disposeString(cxstr)
    113     return str
    114 end
    115 
    116 -------------------------------------------------------------------------
    117 -------------------------- CompilationDatabase --------------------------
    118 -------------------------------------------------------------------------
    119 
    120 -- newargs = api.stripArgs(args, pattern, num)
    121 function api.stripArgs(args, pattern, num)
    122     assert(args[0] == nil)
    123     local numArgs = #args
    124 
    125     for i=1,numArgs do
    126         if (args[i] and args[i]:find(pattern)) then
    127             for j=0,num-1 do
    128                 args[i+j] = nil
    129             end
    130         end
    131     end
    132 
    133     local newargs = {}
    134     for i=1,numArgs do
    135         newargs[#newargs+1] = args[i]
    136     end
    137     return newargs
    138 end
    139 
    140 local CompDB_Error_Ar = ffi.typeof[[
    141 CXCompilationDatabase_Error [1]
    142 ]]
    143 
    144 local CompileCommand_mt = {
    145     __index = {
    146         -- args = cmd:getArgs([alsoCompilerExe])
    147         -- args: sequence table. If <alsoCompilerExe> is true, args[0] is the
    148         --  compiler executable.
    149         getArgs = function(self, alsoCompilerExe)
    150             local args = {}
    151             for i = (alsoCompilerExe and 0 or 1), self.numArgs-1 do
    152                 local cxstr = clang.clang_CompileCommand_getArg(self._ptr, i)
    153                 args[i] = getString(cxstr)
    154             end
    155 
    156             return args
    157         end,
    158 
    159         -- dir = cmd:getDirectory()
    160         getDirectory = function(self)
    161             local cxstr = clang.clang_CompileCommand_getDirectory(self._ptr)
    162             return getString(cxstr)
    163         end,
    164 
    165         -- paths = cmd:getSourcePaths()
    166         -- paths: sequence table.
    167         getSourcePaths = function(self)
    168             -- XXX: This is a workaround implementation due to a missing
    169             -- clang_CompileCommand_getMappedSourcePath symbol in libclang.so,
    170             -- see commented out code below.
    171             local args = self:getArgs()
    172             for i=1,#args do
    173                 if (args[i] == '-c' and args[i+1]) then
    174                     local sourceFile = args[i+1]
    175                     if (sourceFile:sub(1,1) ~= "/") then  -- XXX: Windows
    176                         sourceFile = self:getDirectory() .. "/" .. sourceFile
    177                     end
    178                     return { sourceFile }
    179                 end
    180             end
    181 
    182             print(table.concat(args, ' '))
    183             check(false, "Did not find -c option (workaround for missing "..
    184                       "clang_CompileCommand_getMappedSourcePath symbol)")
    185 --[[
    186             local paths = {}
    187             for i=0,self.numSources-1 do
    188                 -- XXX: for me, the symbol is missing in libclang.so:
    189                 local cxstr = clang.clang_CompileCommand_getMappedSourcePath(self._ptr, i)
    190                 paths[i] = getString(cxstr)
    191             end
    192             return paths
    193 --]]
    194         end,
    195     },
    196 }
    197 
    198 local CompileCommands_mt = {
    199     -- #commands: number of commands
    200     __len = function(self)
    201         return self.numCommands
    202     end,
    203 
    204     -- commands[i]: get the i'th CompileCommand object (i is 1-based)
    205     __index = function(self, i)
    206         check(type(i) == "number", "<i> must be a number", 2)
    207         check(i >= 1 and i <= self.numCommands, "<i> must be in [1, numCommands]", 2)
    208         local cmdPtr = clang.clang_CompileCommands_getCommand(self._ptr, i-1)
    209         local numArgs = clang.clang_CompileCommand_getNumArgs(cmdPtr)
    210         local numSources = 0 --clang.clang_CompileCommand_getNumMappedSources(cmdPtr)
    211         return CompileCommand_t(cmdPtr, numArgs, numSources)
    212     end,
    213 
    214     __gc = function(self)
    215         clang.clang_CompileCommands_dispose(self._ptr)
    216     end,
    217 }
    218 
    219 local CompilationDatabase_mt = {
    220     __index = {
    221         getCompileCommands = function(self, completeFileName)
    222             check(type(completeFileName) == "string", "<completeFileName> must be a string", 2)
    223             local cmdsPtr = clang.clang_CompilationDatabase_getCompileCommands(
    224                 self._ptr, completeFileName)
    225             local numCommands = clang.clang_CompileCommands_getSize(cmdsPtr)
    226             return CompileCommands_t(cmdsPtr, numCommands)
    227         end,
    228 
    229         getAllCompileCommands = function(self)
    230             local cmdsPtr = clang.clang_CompilationDatabase_getAllCompileCommands(self._ptr)
    231             local numCommands = clang.clang_CompileCommands_getSize(cmdsPtr)
    232             return CompileCommands_t(cmdsPtr, numCommands)
    233         end,
    234     },
    235 
    236     __gc = function(self)
    237         clang.clang_CompilationDatabase_dispose(self._ptr)
    238     end,
    239 }
    240 
    241 -- compDB = api.CompilationDatabase(buildDir)
    242 function api.CompilationDatabase(buildDir)
    243     check(type(buildDir) == "string", "<buildDir> must be a string", 2)
    244 
    245     local errAr = CompDB_Error_Ar()
    246     local ptr = clang.clang_CompilationDatabase_fromDirectory(buildDir, errAr)
    247     assert(ptr ~= nil or errAr[0] ~= 'CXCompilationDatabase_NoError')
    248 
    249     return ptr ~= nil and CompilationDatabase_t(ptr) or nil
    250 end
    251 
    252 -------------------------------------------------------------------------
    253 --------------------------------- Index ---------------------------------
    254 -------------------------------------------------------------------------
    255 
    256 -- Metatable for our Index_t.
    257 local Index_mt = {
    258     __index = {},
    259 
    260     __gc = function(self)
    261         -- "The index must not be destroyed until all of the translation units created
    262         --  within that index have been destroyed."
    263         for i=1,#self._tus do
    264             self._tus[i]:_cleanup()
    265         end
    266         clang.clang_disposeIndex(self._idx)
    267     end,
    268 }
    269 
    270 local function NewIndex(cxidx)
    271     assert(ffi.istype("CXIndex", cxidx))
    272     -- _tus is a list of the Index_t's TranslationUnit_t objects.
    273     local index = { _idx=cxidx, _tus={} }
    274     return setmetatable(index, Index_mt)
    275 end
    276 
    277 -------------------------------------------------------------------------
    278 ---------------------------- TranslationUnit ----------------------------
    279 -------------------------------------------------------------------------
    280 
    281 local function check_tu_valid(self)
    282     if (self._tu == nil) then
    283         error("Attempt to access freed TranslationUnit", 3)
    284     end
    285 end
    286 
    287 -- Construct a Cursor_t from a libclang's CXCursor <cxcur>. If <cxcur> is the
    288 -- NULL cursor, return nil.
    289 local function getCursor(cxcur)
    290     return (clang.clang_Cursor_isNull(cxcur) == 0) and Cursor_t(cxcur) or nil
    291 end
    292 
    293 -- Metatable for our TranslationUnit_t.
    294 local TranslationUnit_mt = {
    295     __index = {
    296         _cleanup = function(self)
    297             if (self._tu ~= nil) then
    298                 local addr = getCXTUaddr(self._tu)
    299                 TUCount[addr] = TUCount[addr]-1
    300                 if (TUCount[addr] == 0) then
    301                     clang.clang_disposeTranslationUnit(self._tu)
    302                     TUCount[addr] = nil
    303                 end
    304                 self._tu = nil
    305             end
    306         end,
    307 
    308         cursor = function(self)
    309             check_tu_valid(self)
    310             local cxcur = clang.clang_getTranslationUnitCursor(self._tu)
    311             return getCursor(cxcur)
    312         end,
    313 
    314         file = function(self, filename)
    315             check_tu_valid(self)
    316             if (type(filename) ~= "string") then
    317                 error("<filename> must be a string", 2)
    318             end
    319             local cxfile = clang.clang_getFile(self._tu, filename)
    320             return getString(clang.clang_getFileName(cxfile))  -- NYI: modification time
    321         end,
    322 
    323         diagnostics = function(self)
    324             check_tu_valid(self)
    325 
    326             local numdiags = clang.clang_getNumDiagnostics(self._tu)
    327             local tab = {}
    328 
    329             for i=0,numdiags-1 do
    330                 local diag = clang.clang_getDiagnostic(self._tu, i)
    331                 tab[i+1] = {
    332                     category = getString(clang.clang_getDiagnosticCategoryText(diag)),
    333                     text = getString(clang.clang_formatDiagnostic(
    334                                          diag, clang.clang_defaultDiagnosticDisplayOptions())),
    335                     severity = clang.clang_getDiagnosticSeverity(diag),
    336                 }
    337                 clang.clang_disposeDiagnostic(diag)
    338             end
    339 
    340             return tab
    341         end,
    342     },
    343 }
    344 
    345 TranslationUnit_mt.__gc = TranslationUnit_mt.__index._cleanup
    346 
    347 -------------------------------------------------------------------------
    348 --------------------------------- Cursor --------------------------------
    349 -------------------------------------------------------------------------
    350 
    351 local function getType(cxtyp)
    352     return cxtyp.kind ~= 'CXType_Invalid' and Type_t(cxtyp) or nil
    353 end
    354 
    355 local CXFileAr = ffi.typeof("CXFile [1]")
    356 
    357 local LineCol = ffi.typeof[[
    358 union {
    359     struct { unsigned line, col; };
    360     unsigned ar[2];
    361 }
    362 ]]
    363 
    364 local LineColOfs = ffi.typeof[[
    365 union {
    366     struct { unsigned line, col, offset; };
    367     unsigned ar[3];
    368 }
    369 ]]
    370 
    371 -- Get line number, column number and offset for a given CXSourceRange
    372 local function getLineColOfs(cxsrcrange, cxfile, clang_rangefunc)
    373     local cxsrcloc = clang[clang_rangefunc](cxsrcrange)
    374     local lco = LineColOfs()
    375     clang.clang_getSpellingLocation(cxsrcloc, cxfile, lco.ar, lco.ar+1, lco.ar+2)
    376     return lco
    377 end
    378 
    379 -- Returns a LineColOfs for the beginning and end of a range. Also, the file name.
    380 local function getBegEndFilename(cxsrcrange)
    381     local cxfilear = CXFileAr()
    382     local Beg = getLineColOfs(cxsrcrange, cxfilear, "clang_getRangeStart")
    383     local filename = getString(clang.clang_getFileName(cxfilear[0]))
    384     local End = getLineColOfs(cxsrcrange, cxfilear, "clang_getRangeEnd")
    385     return Beg, End, filename
    386 end
    387 
    388 local function getPresumedLineCol(cxsrcrange, clang_rangefunc)
    389     local cxsrcloc = clang[clang_rangefunc](cxsrcrange)
    390     local linecol = LineCol()
    391     local file = CXString()
    392     clang.clang_getPresumedLocation(cxsrcloc, file, linecol.ar, linecol.ar+1)
    393     return linecol, getString(file)
    394 end
    395 
    396 local function getSourceRange(cxcur)
    397     local cxsrcrange = clang.clang_getCursorExtent(cxcur)
    398     return (clang.clang_Range_isNull(cxsrcrange) == 0) and cxsrcrange or nil
    399 end
    400 
    401 -- Metatable for our Cursor_t.
    402 local Cursor_mt = {
    403     __eq = function(cur1, cur2)
    404         if (ffi.istype(Cursor_t, cur1) and ffi.istype(Cursor_t, cur2)) then
    405             return (clang.clang_equalCursors(cur1._cur, cur2._cur) ~= 0)
    406         else
    407             return false
    408         end
    409     end,
    410 
    411     __index = {
    412         parent = function(self)
    413             return getCursor(clang.clang_getCursorSemanticParent(self._cur))
    414         end,
    415 
    416         name = function(self)
    417             return getString(clang.clang_getCursorSpelling(self._cur))
    418         end,
    419 
    420         displayName = function(self)
    421             return getString(clang.clang_getCursorDisplayName(self._cur))
    422         end,
    423 
    424         kind = function(self)
    425             local kindnum = tonumber(self:kindnum())
    426             local kindstr = g_CursorKindName[kindnum]
    427             return kindstr or "Unknown"
    428         end,
    429 
    430         templateKind = function(self)
    431             local kindnum = tonumber(clang.clang_getTemplateCursorKind(self._cur))
    432             local kindstr = g_CursorKindName[kindnum]
    433             return kindstr or "Unknown"
    434         end,
    435 
    436         arguments = function(self)
    437             local tab = {}
    438             local numargs = clang.clang_Cursor_getNumArguments(self._cur)
    439             for i=1,numargs do
    440                 tab[i] = getCursor(clang.clang_Cursor_getArgument(self._cur, i-1))
    441             end
    442             return tab
    443         end,
    444 
    445         overloadedDecls = function(self)
    446             local tab = {}
    447             local numdecls = clang.clang_getNumOverloadedDecls(self._cur)
    448             for i=1,numdecls do
    449                 tab[i] = getCursor(clang.clang_getOverloadedDecl(self._cur, i-1))
    450             end
    451             return tab
    452         end,
    453 
    454         location = function(self, linesfirst)
    455             local cxsrcrange = getSourceRange(self._cur)
    456             if (cxsrcrange == nil) then
    457                 return nil
    458             end
    459 
    460             local Beg, End, filename = getBegEndFilename(cxsrcrange)
    461 
    462             if (linesfirst == 'offset') then
    463                 return filename, Beg.offset, End.offset
    464             elseif (linesfirst) then
    465                 -- LJClang order -- IMO you're usually more interested in the
    466                 -- line number
    467                 return filename, Beg.line, End.line, Beg.col, End.col, Beg.offset, End.offset
    468             else
    469                 -- luaclang-parser order (offset: XXX)
    470                 return filename, Beg.line, Beg.col, End.line, End.col, Beg.offset, End.offset
    471             end
    472         end,
    473 
    474         presumedLocation = function(self, linesfirst)
    475             local cxsrcrange = getSourceRange(self._cur)
    476             if (cxsrcrange == nil) then
    477                 return nil
    478             end
    479 
    480             local Beg, filename = getPresumedLineCol(cxsrcrange, "clang_getRangeStart")
    481             local End = getPresumedLineCol(cxsrcrange, "clang_getRangeEnd")
    482 
    483             if (linesfirst) then
    484                 return filename, Beg.line, End.line, Beg.col, End.col
    485             else
    486                 return filename, Beg.line, Beg.col, End.line, End.col
    487             end
    488         end,
    489 
    490         referenced = function(self)
    491             return getCursor(clang.clang_getCursorReferenced(self._cur))
    492         end,
    493 
    494         definition = function(self)
    495             return getCursor(clang.clang_getCursorDefinition(self._cur))
    496         end,
    497 
    498         isDeleted = function(self)
    499             return clang.clang_CXX_isDeleted(self._cur) ~= 0
    500         end,
    501 
    502         isMutable = function(self)
    503             return clang.clang_CXXField_isMutable(self._cur) ~= 0
    504         end,
    505 
    506         isDefaulted = function(self)
    507             return clang.clang_CXXMethod_isDefaulted(self._cur) ~= 0
    508         end,
    509 
    510         isPureVirtual = function(self)
    511             return clang.clang_CXXMethod_isPureVirtual(self._cur) ~= 0
    512         end,
    513 
    514         isVirtual = function(self)
    515             return clang.clang_CXXMethod_isVirtual(self._cur) ~= 0
    516         end,
    517 
    518         isOverride = function(self)
    519             return clang.clang_CXXMethod_isOverride(self._cur) ~= 0
    520         end,
    521 
    522         isStatic = function(self)
    523             return clang.clang_CXXMethod_isStatic(self._cur) ~= 0
    524         end,
    525 
    526         isConst = function(self)
    527             return clang.clang_CXXMethod_isConst(self._cur) ~= 0
    528         end,
    529 
    530         type = function(self)
    531             return getType(clang.clang_getCursorType(self._cur))
    532         end,
    533 
    534         resultType = function(self)
    535             return getType(clang.clang_getCursorResultType(self._cur))
    536         end,
    537 
    538         access = function(self)
    539             local spec = clang.clang_getCXXAccessSpecifier(self._cur);
    540 
    541             if (spec == 'CX_CXXPublic') then
    542                 return "public"
    543             elseif (spec == 'CX_CXXProtected') then
    544                 return "protected"
    545             elseif (spec == 'CX_CXXPrivate') then
    546                 return "private"
    547             else
    548                 assert(spec == 'CX_CXXInvalidAccessSpecifier')
    549                 return nil
    550             end
    551         end,
    552 
    553         --== LJClang-specific ==--
    554 
    555         translationUnit = function(self)
    556             return TranslationUnit_t(clang.clang_Cursor_getTranslationUnit(self._cur))
    557         end,
    558 
    559         -- XXX: Should be a TranslationUnit_t method instead.
    560         --
    561         -- NOTE: *Sometimes* returns one token too much, see
    562         --   http://clang-developers.42468.n3.nabble.com/querying-information-about-preprocessing-directives-in-libclang-td2740612.html
    563         -- Related bug report:
    564         --   http://llvm.org/bugs/show_bug.cgi?id=9069
    565         -- Also, see TOKENIZE_WORKAROUND in extractdecls.lua
    566         _tokens = function(self)
    567             local tu = self:translationUnit()
    568             local cxtu = tu._tu
    569 
    570             local _, b, e = self:location('offset')
    571             local cxsrcrange = getSourceRange(self._cur)
    572             if (cxsrcrange == nil) then
    573                 return nil
    574             end
    575 
    576             local ntoksar = ffi.new("unsigned [1]")
    577             local tokensar = ffi.new("CXToken *[1]")
    578             clang.clang_tokenize(cxtu, cxsrcrange, tokensar, ntoksar)
    579             local numtoks = ntoksar[0]
    580             local tokens = tokensar[0]
    581 
    582             local tab = {}
    583             local tabextra = {}
    584 
    585             local kinds = {
    586                 [tonumber(C.CXToken_Punctuation)] = 'Punctuation',
    587                 [tonumber(C.CXToken_Keyword)] = 'Keyword',
    588                 [tonumber(C.CXToken_Identifier)] = 'Identifier',
    589                 [tonumber(C.CXToken_Literal)] = 'Literal',
    590                 [tonumber(C.CXToken_Comment)] = 'Comment',
    591             }
    592 
    593             for i=0,numtoks-1 do
    594                 if (clang.clang_getTokenKind(tokens[i]) ~= 'CXToken_Comment') then
    595                     local sourcerange = clang.clang_getTokenExtent(cxtu, tokens[i])
    596                     local Beg, End, filename = getBegEndFilename(sourcerange)
    597                     local tb, te = Beg.offset, End.offset
    598 
    599                     if (tb >= b and te <= e) then
    600                         local kind = clang.clang_getTokenKind(tokens[i])
    601                         local extent = getString(clang.clang_getTokenSpelling(cxtu, tokens[i]))
    602                         tab[#tab+1] = extent
    603                         tabextra[#tabextra+1] = {
    604                             extent = extent,
    605                             kind = kinds[tonumber(kind)],
    606                             b = b, e = e,
    607                             tb = tb, te = te
    608                         }
    609                     end
    610                 end
    611             end
    612 
    613             clang.clang_disposeTokens(cxtu, tokens, numtoks)
    614 
    615             return tab, tabextra
    616         end,
    617 
    618         lexicalParent = function(self)
    619             return getCursor(clang.clang_getCursorLexicalParent(self._cur))
    620         end,
    621 
    622         baseTemplate = function(self)
    623             return getCursor(clang.clang_getSpecializedCursorTemplate(self._cur))
    624         end,
    625 
    626         -- Returns an enumeration constant, which in LuaJIT can be compared
    627         -- against a *string*, too.
    628         -- XXX: Should we split into 'kindenum' (giving the enum) and 'kindnum'?
    629         kindnum = function(self)
    630             return clang.clang_getCursorKind(self._cur)
    631         end,
    632 
    633         haskind = function(self, kind)
    634             if (type(kind) == "string") then
    635                 return self:kindnum() == "CXCursor_"..kind
    636             else
    637                 return self:kindnum() == kind
    638             end
    639         end,
    640 
    641         enumValue = function(self, unsigned)
    642             if (not self:haskind("EnumConstantDecl")) then
    643                 error("cursor must have kind EnumConstantDecl", 2)
    644             end
    645 
    646             if (unsigned) then
    647                 return clang.clang_getEnumConstantDeclUnsignedValue(self._cur)
    648             else
    649                 return clang.clang_getEnumConstantDeclValue(self._cur)
    650             end
    651         end,
    652 
    653         enumval = function(self, unsigned)
    654             return tonumber(self:enumValue(unsigned))
    655         end,
    656 
    657         isDefinition = function(self)
    658             return (clang.clang_isCursorDefinition(self._cur) ~= 0)
    659         end,
    660 --[=[
    661         --| tab = cur:argtypes([alsoret])
    662         argtypes = function(self, alsoret)
    663             if (clang.clang_Cursor_getNumArguments(self._cur) == -1) then
    664                 return nil
    665             end
    666 
    667             local tab = self:arguments()
    668 
    669             if (alsoret) then
    670                 tab[0] = self:resultType()
    671             end
    672 
    673             for i=1,#tab do
    674                 tab[i] = tab[i]:type()
    675             end
    676         end,
    677 --]=]
    678         typedefType = function(self)
    679             return getType(clang.clang_getTypedefDeclUnderlyingType(self._cur))
    680         end,
    681     },
    682 }
    683 
    684 Cursor_mt.__tostring = Cursor_mt.__index.name
    685 
    686 -------------------------------------------------------------------------
    687 ---------------------------------- Type ---------------------------------
    688 -------------------------------------------------------------------------
    689 
    690 -- Metatable for our Type_t.
    691 local Type_mt = {
    692     __eq = function(typ1, typ2)
    693         if (ffi.istype(Type_t, typ1) and ffi.istype(Type_t, typ2)) then
    694             return (clang.clang_equalTypes(typ1._typ, typ2._typ) ~= 0)
    695         else
    696             return false
    697         end
    698     end,
    699 
    700     __index = {
    701         name = function(self)
    702             return getString(clang.clang_getTypeSpelling(self._typ))
    703         end,
    704 
    705         canonical = function(self)
    706             -- NOTE: no dispatching to getPointeeType() for pointer types like
    707             -- luaclang-parser.
    708             return getType(clang.clang_getCanonicalType(self._typ))
    709         end,
    710 
    711         pointee = function(self)
    712             return getType(clang.clang_getPointeeType(self._typ))
    713         end,
    714 
    715         resultType = function(self)
    716             return getType(clang.clang_getResultType(self._typ))
    717         end,
    718 
    719         arrayElementType = function(self)
    720             return getType(clang.clang_getArrayElementType(self._typ))
    721         end,
    722 
    723         arraySize = function(self)
    724             return tonumber(clang.clang_getArraySize(self._typ))
    725         end,
    726 
    727         isConst = function(self)
    728             return (clang.clang_isConstQualifiedType(self._typ) ~= 0);
    729         end,
    730 
    731         isPod = function(self)
    732             return (clang.clang_isPODType(self._typ) ~= 0);
    733         end,
    734 
    735         isFinal = function(self)
    736             return (clang.clang_isFinalType(self._typ) ~= 0);
    737         end,
    738 
    739         isAbstract = function(self)
    740             return (clang.clang_isAbstractType(self._typ) ~= 0);
    741         end,
    742 
    743         isNoexcept = function(self)
    744             return (clang.clang_isNoexcept(self._typ) ~= 0);
    745         end,
    746 
    747         declaration = function(self)
    748             return getCursor(clang.clang_getTypeDeclaration(self._typ))
    749         end,
    750 
    751         --== LJClang-specific ==--
    752         -- Returns an enumeration constant.
    753         kindnum = function(self)
    754             return self._typ.kind
    755         end,
    756 
    757         haskind = function(self, kind)
    758             if (type(kind) == "string") then
    759                 return self:kindnum() == "CXType_"..kind
    760             else
    761                 return self:kindnum() == kind
    762             end
    763         end,
    764 
    765         templateArguments = function(self)
    766             local tab = {}
    767             local numargs = clang.clang_Type_getNumTemplateArguments(self._typ)
    768             for i=1,numargs do
    769                 tab[i] = getType(clang.clang_Type_getTemplateArgumentAsType(self._typ, i-1))
    770             end
    771             return tab
    772         end,
    773     },
    774 }
    775 
    776 -- Aliases
    777 local Type__index = Type_mt.__index
    778 Type__index.isConstQualified = Type__index.isConst
    779 
    780 Type_mt.__tostring = Type__index.name
    781 
    782 -------------------------------------------------------------------------
    783 
    784 function api.clangVersion()
    785   return getString(clang.clang_getClangVersion())
    786 end
    787 
    788 --| index = clang.createIndex([excludeDeclarationsFromPCH [, displayDiagnostics]])
    789 function api.createIndex(excludeDeclarationsFromPCH, displayDiagnostics)
    790     local cxidx = clang.clang_createIndex(excludeDeclarationsFromPCH or false,
    791                                           displayDiagnostics or false)
    792     if (cxidx == nil) then
    793         return nil
    794     end
    795 
    796     return NewIndex(cxidx)
    797 end
    798 
    799 -- argstab = clang.splitAtWhitespace(args)
    800 function api.splitAtWhitespace(args)
    801     assert(type(args) == "string")
    802     local argstab = {}
    803     -- Split delimited by whitespace.
    804     for str in args:gmatch("[^%s]+") do
    805         argstab[#argstab+1] = str
    806     end
    807     return argstab
    808 end
    809 
    810 -- Is <tab> a sequence of strings?
    811 local function iscellstr(tab)
    812     for i=1,#tab do
    813         if (type(tab[i]) ~= "string") then
    814             return false
    815         end
    816     end
    817 
    818     -- We require this because in ARGS_FROM_TAB below, an index 0 would be
    819     -- interpreted as the starting index.
    820     return (tab[0] == nil)
    821 end
    822 
    823 local function check_iftab_iscellstr(tab, name)
    824     if (type(tab)=="table") then
    825         if (not iscellstr(tab)) then
    826             error(name.." must be a string sequence when a table with no element at [0]", 3)
    827         end
    828     end
    829 end
    830 
    831 --| tunit = index:parse(srcfile, args [, opts])
    832 --|
    833 --| <args>: string or sequence of strings
    834 --| <opts>: number or sequence of strings (CXTranslationUnit_* enum members,
    835 --|  without the prefix)
    836 function Index_mt.__index.parse(self, srcfile, args, opts)
    837     check(type(srcfile)=="string", "<srcfile> must be a string", 2)
    838     check(type(args)=="string" or type(args)=="table", "<args> must be a string or table", 2)
    839     check_iftab_iscellstr(args, "<args>")
    840 
    841     if (srcfile == "") then
    842         srcfile = nil
    843     end
    844 
    845     if (opts == nil) then
    846         opts = 0
    847     else
    848         check(type(opts)=="number" or type(opts)=="table", 2)
    849         check_iftab_iscellstr(args, "<opts>")
    850     end
    851 
    852     -- Input argument handling.
    853 
    854     if (type(args)=="string") then
    855         args = api.splitAtWhitespace(args)
    856     end
    857 
    858     if (type(opts)=="table") then
    859         local optflags = {}
    860         for i=1,#opts do
    861             optflags[i] = clang["CXTranslationUnit_"..opts[i]]  -- look up the enum
    862         end
    863         opts = bit.bor(unpack(optflags))
    864     end
    865 
    866     local argsptrs = ffi.new("const char * [?]", #args, args)  -- ARGS_FROM_TAB
    867 
    868     -- Create the CXTranslationUnit.
    869     local tunitptr = clang.clang_parseTranslationUnit(
    870         self._idx, srcfile, argsptrs, #args, nil, 0, opts)
    871 
    872     if (tunitptr == nil) then
    873         return nil
    874     end
    875 
    876     -- Wrap it in a TranslationUnit_t.
    877     local tunit = TranslationUnit_t(tunitptr)
    878 
    879     -- Add this TranslationUnit_t to the list of its Index_t's TUs.
    880     self._tus[#self._tus+1] = tunit
    881 
    882     return tunit
    883 end
    884 
    885 
    886 -- enum CXChildVisitResult constants
    887 api.ChildVisitResult = ffi.new[[struct{
    888     static const int Break = 0;
    889     static const int Continue = 1;
    890     static const int Recurse = 2;
    891 }]]
    892 
    893 function api.regCursorVisitor(visitorfunc)
    894     check(type(visitorfunc)=="function", "<visitorfunc> must be a Lua function", 2)
    895 
    896     local ret = support.ljclang_regCursorVisitor(visitorfunc, nil, 0)
    897     if (ret < 0) then
    898         error("failed registering visitor function, code "..ret, 2)
    899     end
    900 
    901     return ret
    902 end
    903 
    904 local Cursor_ptr_t = ffi.typeof("$ *", Cursor_t)
    905 
    906 function api.Cursor(cur)
    907     check(ffi.istype(Cursor_ptr_t, cur), "<cur> must be a cursor as passed to the visitor callback", 2)
    908     return Cursor_t(cur[0])
    909 end
    910 
    911 -- Support for legacy luaclang-parser API collecting direct descendants of a
    912 -- cursor: this will be the table where they go.
    913 local collectTab
    914 
    915 local function collectDirectChildren(cur)
    916     debugf("collectDirectChildren: %s, child cursor kind: %s", tostring(collectTab), cur:kind())
    917     collectTab[#collectTab+1] = Cursor_t(cur[0])
    918     return 1  -- Continue
    919 end
    920 
    921 local cdc_visitoridx = api.regCursorVisitor(collectDirectChildren)
    922 
    923 function Cursor_mt.__index.children(self, visitoridx)
    924     if (visitoridx ~= nil) then
    925         -- LJClang way of visiting
    926         local ret = support.ljclang_visitChildren(self._cur, visitoridx)
    927         return (ret ~= 0)
    928     else
    929         -- luaclang-parser way
    930         if (collectTab ~= nil) then
    931             error("children() must not be called while another invocation is active", 2)
    932             collectTab = nil
    933         end
    934 
    935         collectTab = {}
    936         -- XXX: We'll be blocked if the visitor callback errors.
    937         support.ljclang_visitChildren(self._cur, cdc_visitoridx)
    938         local tab = collectTab
    939         collectTab = nil
    940         return tab
    941     end
    942 end
    943 
    944 
    945 -- Register the metatables for the custom ctypes.
    946 ffi.metatype(Index_t, Index_mt)
    947 ffi.metatype(TranslationUnit_t_, TranslationUnit_mt)
    948 ffi.metatype(Cursor_t, Cursor_mt)
    949 ffi.metatype(Type_t, Type_mt)
    950 
    951 ffi.metatype(CompileCommand_t, CompileCommand_mt)
    952 ffi.metatype(CompileCommands_t, CompileCommands_mt)
    953 ffi.metatype(CompilationDatabase_t, CompilationDatabase_mt)
    954 
    955 api.Index_t = Index_t
    956 api.TranslationUnit_t = TranslationUnit_t_
    957 api.Cursor_t = Cursor_t
    958 api.Type_t = Type_t
    959 
    960 -- Done!
    961 return api