// Copyright (c) 2023 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #include "SceneItemComponentRegistry.hpp" using namespace Dawn; struct SceneItemComponentRuleset SceneItemComponentRegistry::parseFile( File *file, std::string clazz ) { assertNotNull(file, "SceneItemCOmponentRegistry::parseFile: File is null"); std::string data; if(!file->readString(&data)) { std::cout << "Failed to read ruleset file!" << std::endl; return { .name = "" }; } // Begin scanning contentsr // std::string include = this->sources; auto toRemove = File::normalizeSlashes(this->sources + FILE_PATH_SEP); auto includePath = file->filename.substr(toRemove.size(), file->filename.size() - toRemove.size()); // Now locate the first subdir since we don't want to include the root path (e.g. dawn, dawnrose, etc) auto firstSlash = includePath.find(FILE_PATH_SEP); if(firstSlash != std::string::npos) { includePath = includePath.substr(firstSlash + 1, includePath.size() - firstSlash - 1); } struct SceneItemComponentRuleset ruleset; // Replace all file seps with slashes size_t pos = 0; while ((pos = includePath.find(FILE_PATH_SEP, pos)) != std::string::npos) { includePath.replace(pos, 1, "/"); pos += 1; } ruleset.include = includePath; ruleset.name = clazz; std::regex_constants::syntax_option_type regexFlags; #if defined(__GNUC__) regexFlags = std::regex_constants::ECMAScript | std::regex_constants::multiline; #else regexFlags = std::regex_constants::ECMAScript; #endif // First, let's look for what class(es) this class extends std::regex regexClassName("class\\s+\\w+\\s+:\\s+", regexFlags); std::smatch match; if(std::regex_search(data, match, regexClassName)) { // Now find the next "{" auto matchStart = match.position() + match.length(); auto openBracePos = data.find("{", matchStart); if(openBracePos == std::string::npos) { std::cout << "Failed to find open brace for class!" << std::endl; return { .name = "" }; } // Match each of the class names std::regex regexClass("(public\\s+(\\w+))[,\\s{]+", regexFlags); std::sregex_iterator itClass(data.begin() + matchStart, data.begin() + openBracePos, regexClass); std::sregex_iterator endClass; while(itClass != endClass) { // Get the class name auto className = itClass->str(2); itClass++; // We don't parse interface classes if(className[0] == 'I') continue; ruleset.extends.push_back(className); } } // Find each instance of "@optional" when it's used within a comment // e.g. // @optional or /* @optional */ in the string data.1 std::regex regex("^\\s*(?:\\/\\/|\\/\\*){1}\\s*\\@(optional|innerXml)\\s*(?:\\*\\/)?\\s*$", regexFlags); std::sregex_iterator it(data.begin(), data.end(), regex); std::sregex_iterator end; while(it != end) { // Extract the kind of parameter this is std::smatch match; if(!std::regex_search(data, match, regex)) { std::cout << "Failed to determine parameter type!" << std::endl; return { .name = "" }; } std::string paramType = match[1].str(); // Find the next ";" auto endPos = data.find(";", it->position() + it->length()); if(endPos == std::string::npos) { std::cout << "Failed to find end of line for attribute!" << std::endl; return { .name = "" }; } // Go backwards see if there's an equals sign after the match but before endPos auto equalsPos = data.rfind("=", endPos); // If there's an equals sign, we search backwards from there, // otherwise we search backwards from the ; auto varStart = it->position() + it->length() + 1; size_t lastSearchPos = ( (equalsPos == std::string::npos || equalsPos <= varStart) ? endPos : equalsPos ); // Now we have our string auto varLength = lastSearchPos - varStart; auto variableString = data.substr(varStart, varLength); // Now (should) be able to extract the type; std::regex regex2("^\\s*(?:[\\S]+<)?([\\w*:_\\s]+)(?:[\\S]+)? (\\**[\\w]+)\\s*$", regexFlags); std::smatch match2; if(!std::regex_search(variableString, match2, regex2)) { std::cout << "Failed to extract type and name from variable string! " << variableString << std::endl; return { .name = "" }; } // Now we have our type and name auto type = match2[1].str(); auto name = match2[2].str(); // Now store depending on the type if(paramType == "optional") { ruleset.selfOptional[name] = type; } else if(paramType == "innerXml") { ruleset.innerXml[name] = type; } else { assertUnreachable("SceneItemComponentRegistry::parseFile: Unknown parameter type"); } ++it; } return ruleset; } struct SceneItemComponentRuleset SceneItemComponentRegistry::getRuleset(std::string type) { if(this->rulesets.find(type) != this->rulesets.end()) { return this->rulesets[type]; } std::vector pathsToScan; pathsToScan.push_back(this->sources); auto it = pathsToScan.begin(); while(it != pathsToScan.end()) { auto str = *it; pathsToScan.erase(it); Directory dir = Directory(str); auto children = dir.readDirectory(); auto itChildren = children.begin(); while(itChildren != children.end()) { if(itChildren->second == DIRECTORY_CHILD_TYPE_DIRECTORY) { pathsToScan.push_back(str + "/" + itChildren->first); ++itChildren; continue; } if(itChildren->first != type+".hpp") { ++itChildren; continue; } // Load ruleset File file(str + "/" + itChildren->first); auto parsed = SceneItemComponentRegistry::parseFile(&file, type); if(parsed.name == "") { std::cout << "Parsing error occured on " << type << std::endl; return parsed; } // Update optionals parsed.optional.insert(parsed.selfOptional.begin(), parsed.selfOptional.end()); // Recursively parse children auto itExtends = parsed.extends.begin(); while(itExtends != parsed.extends.end()) { auto ruleset = this->getRuleset(*itExtends); if(ruleset.name == "") { ++itExtends; continue; } // Merge ruleset parsed.optional.insert(ruleset.optional.begin(), ruleset.optional.end()); ++itExtends; } this->rulesets[type] = parsed; return parsed; } it = pathsToScan.begin(); }; return { .name = "" }; }