Files

174 lines
5.0 KiB
Lua

local ffi = require("ffi")
local log = require("log")
local ast = require("ast")
local documentation = require("documentation")
local clang = require("clang")
local M = {}
local function parse_file_level_docs(header_path)
local file = io.open(header_path, "r")
if not file then
log.error("parser: cannot open '%s' for file-level docs", header_path)
return nil
end
local lines = {}
for line in file:lines() do
local stripped = line:match("^%s*(.-)%s*$")
if stripped and stripped:sub(1, 3) == "//!" then
local content = stripped:sub(4)
if content:sub(1, 1) == " " then
content = content:sub(2)
end
table.insert(lines, content)
end
end
file:close()
if #lines == 0 then
return nil
end
local raw = table.concat(lines, "\n")
local documentation_table = documentation.parse(raw)
return documentation_table
end
--- Dispatch table for parse functions for different types
--- TODO: implement parser function for each type
local DECL_HANDLERS = {
[clang.CURSOR_KIND.FUNCTION_DECL] = function(ctx, cursor, loc) end,
[clang.CURSOR_KIND.STRUCT_DECL] = function(ctx, cursor, loc) end,
[clang.CURSOR_KIND.UNION_DECL] = function(ctx, cursor, loc) end,
[clang.CURSOR_KIND.ENUM_DECL] = function(ctx, cursor, loc) end,
[clang.CURSOR_KIND.TYPEDEF_DECL] = function(ctx, cursor, loc) end,
}
--- Visitor function that will be called from `clang.visit_children` to traverse the parsed file
local function visit_node(ctx, cursor)
-- Skip nodes that are not from the main file (e.g. included headers, system headers)
if not clang.is_from_main_file(ctx.clang_context, cursor) then
return clang.CHILD_VISIT.CONTINUE
end
local kind = clang.cursor_kind(ctx.clang_context, cursor)
local handler = DECL_HANDLERS[kind]
if handler then
local loc = clang.cursor_location(ctx.clang_context, cursor, ctx.header_path)
handler(ctx, cursor, loc)
return clang.CHILD_VISIT.CONTINUE
end
-- Recurse into unexposed decls
return clang.CHILD_VISIT.RECURSE
end
function M.register_symbol(registry, name, file, anchor, kind, extra)
registry.symbols[name] = {
name = name,
file = file,
anchor = anchor,
kind = kind,
extra = extra,
}
end
function M.find_symbol(registry, name)
return registry.symbols[name]
end
function M.create_project()
return {
modules = {},
registry = {
symbols = {},
},
}
end
function M.parse_header(clang_context, header_path, clang_args, project_registry)
log.info("Parsing header %s.", header_path)
local file = io.open(header_path, "r")
if not file then
log.error("Aborting function: Failed to open header file %s.", header_path)
return
end
if not clang_context or not clang_context.library then
log.error("Aborting function: libclang not initialized.")
return
end
local args = clang_args or {}
local argc = #args
local argv = nil
if argc > 0 then
argv = ffi.new("const char*[?]", argc)
for i = 1, argc do
argv[i - 1] = args[i]
end
end
local clang_library = clang_context.library
local own_index = false
local index = clang_library.clang_createIndex(0, 0)
local unsaved_files = nil
local num_unsaved_files = 0
local options = 0x40 -- CXTranslationUnit_SkipFunctionBodies
local ok, translation_unit = pcall(
clang_library.clang_parseTranslationUnit,
index, header_path, argv, argc, unsaved_files, num_unsaved_files, options
)
if not ok then
log.error("Aborting function: failed to parse translation unit because %s.", tostring(translation_unit))
return nil
end
if translation_unit == nil then
clang_library.clang_disposeIndex(index)
log.error("Aborting function: failed to create parse translation unit %s.", header_path)
return
end
log.debug("Translation unit created successfully for %s.", header_path)
local file_level_documentation = parse_file_level_docs(header_path)
local module_name = header_path:match("([^/\\]+)$") or header_path
local module_node = ast.create_module(module_name, header_path, file_level_documentation)
local root_cursor = clang_library.clang_getTranslationUnitCursor(translation_unit)
local ctx = {
clang_context = clang_context,
header_path = header_path,
module_name = module_name,
module_node = module_node,
project_registry = project_registry,
seen_names = {},
}
clang.visit_children(clang_context, root_cursor, function(cursor, parent)
return visit_node(ctx, cursor)
end)
clang_library.clang_disposeTranslationUnit(translation_unit)
clang_library.clang_disposeIndex(index)
log.info("Finished parsing %s (%d items).", header_path, #module_node.items)
return module_node
end
return M