MetaCore/tools/MetaCoreHeaderTool/main.cpp

217 lines
6.8 KiB
C++

#include <cctype>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
namespace {
enum class MetaCoreReflectedKind {
Struct,
Class,
Enum
};
struct MetaCoreReflectedField {
std::string Name{};
};
struct MetaCoreReflectedType {
MetaCoreReflectedKind Kind = MetaCoreReflectedKind::Struct;
std::string Name{};
std::vector<MetaCoreReflectedField> Fields{};
};
[[nodiscard]] std::string MetaCoreTrim(std::string value) {
auto isWhitespace = [](unsigned char character) {
return std::isspace(character) != 0;
};
while (!value.empty() && isWhitespace(static_cast<unsigned char>(value.front()))) {
value.erase(value.begin());
}
while (!value.empty() && isWhitespace(static_cast<unsigned char>(value.back()))) {
value.pop_back();
}
return value;
}
[[nodiscard]] std::string MetaCoreBuildIncludePath(const std::filesystem::path& path) {
const std::string full = path.generic_string();
constexpr std::string_view marker = "/Public/";
const std::size_t markerIndex = full.find(marker);
if (markerIndex == std::string::npos) {
return path.filename().generic_string();
}
return full.substr(markerIndex + marker.size());
}
[[nodiscard]] std::vector<MetaCoreReflectedType> MetaCoreParseHeader(const std::filesystem::path& path) {
std::ifstream input(path);
if (!input.is_open()) {
throw std::runtime_error("Unable to open header: " + path.string());
}
const std::regex typeRegex(R"(^(struct|class|enum\s+class)\s+([A-Za-z_]\w*))");
const std::regex fieldRegex(R"(^(.+?)\s+([A-Za-z_]\w*)\s*(?:\{.*\}|=.*)?;$)");
std::vector<MetaCoreReflectedType> reflectedTypes;
std::optional<MetaCoreReflectedKind> pendingKind;
MetaCoreReflectedType* currentType = nullptr;
bool expectingField = false;
std::string line;
while (std::getline(input, line)) {
const std::string trimmed = MetaCoreTrim(line);
if (trimmed.empty() || trimmed.starts_with("//")) {
continue;
}
if (trimmed.starts_with("MC_STRUCT")) {
pendingKind = MetaCoreReflectedKind::Struct;
continue;
}
if (trimmed.starts_with("MC_CLASS")) {
pendingKind = MetaCoreReflectedKind::Class;
continue;
}
if (trimmed.starts_with("MC_ENUM")) {
pendingKind = MetaCoreReflectedKind::Enum;
continue;
}
if (trimmed.starts_with("MC_PROPERTY")) {
expectingField = true;
continue;
}
if (trimmed.starts_with("MC_GENERATED_BODY")) {
continue;
}
if (pendingKind.has_value()) {
std::smatch match;
if (std::regex_search(trimmed, match, typeRegex)) {
reflectedTypes.push_back(MetaCoreReflectedType{
pendingKind.value(),
match[2].str(),
{}
});
currentType = &reflectedTypes.back();
pendingKind.reset();
expectingField = false;
continue;
}
}
if (currentType != nullptr && trimmed == "};") {
currentType = nullptr;
expectingField = false;
continue;
}
if (currentType == nullptr || currentType->Kind == MetaCoreReflectedKind::Enum || !expectingField) {
continue;
}
std::smatch match;
if (!std::regex_search(trimmed, match, fieldRegex)) {
throw std::runtime_error("Unable to parse reflected field in " + path.string() + ": " + trimmed);
}
currentType->Fields.push_back(MetaCoreReflectedField{
match[2].str()
});
expectingField = false;
}
return reflectedTypes;
}
[[nodiscard]] std::string MetaCoreBuildOutput(
const std::string& functionName,
const std::vector<std::filesystem::path>& headers
) {
std::vector<MetaCoreReflectedType> reflectedTypes;
for (const auto& header : headers) {
const auto parsed = MetaCoreParseHeader(header);
reflectedTypes.insert(reflectedTypes.end(), parsed.begin(), parsed.end());
}
std::ostringstream output;
output << "#include \"MetaCoreFoundation/MetaCoreGeneratedReflection.h\"\n";
output << "#include \"MetaCoreFoundation/MetaCoreReflection.h\"\n";
for (const auto& header : headers) {
output << "#include \"" << MetaCoreBuildIncludePath(header) << "\"\n";
}
output << "\nnamespace MetaCore {\n\n";
output << "void " << functionName << "(MetaCoreTypeRegistry& registry) {\n";
for (const auto& reflectedType : reflectedTypes) {
if (reflectedType.Kind == MetaCoreReflectedKind::Enum) {
output << " MetaCoreRegisterGeneratedEnum<" << reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
continue;
}
output << " auto " << reflectedType.Name << "Builder = MetaCoreRegisterGeneratedStruct<"
<< reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
for (const auto& field : reflectedType.Fields) {
output << " " << reflectedType.Name << "Builder.Field<&" << reflectedType.Name << "::"
<< field.Name << ">(\"" << field.Name << "\");\n";
}
}
output << "}\n\n} // namespace MetaCore\n";
return output.str();
}
} // namespace
int main(int argc, char** argv) {
std::filesystem::path outputPath;
std::string functionName;
std::vector<std::filesystem::path> headers;
for (int index = 1; index < argc; ++index) {
const std::string_view argument(argv[index]);
if (argument == "--output" && index + 1 < argc) {
outputPath = argv[++index];
continue;
}
if (argument == "--function" && index + 1 < argc) {
functionName = argv[++index];
continue;
}
if (argument == "--module" && index + 1 < argc) {
++index;
continue;
}
headers.emplace_back(argv[index]);
}
if (outputPath.empty() || functionName.empty() || headers.empty()) {
std::cerr << "Usage: MetaCoreHeaderTool --output <file> --function <name> [--module <name>] <headers...>\n";
return 1;
}
try {
const std::string output = MetaCoreBuildOutput(functionName, headers);
std::ofstream file(outputPath, std::ios::trunc);
if (!file.is_open()) {
std::cerr << "Unable to open output file: " << outputPath << '\n';
return 1;
}
file << output;
} catch (const std::exception& exception) {
std::cerr << exception.what() << '\n';
return 1;
}
return 0;
}