openblocks/autogen/src/data/analysis.cpp

233 lines
No EOL
7.8 KiB
C++

#include "analysis.h"
#include "../util.h"
#include <cctype>
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/Index.h>
#include <cstdio>
#include <optional>
using namespace data;
static std::string toStaticName(std::string orig) {
bool isSnakeCase = orig.find('_') == -1;
std::string newName = "";
int wordStart = 0;
for (char c : orig) {
if (c == '_') {
wordStart = 1;
continue;
}
if (wordStart == 1)
newName += std::toupper(c);
else if (wordStart == 0)
newName += std::tolower(c);
else if (wordStart == 2)
newName += c;
if (c >= 'a' && c <= 'z')
wordStart = 2;
else
wordStart = 0;
}
newName[0] = std::tolower(newName[0]);
return newName;
}
// Constructors are stored the same way as static functions, but with the name "new"
static void processConstructor(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_data_ctor");
if (!propertyDef) return;
MethodAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
CXType retType = clang_getCursorResultType(cur);
anly.name = result["name"];
anly.functionName = "__ctor";
anly.returnType = state->name;
// if name field is not provided, use new
if (anly.name == "") {
anly.name = "new";
}
// Populate parameter list
// https://stackoverflow.com/a/45867090/16255372
for (int i = 0; i < clang_Cursor_getNumArguments(cur); i++) {
CXCursor paramCur = clang_Cursor_getArgument(cur, i);
std::string paramName = x_clang_toString(clang_getCursorDisplayName(paramCur));
std::string paramType = x_clang_toString(clang_getTypeSpelling(clang_getCursorType(paramCur)));
MethodParameter param;
param.name = paramName;
param.type = paramType;
anly.parameters.push_back(param);
}
state->staticMethods.push_back(anly);
}
static void processMethod(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_data_method");
if (!propertyDef) return;
MethodAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
CXType retType = clang_getCursorResultType(cur);
bool isStatic = clang_CXXMethod_isStatic(cur);
anly.name = result["name"];
anly.functionName = symbolName;
anly.returnType = x_clang_toString(clang_getTypeSpelling(retType));
// if name field is not provided, use fieldName instead, but capitalize the first character
if (anly.name == "") {
anly.name = symbolName;
if (!isStatic)
anly.name[0] = std::toupper(anly.name[0]);
else
anly.name[0] = std::tolower(anly.name[0]);
}
// Populate parameter list
// https://stackoverflow.com/a/45867090/16255372
for (int i = 0; i < clang_Cursor_getNumArguments(cur); i++) {
CXCursor paramCur = clang_Cursor_getArgument(cur, i);
std::string paramName = x_clang_toString(clang_getCursorDisplayName(paramCur));
std::string paramType = x_clang_toString(clang_getTypeSpelling(clang_getCursorType(paramCur)));
MethodParameter param;
param.name = paramName;
param.type = paramType;
anly.parameters.push_back(param);
}
// If it's a static method, push it into the library instead
if (clang_CXXMethod_isStatic(cur))
state->staticMethods.push_back(anly);
else
state->methods.push_back(anly);
}
// This processes both methods and fields
static void processProperty(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_data_prop");
if (!propertyDef) return;
PropertyAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
CXType retType = clang_getCursorResultType(cur);
bool isStatic = clang_getCursorKind(cur) == CXCursor_VarDecl || clang_CXXMethod_isStatic(cur);
anly.name = result["name"];
anly.backingSymbol = symbolName;
anly.valueType = x_clang_toString(clang_getTypeSpelling(retType));
anly.backingType = clang_getCursorKind(cur) == CXCursor_CXXMethod ? PropertyBackingType::Method : PropertyBackingType::Field;
// if name field is not provided, use fieldName instead, but capitalize the first character
if (anly.name == "") {
anly.name = symbolName;
if (!isStatic)
anly.name[0] = std::toupper(anly.name[0]);
else
anly.name = toStaticName(anly.name);
}
// If it's a static property, push it into the library instead
if (isStatic)
state->staticProperties.push_back(anly);
else
state->properties.push_back(anly);
}
static bool hasMethod(CXCursor cur, std::string methodName) {
bool found = false;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_CXXMethod) return CXChildVisit_Continue;
if (x_clang_toString(clang_getCursorSpelling(cur)) == methodName) {
found = true;
return CXChildVisit_Break;
}
return CXChildVisit_Continue;
});
return found;
}
static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
ClassAnalysis anly;
std::string propertyDef = findAnnotation(cur, "OB::def_data").value();
auto result = parseAnnotationString(propertyDef);
anly.name = className;
anly.serializedName = result["name"];
anly.hasFromString = hasMethod(cur, "FromString");
anly.isSerializable = hasMethod(cur, "Serialize") && hasMethod(cur, "Deserialize");
if (anly.serializedName == "")
anly.serializedName = className;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind == CXCursor_Constructor) {
processConstructor(cur, &anly);
}
if (kind == CXCursor_CXXMethod || kind == CXCursor_FieldDecl || kind == CXCursor_VarDecl) {
processProperty(cur, &anly);
}
if (kind == CXCursor_CXXMethod) {
processMethod(cur, &anly);
}
return CXChildVisit_Continue;
});
state->classes[className] = anly;
}
bool data::analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state) {
// Search for classes
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind == CXCursor_Namespace) return CXChildVisit_Recurse;
if (kind != CXCursor_ClassDecl) return CXChildVisit_Continue;
CXSourceLocation loc = clang_getCursorLocation(cur);
if (!clang_Location_isFromMainFile(loc)) return CXChildVisit_Continue; // This class is not from this header. Skip
std::string className = x_clang_toString(clang_getCursorDisplayName(cur));
// Forward-decls can slip through the cracks, this prevents that, but also allows us to filter non-instance classes in the src/objects directory
if (!findAnnotation(cur, "OB::def_data")) return CXChildVisit_Continue; // Class is not "primary" declaration/is not instance, skip
if (state->classes.count(className) > 0) return CXChildVisit_Continue; // Class has already been analyzed, skip...
processClass(cur, state, className, srcRoot);
return CXChildVisit_Continue;
});
return true;
}