Mock up of the software. Fully generated with Gemini 3.8 as a proof of concept.
This commit is contained in:
1 parent
7bc854e6b0
commit
ea9d88ce3c
84 files changed
+115526
No files matched your search
@@ -0,0 +1,276 @@
|
||||
#include "clang_inspector.hpp"
|
||||
#include "../core/log.hpp"
|
||||
#include <chrono>
|
||||
|
||||
namespace ci {
|
||||
|
||||
static std::string cx_to_string(CXString str) {
|
||||
const char* cstr = clang_getCString(str);
|
||||
std::string s = cstr ? cstr : "";
|
||||
clang_disposeString(str);
|
||||
return s;
|
||||
}
|
||||
|
||||
ClangInspector::ClangInspector() {
|
||||
index_ = clang_createIndex(0, 0);
|
||||
if (!index_) {
|
||||
Logger::get().error("Failed to create Clang Index (libclang).");
|
||||
} else {
|
||||
Logger::get().info("libclang initialized successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
ClangInspector::~ClangInspector() {
|
||||
if (index_) {
|
||||
clang_disposeIndex(index_);
|
||||
index_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ClangInspector::clear() {
|
||||
functions_.clear();
|
||||
ast_roots_.clear();
|
||||
diagnostics_.clear();
|
||||
has_errors_ = false;
|
||||
}
|
||||
|
||||
void ClangInspector::build_ast_node(CXCursor cursor, ASTNode& node, CXTranslationUnit tu) {
|
||||
node.kind = clang_getCursorKind(cursor);
|
||||
node.kind_name = cx_to_string(clang_getCursorKindSpelling(node.kind));
|
||||
node.name = cx_to_string(clang_getCursorSpelling(cursor));
|
||||
node.type_name = cx_to_string(clang_getTypeSpelling(clang_getCursorType(cursor)));
|
||||
|
||||
CXSourceRange range = clang_getCursorExtent(cursor);
|
||||
unsigned line = 0, col = 0, end_line = 0, end_col = 0;
|
||||
clang_getSpellingLocation(clang_getRangeStart(range), nullptr, &line, &col, nullptr);
|
||||
clang_getSpellingLocation(clang_getRangeEnd(range), nullptr, &end_line, &end_col, nullptr);
|
||||
|
||||
node.line = (int)line;
|
||||
node.column = (int)col;
|
||||
node.end_line = (int)end_line;
|
||||
node.end_column = (int)end_col;
|
||||
|
||||
// Token inspection for literals and binary operators
|
||||
if (tu && (node.kind == CXCursor_IntegerLiteral || node.kind == CXCursor_FloatingLiteral || node.kind == CXCursor_BinaryOperator)) {
|
||||
CXToken* tokens = nullptr;
|
||||
unsigned num_tokens = 0;
|
||||
clang_tokenize(tu, range, &tokens, &num_tokens);
|
||||
if (tokens && num_tokens > 0) {
|
||||
if (node.kind == CXCursor_IntegerLiteral) {
|
||||
std::string tok = cx_to_string(clang_getTokenSpelling(tu, tokens[0]));
|
||||
try {
|
||||
node.int_val = std::stoll(tok, nullptr, 0);
|
||||
if (node.name.empty()) node.name = tok;
|
||||
} catch (...) {}
|
||||
} else if (node.kind == CXCursor_FloatingLiteral) {
|
||||
std::string tok = cx_to_string(clang_getTokenSpelling(tu, tokens[0]));
|
||||
try {
|
||||
node.float_val = std::stod(tok);
|
||||
if (node.name.empty()) node.name = tok;
|
||||
} catch (...) {}
|
||||
} else if (node.kind == CXCursor_BinaryOperator && node.name.empty()) {
|
||||
for (unsigned t = 0; t < num_tokens; ++t) {
|
||||
std::string tok = cx_to_string(clang_getTokenSpelling(tu, tokens[t]));
|
||||
if (tok == "==" || tok == "!=" || tok == "<=" || tok == "<" ||
|
||||
tok == ">=" || tok == ">" || tok == "+" || tok == "-" ||
|
||||
tok == "*" || tok == "/" || tok == "%" || tok == "=" ||
|
||||
tok == "+=" || tok == "-=" || tok == "*=" || tok == "/=" ||
|
||||
tok == "&&" || tok == "||") {
|
||||
node.name = tok;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
clang_disposeTokens(tu, tokens, num_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
struct RecurseContext {
|
||||
ClangInspector* self;
|
||||
ASTNode* parent;
|
||||
CXTranslationUnit tu;
|
||||
} ctx = { this, &node, tu };
|
||||
|
||||
clang_visitChildren(cursor, [](CXCursor child, CXCursor parent, CXClientData data) -> CXChildVisitResult {
|
||||
RecurseContext* c = static_cast<RecurseContext*>(data);
|
||||
CXCursorKind ck = clang_getCursorKind(child);
|
||||
if (ck != CXCursor_NoDeclFound && ck != CXCursor_InvalidFile) {
|
||||
ASTNode child_node;
|
||||
c->self->build_ast_node(child, child_node, c->tu);
|
||||
c->parent->children.push_back(child_node);
|
||||
}
|
||||
return CXChildVisit_Continue;
|
||||
}, &ctx);
|
||||
}
|
||||
|
||||
bool ClangInspector::inspect_code(const std::string& filename, const std::string& code_str) {
|
||||
if (!index_) {
|
||||
index_ = clang_createIndex(0, 0);
|
||||
if (!index_) return false;
|
||||
}
|
||||
|
||||
clear();
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
CXUnsavedFile unsaved;
|
||||
unsaved.Filename = filename.c_str();
|
||||
unsaved.Contents = code_str.c_str();
|
||||
unsaved.Length = (unsigned long)code_str.size();
|
||||
|
||||
const char* args[] = {
|
||||
"-std=c11",
|
||||
"-I.",
|
||||
"-Wno-everything"
|
||||
};
|
||||
|
||||
CXTranslationUnit tu = clang_parseTranslationUnit(
|
||||
index_,
|
||||
filename.c_str(),
|
||||
args, sizeof(args) / sizeof(args[0]),
|
||||
&unsaved, 1,
|
||||
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_KeepGoing
|
||||
);
|
||||
|
||||
if (!tu) {
|
||||
Logger::get().error("libclang failed to parse translation unit: " + filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Diagnostics extraction
|
||||
unsigned num_diags = clang_getNumDiagnostics(tu);
|
||||
for (unsigned i = 0; i < num_diags; ++i) {
|
||||
CXDiagnostic diag = clang_getDiagnostic(tu, i);
|
||||
CXDiagnosticSeverity sev = clang_getDiagnosticSeverity(diag);
|
||||
|
||||
CXString diag_spelling = clang_formatDiagnostic(diag, clang_defaultDiagnosticDisplayOptions());
|
||||
std::string msg = cx_to_string(diag_spelling);
|
||||
|
||||
CXSourceLocation loc = clang_getDiagnosticLocation(diag);
|
||||
unsigned line = 0, col = 0;
|
||||
clang_getSpellingLocation(loc, nullptr, &line, &col, nullptr);
|
||||
|
||||
DiagnosticItem item;
|
||||
item.message = msg;
|
||||
item.line = (int)line;
|
||||
item.column = (int)col;
|
||||
item.filename = filename;
|
||||
|
||||
if (sev >= CXDiagnostic_Error) {
|
||||
item.level = LogLevel::Error;
|
||||
has_errors_ = true;
|
||||
} else if (sev == CXDiagnostic_Warning) {
|
||||
item.level = LogLevel::Warning;
|
||||
} else {
|
||||
item.level = LogLevel::Info;
|
||||
}
|
||||
|
||||
diagnostics_.push_back(item);
|
||||
clang_disposeDiagnostic(diag);
|
||||
}
|
||||
|
||||
// 2. Visit Top-Level AST
|
||||
CXCursor root = clang_getTranslationUnitCursor(tu);
|
||||
|
||||
struct VisitorContext {
|
||||
ClangInspector* self;
|
||||
std::string filename;
|
||||
CXTranslationUnit tu;
|
||||
} ctx = { this, filename, tu };
|
||||
|
||||
clang_visitChildren(root, [](CXCursor cursor, CXCursor parent, CXClientData client_data) -> CXChildVisitResult {
|
||||
VisitorContext* c = static_cast<VisitorContext*>(client_data);
|
||||
CXCursorKind kind = clang_getCursorKind(cursor);
|
||||
|
||||
// Only look at declarations in main input file
|
||||
CXSourceLocation loc = clang_getCursorLocation(cursor);
|
||||
CXFile file = nullptr;
|
||||
unsigned line = 0, col = 0;
|
||||
clang_getSpellingLocation(loc, &file, &line, &col, nullptr);
|
||||
|
||||
if (file != nullptr) {
|
||||
std::string file_str = cx_to_string(clang_getFileName(file));
|
||||
if (!file_str.empty() && file_str != c->filename) {
|
||||
return CXChildVisit_Continue; // skip headers
|
||||
}
|
||||
}
|
||||
|
||||
// Add to AST roots
|
||||
ASTNode root_node;
|
||||
c->self->build_ast_node(cursor, root_node, c->tu);
|
||||
c->self->ast_roots_.push_back(root_node);
|
||||
|
||||
// Extract Functions
|
||||
if (kind == CXCursor_FunctionDecl) {
|
||||
std::string func_name = cx_to_string(clang_getCursorSpelling(cursor));
|
||||
CXType ret_type = clang_getCursorResultType(cursor);
|
||||
std::string ret_name = cx_to_string(clang_getTypeSpelling(ret_type));
|
||||
|
||||
FunctionDef def;
|
||||
def.name = func_name;
|
||||
def.return_type = ret_name.empty() ? "void" : ret_name;
|
||||
def.line_start = (int)line;
|
||||
|
||||
CXSourceRange range = clang_getCursorExtent(cursor);
|
||||
unsigned end_l = 0;
|
||||
clang_getSpellingLocation(clang_getRangeEnd(range), nullptr, &end_l, nullptr, nullptr);
|
||||
def.line_end = (int)end_l;
|
||||
|
||||
// Extract Parameters
|
||||
int num_args = clang_Cursor_getNumArguments(cursor);
|
||||
for (int a = 0; a < num_args; ++a) {
|
||||
CXCursor arg_cursor = clang_Cursor_getArgument(cursor, a);
|
||||
std::string arg_name = cx_to_string(clang_getCursorSpelling(arg_cursor));
|
||||
CXType arg_type = clang_getCursorType(arg_cursor);
|
||||
std::string arg_type_name = cx_to_string(clang_getTypeSpelling(arg_type));
|
||||
|
||||
FunctionParam param;
|
||||
param.name = arg_name.empty() ? ("arg" + std::to_string(a)) : arg_name;
|
||||
param.type_name = arg_type_name;
|
||||
|
||||
if (arg_type_name.find("float") != std::string::npos) {
|
||||
param.type = ParamType::Float;
|
||||
} else if (arg_type_name.find("double") != std::string::npos) {
|
||||
param.type = ParamType::Double;
|
||||
} else if (arg_type_name.find("char*") != std::string::npos || arg_type_name.find("const char*") != std::string::npos) {
|
||||
param.type = ParamType::String;
|
||||
} else {
|
||||
param.type = ParamType::Int;
|
||||
if (func_name == "factorial") {
|
||||
param.int_val = 5;
|
||||
param.int_min = 0;
|
||||
param.int_max = 20;
|
||||
}
|
||||
}
|
||||
|
||||
def.params.push_back(param);
|
||||
}
|
||||
|
||||
// Build full recursive body AST for this function
|
||||
c->self->build_ast_node(cursor, def.body_ast, c->tu);
|
||||
c->self->functions_.push_back(def);
|
||||
}
|
||||
|
||||
return CXChildVisit_Continue;
|
||||
}, &ctx);
|
||||
|
||||
clang_disposeTranslationUnit(tu);
|
||||
|
||||
auto t1 = std::chrono::high_resolution_clock::now();
|
||||
parse_time_ms_ = (int)std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << "libclang AST inspected in " << parse_time_ms_ << "ms ("
|
||||
<< functions_.size() << " functions, "
|
||||
<< ast_roots_.size() << " AST declarations, "
|
||||
<< diagnostics_.size() << " diagnostics)";
|
||||
|
||||
if (has_errors_) {
|
||||
Logger::get().warn(ss.str());
|
||||
} else {
|
||||
Logger::get().success(ss.str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ci
|
||||
Reference in new issue
Block a user