mirror of
https://github.com/pound-emu/ballistic.git
synced 2026-10-24 23:45:05 -07:00
174 lines
5.0 KiB
Lua
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
|