470 lines
16 KiB
C++
470 lines
16 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");
|
|
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;
|
|
}
|