204 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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<std::string> 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 = "" };
 | |
| }
 |