Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw an error if there is an undeclared type or a template. #4

Open
wants to merge 13 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Kodgen/Include/Kodgen/CodeGen/CodeGenManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,34 @@ namespace kodgen
std::set<fs::path> const& toProcessFiles,
CodeGenResult& out_genResult) noexcept;

/**
* @brief Process all provided files ignoring Clang parsing errors on multiple threads.
*
* @param fileParser Original file parser to use to parse registered files. A copy of this parser will be used for each generation thread.
* @param codeGenUnit Generation unit used to generate files. It must have a clean state when this method is called.
* @param toProcessFiles Collection of all files to process.
* @param out_genResult Reference to the generation result to fill during file generation.
*/
template <typename FileParserType, typename CodeGenUnitType>
void processFilesIgnoreErrors(FileParserType& fileParser,
CodeGenUnitType& codeGenUnit,
std::set<fs::path> const& toProcessFiles,
CodeGenResult& out_genResult) noexcept;

/**
* @brief Process all provided files and fail on any Clang parsing errors on multiple threads.
*
* @param fileParser Original file parser to use to parse registered files. A copy of this parser will be used for each generation thread.
* @param codeGenUnit Generation unit used to generate files. It must have a clean state when this method is called.
* @param toProcessFiles Collection of all files to process.
* @param out_genResult Reference to the generation result to fill during file generation.
*/
template <typename FileParserType, typename CodeGenUnitType>
void processFilesFailOnErrors(FileParserType& fileParser,
CodeGenUnitType& codeGenUnit,
std::set<fs::path> const& toProcessFiles,
CodeGenResult& out_genResult) noexcept;

/**
* @brief Identify all files which will be parsed & regenerated.
*
Expand Down
210 changes: 208 additions & 2 deletions Kodgen/Include/Kodgen/CodeGen/CodeGenManager.inl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,212 @@

template <typename FileParserType, typename CodeGenUnitType>
void CodeGenManager::processFiles(FileParserType& fileParser, CodeGenUnitType& codeGenUnit, std::set<fs::path> const& toProcessFiles, CodeGenResult& out_genResult) noexcept
{
ParsingSettings parsingSettings = fileParser.getSettings();
if (!parsingSettings.shouldFailCodeGenerationOnClangErrors)
{
processFilesIgnoreErrors(fileParser, codeGenUnit, toProcessFiles, out_genResult);
}
else
{
processFilesFailOnErrors(fileParser, codeGenUnit, toProcessFiles, out_genResult);
}
}

template <typename FileParserType, typename CodeGenUnitType>
void CodeGenManager::processFilesFailOnErrors(FileParserType& fileParser, CodeGenUnitType& codeGenUnit, std::set<fs::path> const& toProcessFiles, CodeGenResult& out_genResult) noexcept
{
std::vector<std::shared_ptr<TaskBase>> generationTasks;

//Reserve enough space for all tasks
generationTasks.reserve(toProcessFiles.size());

//Launch all parsing -> generation processes
std::shared_ptr<TaskBase> parsingTask;
std::shared_ptr<TaskBase> preParsingTask;

const kodgen::MacroCodeGenUnitSettings* codeGenSettings = codeGenUnit.getSettings();
std::set<fs::path> filesLeftToProcess = toProcessFiles;
std::vector<std::pair<fs::path, ParsingError>> parsingResultsOfFailedFiles;
size_t filesLeftBefore = 0;

// Process files in cycle.
// Files that failed parsing step will be queued for the next cycle iteration to be parsed again.
// This is needed because sometimes not all GENERATED macros are filled on pre-parsing step (usually,
// when we have an include chain of multiple files that use reflection some GENERATED macros
// are not detected on pre-parsing step).
do
{
parsingResultsOfFailedFiles.clear();
filesLeftBefore = filesLeftToProcess.size();
std::set<fs::path> filesToProcessThisIteration = std::move(filesLeftToProcess);

//Lock the thread pool until all tasks have been pushed to avoid competing for the tasks mutex
_threadPool.setIsRunning(false);

// First, run pre-parse step in which we will fill generated files
// with reflection macros (file/class macros).
// This will avoid the following issue: if we are using inheritance and we haven't
// generated parent's macros while parsing child class we will fail with an error.
std::vector<std::set<std::string>> fileMacrosToDefine(filesToProcessThisIteration.size());
size_t iPreParsingFileIndex = 0;
for (fs::path const& file : filesToProcessThisIteration)
{
std::set<std::string>* macrosToDefine = &fileMacrosToDefine[iPreParsingFileIndex];
auto preParsingTaskLambda = [codeGenSettings, &fileParser, &file, macrosToDefine](TaskBase*) -> bool
{
FileParserType fileParserCopy = fileParser;
return fileParserCopy.prepareForParsing(file, codeGenSettings, *macrosToDefine);
};

preParsingTask = _threadPool.submitTask(std::string("Pre-parsing ") + file.string(), preParsingTaskLambda);

iPreParsingFileIndex += 1;
}

// Wait for pre-parse step to finish.
_threadPool.setIsRunning(true);
_threadPool.joinWorkers();
_threadPool.setIsRunning(false);

// Define generated macros.
iPreParsingFileIndex = 0;
for (fs::path const& file : filesToProcessThisIteration)
{
if (!fileMacrosToDefine[iPreParsingFileIndex].empty())
{
// Populate generated file with macros.
const auto generatedFilePath = codeGenSettings->getOutputDirectory() / codeGenSettings->getGeneratedHeaderFileName(file);

std::ofstream generatedfile(generatedFilePath, std::ios::app);
for (const auto& macroName : fileMacrosToDefine[iPreParsingFileIndex])
{
generatedfile << "#define " + macroName + " " << std::endl;
}
generatedfile.close();
}

iPreParsingFileIndex += 1;
}

// Run parsing step.
std::vector<std::shared_ptr<TaskBase>> parsingTasks;
for (fs::path const& file : filesToProcessThisIteration)
{
auto parsingTaskLambda = [codeGenSettings, &fileParser, &file, &filesLeftToProcess, &parsingResultsOfFailedFiles](TaskBase*) -> FileParsingResult
{
FileParsingResult parsingResult;

// Copy a parser for this task.
FileParserType fileParserCopy = fileParser;

fileParserCopy.parseFailOnErrors(file, parsingResult, codeGenSettings);
if (!parsingResult.errors.empty())
{
for (const auto& error : parsingResult.errors)
{
parsingResultsOfFailedFiles.push_back(std::make_pair(file, error));
}
parsingResult.errors.clear();
filesLeftToProcess.insert(file);
}

return parsingResult;
};

//Add file to the list of parsed files before starting the task to avoid having to synchronize threads
out_genResult.parsedFiles.push_back(file);

parsingTask = _threadPool.submitTask(std::string("Parsing ") + file.string(), parsingTaskLambda);
parsingTasks.push_back(parsingTask);
}

// Wait for parse step to finish.
// We wait here for all tasks to be finished because on the next step
// (the generation step) we will fill generated files with an actual data
// and this will clear existing macro defines that generated files have,
// but we need these macros for all parsing tasks to be finished without errors.
_threadPool.setIsRunning(true);
_threadPool.joinWorkers();
_threadPool.setIsRunning(false);

// Run code generation after all files were parsed.
size_t parsingTaskIndex = 0;
for (fs::path const& file : filesToProcessThisIteration)
{
// Check if the parsing step failed for this file.
bool bParsingFailed = false;
for (const auto& failedFile : filesLeftToProcess)
{
if (failedFile == file)
{
bParsingFailed = true;
break;
}
}
if (bParsingFailed)
{
parsingTaskIndex += 1;
continue;
}

// Clear generated file as it will be filled with an actual information.
// Right now it has some defines that were used for proper parsing.
const auto generatedFilePath = codeGenSettings->getOutputDirectory() / codeGenSettings->getGeneratedHeaderFileName(file);
std::ofstream generatedFile(generatedFilePath); // truncate the file
generatedFile.close();

auto generationTaskLambda = [&codeGenUnit](TaskBase* parsingTask) -> CodeGenResult
{
CodeGenResult out_generationResult;

// Copy the generation unit model to have a fresh one for this generation unit.
CodeGenUnitType generationUnit = codeGenUnit;

// Get the result of the parsing task.
FileParsingResult parsingResult = TaskHelper::getDependencyResult<FileParsingResult>(parsingTask, 0u);

// Generate the file if no errors occured during parsing.
if (parsingResult.errors.empty())
{
out_generationResult.completed = generationUnit.generateCode(parsingResult);
}

return out_generationResult;
};

generationTasks.emplace_back(_threadPool.submitTask(std::string("Generation ") + file.string(), generationTaskLambda, { parsingTasks[parsingTaskIndex] }));
parsingTaskIndex += 1;
}

// Wait for code generation.
_threadPool.setIsRunning(true);
_threadPool.joinWorkers();
}while(!filesLeftToProcess.empty() && filesLeftBefore != filesLeftToProcess.size());

// Log errors.
if (logger != nullptr)
{
if (!parsingResultsOfFailedFiles.empty())
{
out_genResult.completed = false;
}

for (const auto& error : parsingResultsOfFailedFiles)
{
logger->log("While processing the following file: " + error.first.string() + ": " + error.second.toString(), kodgen::ILogger::ELogSeverity::Error);
}
}

//Merge all generation results together
for (std::shared_ptr<TaskBase>& task : generationTasks)
{
out_genResult.mergeResult(TaskHelper::getResult<CodeGenResult>(task.get()));
}
}

template <typename FileParserType, typename CodeGenUnitType>
void CodeGenManager::processFilesIgnoreErrors(FileParserType& fileParser, CodeGenUnitType& codeGenUnit, std::set<fs::path> const& toProcessFiles, CodeGenResult& out_genResult) noexcept
{
std::vector<std::shared_ptr<TaskBase>> generationTasks;
uint8 iterationCount = codeGenUnit.getIterationCount();
Expand All @@ -30,7 +236,7 @@ void CodeGenManager::processFiles(FileParserType& fileParser, CodeGenUnitType& c
FileParserType fileParserCopy = fileParser;
FileParsingResult parsingResult;

fileParserCopy.parse(file, parsingResult);
fileParserCopy.parseIgnoreErrors(file, parsingResult);

return parsingResult;
};
Expand Down Expand Up @@ -119,4 +325,4 @@ CodeGenResult CodeGenManager::run(FileParserType& fileParser, CodeGenUnitType& c
}

return genResult;
}
}
72 changes: 65 additions & 7 deletions Kodgen/Include/Kodgen/Parsing/FileParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <string>
#include <vector>
#include <set>
#include <memory> //std::shared_ptr

#include <clang-c/Index.h>
Expand All @@ -19,6 +20,7 @@
#include "Kodgen/Parsing/PropertyParser.h"
#include "Kodgen/Misc/Filesystem.h"
#include "Kodgen/Misc/ILogger.h"
#include <Kodgen/CodeGen/Macro/MacroCodeGenUnitSettings.h>

namespace kodgen
{
Expand Down Expand Up @@ -109,6 +111,36 @@ namespace kodgen
*/
bool logDiagnostic(CXTranslationUnit const& translationUnit) const noexcept;

/**
* Splits macro pattern into two parts between "##...##" substring. For example:
* for "##CLASSFULLNAME##_GENERATED" it will return a pair of "" (empty) and "_GENERATED".
*
* @param macroPattern Macro pattern.
*
* @return a pair of strings between "##...##" substring.
*/
static std::pair<std::string, std::string> splitMacroPattern(const std::string& macroPattern);

/**
* @brief Returns all errors found during translation unit parsing.
*
* @parma toParseFile File that was used in parsing.
* @param translationUnit Translation unit we want to check.
* @param codeGenSettings Code generation settings that was used.
* @param notFoundGeneratedMacroNames Set of GENERATED macro names that were not found
* during parsing.
*
* @remark This function ignores errors caused by including generated headers or
* using GENERATED or reflection macros.
*
* @return array of errors (if found).
*/
std::vector<std::string> getErrors(
fs::path const& toParseFile,
CXTranslationUnit const& translationUnit,
const kodgen::MacroCodeGenUnitSettings* codeGenSettings,
std::set<std::string>& notFoundGeneratedMacroNames) const noexcept;

/**
* @brief Helper to get the ParsingResult contained in the context as a FileParsingResult.
*
Expand All @@ -120,9 +152,9 @@ namespace kodgen
/**
* @brief Overridable method called just before starting the parsing process of a file
*
* @param parseFile Path to the file which is about to be parsed
* @param parseFile Path to the file which is about to be parsed.
*/
virtual void preParse(fs::path const& parseFile) noexcept;
virtual void preParse(fs::path const& parseFile) noexcept;

/**
* @brief Overridable method called just after the parsing process has been finished
Expand All @@ -143,15 +175,41 @@ namespace kodgen
virtual ~FileParser() noexcept;

/**
* @brief Parse the file and fill the FileParsingResult.
* @brief Prepares initial generated file for parsing before calling to @ref parseFailOnErrors.
*
* @param toParseFile Path to the file to parse.
* @param codeGenSettings Code generation settings.
* @param notFoundGeneratedMacroNames Array of generated macros that needs to be defines.
*
* @return true if the parsing process finished without error, else false
*/
bool prepareForParsing(fs::path const& toParseFile,
const kodgen::MacroCodeGenUnitSettings* codeGenSettings,
std::set<std::string>& notFoundGeneratedMacroNames) noexcept;

/**
* @brief Parse the file and fill the FileParsingResult while ignoring any parsing errors.
*
* @param toParseFile Path to the file to parse.
* @param out_result Result filled while parsing the file.
*
* @return true if the parsing process finished without error, else false
*/
bool parseIgnoreErrors(fs::path const& toParseFile,
FileParsingResult& out_result) noexcept;

/**
* @brief Parse the file and fill the FileParsingResult while failing on any parsing errors.
*
* @param toParseFile Path to the file to parse.
* @param out_result Result filled while parsing the file.
* @param toParseFile Path to the file to parse.
* @param out_result Result filled while parsing the file.
* @param codeGenSettings Code generation settings.
*
* @return true if the parsing process finished without error, else false
*/
bool parse(fs::path const& toParseFile,
FileParsingResult& out_result) noexcept;
bool parseFailOnErrors(fs::path const& toParseFile,
FileParsingResult& out_result,
const kodgen::MacroCodeGenUnitSettings* codeGenSettings) noexcept;

/**
* @brief Getter for _settings field.
Expand Down
Loading