MetaCore/tools/MetaCoreHeaderTool/main.cpp

472 lines
17 KiB
C++

#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace {
enum class MetaCoreReflectedKind {
Struct,
Class,
Enum
};
struct MetaCoreReflectedField {
std::string Name{};
std::string PropertySpec{};
};
struct MetaCoreReflectedEnumValue {
std::string Name{};
};
struct MetaCoreReflectedType {
MetaCoreReflectedKind Kind = MetaCoreReflectedKind::Struct;
std::string Name{};
std::vector<MetaCoreReflectedField> Fields{};
std::vector<MetaCoreReflectedEnumValue> EnumValues{};
};
[[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::string MetaCoreExtractMacroArgument(std::string_view trimmed, std::string_view macroName) {
const std::size_t prefixLength = macroName.size();
if (trimmed.size() <= prefixLength || trimmed[prefixLength] != '(') {
return {};
}
const std::size_t closeIndex = trimmed.rfind(')');
if (closeIndex == std::string_view::npos || closeIndex <= prefixLength) {
return {};
}
return MetaCoreTrim(std::string(trimmed.substr(prefixLength + 1, closeIndex - prefixLength - 1)));
}
[[nodiscard]] std::string MetaCoreEscapeCppString(std::string_view value) {
std::string escaped;
escaped.reserve(value.size());
for (const char character : value) {
switch (character) {
case '\\': escaped += "\\\\"; break;
case '"': escaped += "\\\""; break;
case '\n': escaped += "\\n"; break;
case '\r': escaped += "\\r"; break;
case '\t': escaped += "\\t"; break;
default: escaped.push_back(character); break;
}
}
return escaped;
}
[[nodiscard]] std::string MetaCoreToLower(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char character) {
return static_cast<char>(std::tolower(character));
});
return value;
}
[[nodiscard]] std::string MetaCoreStripQuotes(std::string value) {
value = MetaCoreTrim(std::move(value));
if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
return value.substr(1, value.size() - 2);
}
return value;
}
[[nodiscard]] bool MetaCoreIsTruthyMetadataValue(std::string value) {
value = MetaCoreToLower(MetaCoreTrim(std::move(value)));
return value.empty() || value == "true" || value == "1" || value == "yes";
}
[[nodiscard]] std::size_t MetaCoreFindUnquotedEquals(std::string_view value) {
bool inString = false;
bool escaping = false;
for (std::size_t index = 0; index < value.size(); ++index) {
const char character = value[index];
if (escaping) {
escaping = false;
continue;
}
if (character == '\\') {
escaping = inString;
continue;
}
if (character == '"') {
inString = !inString;
continue;
}
if (!inString && character == '=') {
return index;
}
}
return std::string_view::npos;
}
[[nodiscard]] std::vector<std::string> MetaCoreSplitPropertySpec(std::string_view spec) {
std::vector<std::string> tokens;
std::string token;
bool inString = false;
bool escaping = false;
int nestedDepth = 0;
for (const char character : spec) {
if (escaping) {
token.push_back(character);
escaping = false;
continue;
}
if (character == '\\') {
token.push_back(character);
escaping = inString;
continue;
}
if (character == '"') {
token.push_back(character);
inString = !inString;
continue;
}
if (!inString && (character == '(' || character == '[' || character == '{')) {
++nestedDepth;
} else if (!inString && (character == ')' || character == ']' || character == '}')) {
--nestedDepth;
}
if (!inString && nestedDepth == 0 && character == ',') {
tokens.push_back(MetaCoreTrim(token));
token.clear();
continue;
}
token.push_back(character);
}
if (!MetaCoreTrim(token).empty()) {
tokens.push_back(MetaCoreTrim(token));
}
return tokens;
}
[[nodiscard]] std::unordered_map<std::string, std::string> MetaCoreParsePropertySpec(std::string_view spec) {
std::unordered_map<std::string, std::string> metadata;
for (const std::string& token : MetaCoreSplitPropertySpec(spec)) {
const std::size_t equalsIndex = MetaCoreFindUnquotedEquals(token);
if (equalsIndex == std::string_view::npos) {
metadata.emplace(MetaCoreTrim(token), "true");
continue;
}
metadata.insert_or_assign(
MetaCoreTrim(token.substr(0, equalsIndex)),
MetaCoreTrim(token.substr(equalsIndex + 1))
);
}
return metadata;
}
[[nodiscard]] std::optional<MetaCoreReflectedEnumValue> MetaCoreParseEnumValue(std::string value) {
const std::size_t commentIndex = value.find("//");
if (commentIndex != std::string::npos) {
value = value.substr(0, commentIndex);
}
value = MetaCoreTrim(std::move(value));
if (value.empty() || value == "{" || value == "};") {
return std::nullopt;
}
if (!value.empty() && value.back() == ',') {
value.pop_back();
}
const std::size_t equalsIndex = MetaCoreFindUnquotedEquals(value);
if (equalsIndex != std::string::npos) {
value = value.substr(0, equalsIndex);
}
value = MetaCoreTrim(std::move(value));
if (value.empty()) {
return std::nullopt;
}
if (!std::regex_match(value, std::regex(R"([A-Za-z_]\w*)"))) {
return std::nullopt;
}
return MetaCoreReflectedEnumValue{value};
}
void MetaCoreEmitStringMetadataAssignment(
std::ostringstream& output,
const std::unordered_map<std::string, std::string>& metadata,
std::string_view metadataVariable,
std::string_view propertyName,
std::string_view fieldName
) {
const auto iterator = metadata.find(std::string(propertyName));
if (iterator == metadata.end()) {
return;
}
output << " " << metadataVariable << "." << fieldName << " = \""
<< MetaCoreEscapeCppString(MetaCoreStripQuotes(iterator->second)) << "\";\n";
}
void MetaCoreEmitOptionalDoubleMetadataAssignment(
std::ostringstream& output,
const std::unordered_map<std::string, std::string>& metadata,
std::string_view metadataVariable,
std::string_view propertyName,
std::string_view fieldName
) {
const auto iterator = metadata.find(std::string(propertyName));
if (iterator == metadata.end()) {
return;
}
output << " " << metadataVariable << "." << fieldName << " = "
<< MetaCoreStripQuotes(iterator->second) << ";\n";
}
void MetaCoreEmitBoolMetadataAssignment(
std::ostringstream& output,
const std::unordered_map<std::string, std::string>& metadata,
std::string_view metadataVariable,
std::string_view propertyName,
std::string_view fieldName
) {
const auto iterator = metadata.find(std::string(propertyName));
if (iterator == metadata.end()) {
return;
}
output << " " << metadataVariable << "." << fieldName << " = "
<< (MetaCoreIsTruthyMetadataValue(iterator->second) ? "true" : "false") << ";\n";
}
[[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 pendingPropertySpec;
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;
pendingPropertySpec = MetaCoreExtractMacroArgument(trimmed, "MC_PROPERTY");
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) {
if (auto enumValue = MetaCoreParseEnumValue(trimmed); enumValue.has_value()) {
currentType->EnumValues.push_back(std::move(*enumValue));
}
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(),
pendingPropertySpec
});
expectingField = false;
pendingPropertySpec.clear();
}
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 << " auto " << reflectedType.Name << "Builder = MetaCoreRegisterGeneratedEnum<"
<< reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
for (const auto& enumValue : reflectedType.EnumValues) {
output << " " << reflectedType.Name << "Builder.Value(\""
<< MetaCoreEscapeCppString(enumValue.Name) << "\", "
<< reflectedType.Name << "::" << enumValue.Name << ");\n";
}
continue;
}
output << " auto " << reflectedType.Name << "Builder = MetaCoreRegisterGeneratedStruct<"
<< reflectedType.Name << ">(registry, \"" << reflectedType.Name << "\");\n";
for (const auto& field : reflectedType.Fields) {
if (!field.PropertySpec.empty()) {
const std::string metadataVariable =
reflectedType.Name + field.Name + "EditorMetadata";
const auto metadata = MetaCoreParsePropertySpec(field.PropertySpec);
output << " MetaCoreFieldEditorMetadata " << metadataVariable << "{};\n";
output << " " << metadataVariable << ".RawSpec = \""
<< MetaCoreEscapeCppString(field.PropertySpec) << "\";\n";
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "DisplayName", "DisplayName");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Group", "Group");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Tooltip", "Tooltip");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "ResourceType", "ResourceType");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Resource", "ResourceType");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "DefaultValue", "DefaultValue");
MetaCoreEmitStringMetadataAssignment(output, metadata, metadataVariable, "Default", "DefaultValue");
MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Min", "Min");
MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Max", "Max");
MetaCoreEmitOptionalDoubleMetadataAssignment(output, metadata, metadataVariable, "Step", "Step");
MetaCoreEmitBoolMetadataAssignment(output, metadata, metadataVariable, "ReadOnly", "ReadOnly");
MetaCoreEmitBoolMetadataAssignment(output, metadata, metadataVariable, "Hidden", "Hidden");
MetaCoreEmitBoolMetadataAssignment(output, metadata, metadataVariable, "ResourceReference", "ResourceReference");
output << " " << reflectedType.Name << "Builder.Field<&" << reflectedType.Name << "::"
<< field.Name << ">(\"" << field.Name << "\", " << metadataVariable << ");\n";
continue;
}
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;
}