feat(autogen): scan for props
This commit is contained in:
parent
99a8cbe957
commit
febde86430
7 changed files with 279 additions and 78 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -17,8 +17,8 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug Autogen",
|
"name": "Debug Autogen",
|
||||||
"program": "${workspaceFolder}/autogen/build/autogen",
|
"program": "${workspaceFolder}/autogen/build/autogen",
|
||||||
"args": ["core/src", "core/src/objects", "autogen/build/generated"],
|
"args": ["../core/src", "../core/src/objects", "build/generated"],
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}/autogen",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ add_executable(autogen
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/util.cpp
|
src/util.cpp
|
||||||
src/cache.cpp
|
src/cache.cpp
|
||||||
|
src/analysis.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen")
|
set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen")
|
||||||
|
|
218
autogen/src/analysis.cpp
Normal file
218
autogen/src/analysis.cpp
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
#include "analysis.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <clang-c/Index.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
|
||||||
|
// Very simple parser
|
||||||
|
// Example format:
|
||||||
|
// name="Hello!", world=Test, read_only
|
||||||
|
// Result:
|
||||||
|
// "name": "Hello!", "world": "Test", "read_only": ""
|
||||||
|
std::map<std::string, std::string> parseAnnotationString(std::string src) {
|
||||||
|
std::map<std::string, std::string> result;
|
||||||
|
|
||||||
|
std::string currentIdent = "";
|
||||||
|
std::string currentValue = "";
|
||||||
|
int stage = 0;
|
||||||
|
bool quoted = false;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (; i < src.length(); i++) {
|
||||||
|
if (src[i] == ' ' && (stage != 2 || !quoted)) continue; // Ignore spaces if not in stage 2 and quoted
|
||||||
|
if (stage < 2 && (src[i] >= 'a' && src[i] <= 'z' || src[i] >= 'A' && src[i] <= 'Z' || src[i] >= '0' && src[i] <= '9' || src[i] == '_')) {
|
||||||
|
currentIdent += src[i];
|
||||||
|
stage = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (stage == 1 && src[i] == '=') { // What follows is a value
|
||||||
|
stage = 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (stage == 1 && src[i] == ',') { // Value-less key
|
||||||
|
stage = 0;
|
||||||
|
result[currentIdent] = "";
|
||||||
|
currentIdent = "";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (stage == 2 && quoted && src[i] == '"') { // Close a quoted string
|
||||||
|
quoted = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (stage == 2 && !quoted && src[i] == '"') { // Start a quoted string
|
||||||
|
quoted = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (stage == 2 && !quoted && (src[i] == ' ' || src[i] == ',')) { // Terminate the string
|
||||||
|
stage = 0;
|
||||||
|
result[currentIdent] = currentValue;
|
||||||
|
currentIdent = "";
|
||||||
|
currentValue = "";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (stage == 2) { // Otherwise if in stage 2, simply add the character
|
||||||
|
currentValue += src[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Unexpected symbol: %c at index %d\n", src[i], i);
|
||||||
|
fprintf(stderr, "\t%s\n", src.c_str());
|
||||||
|
fprintf(stderr, "\t%s^\n", i > 0 ? std::string(i, '~').c_str() : "");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the remaining value
|
||||||
|
if (stage == 1) {
|
||||||
|
result[currentIdent] = "";
|
||||||
|
} else if (stage == 2) {
|
||||||
|
result[currentIdent] = currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool findInstanceAnnotation(CXCursor cur) {
|
||||||
|
bool found = false;
|
||||||
|
// Look for annotations in the class itself
|
||||||
|
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||||
|
CXCursorKind kind = clang_getCursorKind(cur);
|
||||||
|
if (kind != CXCursor_AnnotateAttr) return CXChildVisit_Continue;
|
||||||
|
|
||||||
|
std::string annString = x_clang_toString(clang_getCursorDisplayName(cur));
|
||||||
|
if (annString == "OB::INSTANCE") found = true;
|
||||||
|
|
||||||
|
return CXChildVisit_Break;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName) {
|
||||||
|
std::optional<std::string> ret = std::nullopt;
|
||||||
|
|
||||||
|
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||||
|
CXCursorKind kind = clang_getCursorKind(cur);
|
||||||
|
if (kind != CXCursor_AnnotateAttr) return CXChildVisit_Continue;
|
||||||
|
|
||||||
|
std::string annString = x_clang_toString(clang_getCursorDisplayName(cur));
|
||||||
|
if (annString != annotationName) return CXChildVisit_Continue;
|
||||||
|
|
||||||
|
// Look inside for a StringLiteral
|
||||||
|
|
||||||
|
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||||
|
CXCursorKind kind = clang_getCursorKind(cur);
|
||||||
|
// if (kind != CXCursor_StringLiteral) return CXChildVisit_Recurse;
|
||||||
|
// String literals cannot be parsed as CXCursor_StringLiteral. I don't know why.
|
||||||
|
if (kind != CXCursor_UnexposedExpr) return CXChildVisit_Recurse;
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/63859988/16255372
|
||||||
|
auto res = clang_Cursor_Evaluate(cur);
|
||||||
|
ret = clang_EvalResult_getAsStr(res);
|
||||||
|
clang_EvalResult_dispose(res);
|
||||||
|
|
||||||
|
return CXChildVisit_Break;
|
||||||
|
});
|
||||||
|
|
||||||
|
return CXChildVisit_Break;
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string findBaseClass(CXCursor cur) {
|
||||||
|
std::string baseClass = "";
|
||||||
|
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||||
|
CXCursorKind kind = clang_getCursorKind(cur);
|
||||||
|
if (kind != CXCursor_CXXBaseSpecifier) return CXChildVisit_Continue;
|
||||||
|
|
||||||
|
baseClass = x_clang_toString(clang_getCursorDisplayName(cur));
|
||||||
|
|
||||||
|
return CXChildVisit_Break;
|
||||||
|
});
|
||||||
|
return baseClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processField(CXCursor cur, ClassAnalysis* state) {
|
||||||
|
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_prop");
|
||||||
|
if (!propertyDef) return;
|
||||||
|
|
||||||
|
PropertyAnalysis anly;
|
||||||
|
|
||||||
|
auto result = parseAnnotationString(propertyDef.value());
|
||||||
|
std::string fieldName = x_clang_toString(clang_getCursorDisplayName(cur));
|
||||||
|
|
||||||
|
anly.name = result["name"];
|
||||||
|
anly.fieldName = fieldName;
|
||||||
|
|
||||||
|
state->properties.push_back(anly);
|
||||||
|
}
|
||||||
|
|
||||||
|
void processClass(CXCursor cur, AnalysisState* state, std::string className) {
|
||||||
|
ClassAnalysis anly;
|
||||||
|
|
||||||
|
// Find base class
|
||||||
|
std::string baseClass = findBaseClass(cur);
|
||||||
|
|
||||||
|
anly.name = className;
|
||||||
|
anly.baseClass = baseClass;
|
||||||
|
|
||||||
|
// Find annotated fields
|
||||||
|
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||||
|
CXCursorKind kind = clang_getCursorKind(cur);
|
||||||
|
if (kind != CXCursor_FieldDecl) return CXChildVisit_Continue;
|
||||||
|
|
||||||
|
processField(cur, &anly);
|
||||||
|
|
||||||
|
return CXChildVisit_Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
state->classes[className] = anly;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://clang.llvm.org/docs/LibClang.html
|
||||||
|
bool analyzeClasses(std::string path, std::string srcRoot, AnalysisState* state) {
|
||||||
|
const char* cargs[] = { "-x", "c++", "-I", srcRoot.c_str(), 0 };
|
||||||
|
// THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
|
||||||
|
// https://stackoverflow.com/a/59206378/16255372
|
||||||
|
CXIndex index = clang_createIndex(0, 0);
|
||||||
|
CXTranslationUnit unit = clang_parseTranslationUnit(
|
||||||
|
index,
|
||||||
|
path.c_str(), cargs, 4,
|
||||||
|
nullptr, 0,
|
||||||
|
CXTranslationUnit_None);
|
||||||
|
|
||||||
|
if (!unit) {
|
||||||
|
fprintf(stderr, "Failed to parse file\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print errors
|
||||||
|
int ndiags = clang_getNumDiagnostics(unit);
|
||||||
|
for (int i = 0; i < ndiags; i++) {
|
||||||
|
CXDiagnostic diag = clang_getDiagnostic(unit, i);
|
||||||
|
CXString str = clang_formatDiagnostic(diag, 0);
|
||||||
|
fprintf(stderr, "diag: %s\n", clang_getCString(str));
|
||||||
|
|
||||||
|
clang_disposeString(str);
|
||||||
|
clang_disposeDiagnostic(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
CXCursor cursor = clang_getTranslationUnitCursor(unit);
|
||||||
|
|
||||||
|
bool flag = false;
|
||||||
|
// Search for classes
|
||||||
|
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
|
||||||
|
CXCursorKind kind = clang_getCursorKind(cur);
|
||||||
|
if (kind != CXCursor_ClassDecl) return CXChildVisit_Continue;
|
||||||
|
|
||||||
|
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 (!findInstanceAnnotation(cur)) 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);
|
||||||
|
|
||||||
|
return CXChildVisit_Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
23
autogen/src/analysis.h
Normal file
23
autogen/src/analysis.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct PropertyAnalysis {
|
||||||
|
std::string name;
|
||||||
|
std::string fieldName;
|
||||||
|
std::string backingFieldType;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClassAnalysis {
|
||||||
|
std::string name;
|
||||||
|
std::string baseClass;
|
||||||
|
std::vector<PropertyAnalysis> properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnalysisState {
|
||||||
|
std::map<std::string, ClassAnalysis> classes;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool analyzeClasses(std::string path, std::string srcRoot, AnalysisState* state);
|
|
@ -4,21 +4,14 @@
|
||||||
#include <clang-c/CXString.h>
|
#include <clang-c/CXString.h>
|
||||||
#include <clang-c/Index.h>
|
#include <clang-c/Index.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <iostream>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include "analysis.h"
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
bool analyzeFirstPass(std::string path, std::string srcRoot);
|
|
||||||
|
|
||||||
std::map<std::string, std::vector<std::string>> SUBCLASSES;
|
|
||||||
std::map<std::string, std::string> SUPERCLASS;
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
if (argc < 4) {
|
if (argc < 4) {
|
||||||
fprintf(stderr, "Usage: autogen <src-root> <parse-dir> <out-dir>\n");
|
fprintf(stderr, "Usage: autogen <src-root> <parse-dir> <out-dir>\n");
|
||||||
|
@ -32,7 +25,7 @@ int main(int argc, char** argv) {
|
||||||
// Scrape directory for header files
|
// Scrape directory for header files
|
||||||
for (const fs::directory_entry& file : fs::recursive_directory_iterator(argv[2])) {
|
for (const fs::directory_entry& file : fs::recursive_directory_iterator(argv[2])) {
|
||||||
if (!file.is_regular_file()) continue; // Not a file, skip
|
if (!file.is_regular_file()) continue; // Not a file, skip
|
||||||
if (file.path().extension() != ".cpp") continue; // Not a header file, skip
|
if (file.path().extension() != ".h") continue; // Not a header file, skip
|
||||||
if (!hasFileBeenUpdated(file.path())) {
|
if (!hasFileBeenUpdated(file.path())) {
|
||||||
fs::path relpath = fs::relative(file.path(), argv[1]);
|
fs::path relpath = fs::relative(file.path(), argv[1]);
|
||||||
printf("[AUTOGEN] Skipping file %s...\n", relpath.c_str());
|
printf("[AUTOGEN] Skipping file %s...\n", relpath.c_str());
|
||||||
|
@ -43,73 +36,28 @@ int main(int argc, char** argv) {
|
||||||
headerFiles.push_back(file.path());
|
headerFiles.push_back(file.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
// First-pass: Analyze type hierarchy
|
AnalysisState state;
|
||||||
for (std::string path : headerFiles) {
|
|
||||||
fs::path relpath = fs::relative(path, argv[1]);
|
analyzeClasses("../core/src/objects/part.h", argv[1], &state);
|
||||||
printf("[AUTOGEN] [Stage 1] Analyzing file %s...\n", relpath.c_str());
|
|
||||||
if (!analyzeFirstPass(path, argv[1]))
|
for (auto& [_, clazz] : state.classes) {
|
||||||
return 1;
|
printf("Class: %s\n", clazz.name.c_str());
|
||||||
|
if (clazz.baseClass != "")
|
||||||
|
printf("==> Base class: %s\n", clazz.baseClass.c_str());
|
||||||
|
|
||||||
|
if (!clazz.properties.empty())
|
||||||
|
printf("==> Properties:\n");
|
||||||
|
for (auto prop : clazz.properties) {
|
||||||
|
printf("====> %s (%s)\n", prop.name.c_str(), prop.fieldName.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First-pass: Analyze type hierarchy
|
||||||
|
// for (std::string path : headerFiles) {
|
||||||
|
// fs::path relpath = fs::relative(path, argv[1]);
|
||||||
|
// printf("[AUTOGEN] Processing file %s...\n", relpath.c_str());
|
||||||
|
// analyzeClasses(path, argv[1], &state);
|
||||||
|
// }
|
||||||
|
|
||||||
flushCaches(argv[3]);
|
flushCaches(argv[3]);
|
||||||
}
|
|
||||||
|
|
||||||
// https://clang.llvm.org/docs/LibClang.html
|
|
||||||
bool analyzeFirstPass(std::string path, std::string srcRoot) {
|
|
||||||
const char* cargs[] = { "-x", "c++", "-I", srcRoot.c_str(), 0 };
|
|
||||||
// THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
|
|
||||||
// https://stackoverflow.com/a/59206378/16255372
|
|
||||||
CXIndex index = clang_createIndex(0, 0);
|
|
||||||
CXTranslationUnit unit = clang_parseTranslationUnit(
|
|
||||||
index,
|
|
||||||
path.c_str(), cargs, 4,
|
|
||||||
nullptr, 0,
|
|
||||||
CXTranslationUnit_None);
|
|
||||||
|
|
||||||
if (!unit) {
|
|
||||||
fprintf(stderr, "Failed to parse file\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print errors
|
|
||||||
int ndiags = clang_getNumDiagnostics(unit);
|
|
||||||
for (int i = 0; i < ndiags; i++) {
|
|
||||||
CXDiagnostic diag = clang_getDiagnostic(unit, i);
|
|
||||||
CXString str = clang_formatDiagnostic(diag, 0);
|
|
||||||
fprintf(stderr, "diag: %s\n", clang_getCString(str));
|
|
||||||
|
|
||||||
clang_disposeString(str);
|
|
||||||
clang_disposeDiagnostic(diag);
|
|
||||||
}
|
|
||||||
|
|
||||||
CXCursor cursor = clang_getTranslationUnitCursor(unit);
|
|
||||||
|
|
||||||
// Search for classes
|
|
||||||
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
|
|
||||||
CXCursorKind kind = clang_getCursorKind(cur);
|
|
||||||
if (kind != CXCursor_ClassDecl) return CXChildVisit_Continue;
|
|
||||||
|
|
||||||
std::string className = x_clang_toString(clang_getCursorDisplayName(cur));
|
|
||||||
|
|
||||||
std::string baseClass = "";
|
|
||||||
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
|
||||||
CXCursorKind kind = clang_getCursorKind(cur);
|
|
||||||
if (kind != CXCursor_CXXBaseSpecifier) return CXChildVisit_Continue;
|
|
||||||
|
|
||||||
baseClass = x_clang_toString(clang_getCursorDisplayName(cur));
|
|
||||||
return CXChildVisit_Break;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (baseClass != "") {
|
|
||||||
SUPERCLASS[className] = baseClass;
|
|
||||||
|
|
||||||
std::vector<std::string> subclasses = SUBCLASSES[baseClass];
|
|
||||||
subclasses.push_back(className);
|
|
||||||
SUBCLASSES[baseClass] = subclasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CXChildVisit_Continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
7
core/src/objects/annotation.h
Normal file
7
core/src/objects/annotation.h
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Markers for the autogen engine to generate getters, setters, lua, etc.
|
||||||
|
|
||||||
|
#define INSTANCE [[clang::annotate("OB::INSTANCE")]]
|
||||||
|
|
||||||
|
#define def_prop(...) clang::annotate("OB::def_prop", #__VA_ARGS__)
|
|
@ -11,6 +11,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <reactphysics3d/reactphysics3d.h>
|
#include <reactphysics3d/reactphysics3d.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "annotation.h"
|
||||||
|
|
||||||
namespace rp = reactphysics3d;
|
namespace rp = reactphysics3d;
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ struct PartConstructParams {
|
||||||
|
|
||||||
class Snap;
|
class Snap;
|
||||||
|
|
||||||
class Part : public Instance {
|
class INSTANCE Part : public Instance {
|
||||||
protected:
|
protected:
|
||||||
// Joints where this part is Part0
|
// Joints where this part is Part0
|
||||||
std::vector<std::weak_ptr<JointInstance>> primaryJoints;
|
std::vector<std::weak_ptr<JointInstance>> primaryJoints;
|
||||||
|
@ -51,8 +52,11 @@ public:
|
||||||
|
|
||||||
Vector3 velocity;
|
Vector3 velocity;
|
||||||
CFrame cframe;
|
CFrame cframe;
|
||||||
|
[[ def_prop(name="Size") ]]
|
||||||
glm::vec3 size;
|
glm::vec3 size;
|
||||||
|
[[ def_prop(name="Color") ]]
|
||||||
Color3 color;
|
Color3 color;
|
||||||
|
[[ def_prop(name="Transparency") ]]
|
||||||
float transparency = 0.f;
|
float transparency = 0.f;
|
||||||
bool selected = false;
|
bool selected = false;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue