From ac23950f3baa277872e1224f34a0d517a98e4bc2 Mon Sep 17 00:00:00 2001 From: vczh Date: Sat, 21 Jan 2023 04:03:12 -0800 Subject: [PATCH] Upload source code of tools --- .gitignore | 3 + Tools/Executables/CodePack/CodePack.vcxproj | 202 +++++ .../CodePack/CodePack.vcxproj.filters | 84 ++ Tools/Executables/CodePack/Codepack.h | 66 ++ .../CodePack/Codepack_CategorizeCodeFiles.cpp | 63 ++ .../Executables/CodePack/Codepack_Combine.cpp | 214 +++++ .../CodePack/Codepack_GetFiles.cpp | 54 ++ .../CodePack/Codepack_GetIncludeFiles.cpp | 130 +++ Tools/Executables/CodePack/Main.cpp | 298 +++++++ Tools/Executables/CppMerge/CppMerge.vcxproj | 195 +++++ .../CppMerge/CppMerge.vcxproj.filters | 63 ++ Tools/Executables/CppMerge/Main.cpp | 62 ++ Tools/Executables/CppMerge/WfMergeCpp.cpp | 441 ++++++++++ Tools/Executables/CppMerge/WfMergeCpp.h | 42 + Tools/Executables/Executables.sln | 61 ++ Tools/Executables/GacGen/GacGen.cpp | 128 +++ Tools/Executables/GacGen/GacGen.h | 69 ++ Tools/Executables/GacGen/GacGen.vcxproj | 239 ++++++ .../Executables/GacGen/GacGen.vcxproj.filters | 117 +++ Tools/Executables/GacGen/Main.cpp | 788 ++++++++++++++++++ .../GlrParserGen/GlrParserGen.vcxproj | 187 +++++ .../GlrParserGen/GlrParserGen.vcxproj.filters | 75 ++ Tools/Executables/GlrParserGen/Main.cpp | 440 ++++++++++ 23 files changed, 4021 insertions(+) create mode 100644 Tools/Executables/CodePack/CodePack.vcxproj create mode 100644 Tools/Executables/CodePack/CodePack.vcxproj.filters create mode 100644 Tools/Executables/CodePack/Codepack.h create mode 100644 Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp create mode 100644 Tools/Executables/CodePack/Codepack_Combine.cpp create mode 100644 Tools/Executables/CodePack/Codepack_GetFiles.cpp create mode 100644 Tools/Executables/CodePack/Codepack_GetIncludeFiles.cpp create mode 100644 Tools/Executables/CodePack/Main.cpp create mode 100644 Tools/Executables/CppMerge/CppMerge.vcxproj create mode 100644 Tools/Executables/CppMerge/CppMerge.vcxproj.filters create mode 100644 Tools/Executables/CppMerge/Main.cpp create mode 100644 Tools/Executables/CppMerge/WfMergeCpp.cpp create mode 100644 Tools/Executables/CppMerge/WfMergeCpp.h create mode 100644 Tools/Executables/Executables.sln create mode 100644 Tools/Executables/GacGen/GacGen.cpp create mode 100644 Tools/Executables/GacGen/GacGen.h create mode 100644 Tools/Executables/GacGen/GacGen.vcxproj create mode 100644 Tools/Executables/GacGen/GacGen.vcxproj.filters create mode 100644 Tools/Executables/GacGen/Main.cpp create mode 100644 Tools/Executables/GlrParserGen/GlrParserGen.vcxproj create mode 100644 Tools/Executables/GlrParserGen/GlrParserGen.vcxproj.filters create mode 100644 Tools/Executables/GlrParserGen/Main.cpp diff --git a/.gitignore b/.gitignore index 8963c5fa..dc048063 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +#Tools +Tools/*.exe + #Vczh Libraries *.xml.log/ diff --git a/Tools/Executables/CodePack/CodePack.vcxproj b/Tools/Executables/CodePack/CodePack.vcxproj new file mode 100644 index 00000000..f8407017 --- /dev/null +++ b/Tools/Executables/CodePack/CodePack.vcxproj @@ -0,0 +1,202 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {EFF0F387-7C0A-46C5-A544-62A24A6BC978} + Win32Proj + CodePack + 10.0 + + + + Application + true + v143 + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + Application + true + v143 + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + false + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + false + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + true + true + + + + + + true + true + true + true + + + + + + true + true + true + true + + + + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tools/Executables/CodePack/CodePack.vcxproj.filters b/Tools/Executables/CodePack/CodePack.vcxproj.filters new file mode 100644 index 00000000..43690bb4 --- /dev/null +++ b/Tools/Executables/CodePack/CodePack.vcxproj.filters @@ -0,0 +1,84 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {df74faf6-c4d7-49f9-a69c-39430c70787e} + + + + + Source Files + + + Import + + + Import + + + Import + + + Import + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Import + + + Import + + + Import + + + Import + + + Import + + + + + Import + + + Import + + + Import + + + Import + + + Source Files + + + Import + + + \ No newline at end of file diff --git a/Tools/Executables/CodePack/Codepack.h b/Tools/Executables/CodePack/Codepack.h new file mode 100644 index 00000000..be5633ab --- /dev/null +++ b/Tools/Executables/CodePack/Codepack.h @@ -0,0 +1,66 @@ +#include + +using namespace vl; +using namespace vl::collections; +using namespace vl::filesystem; +using namespace vl::glr; +using namespace vl::glr::xml; +using namespace vl::regex; +using namespace vl::console; +using namespace vl::stream; + +extern Regex regexInclude; +extern Regex regexSystemInclude; +extern Regex regexInstruction; + +extern const vint include_path; +extern const vint systemInclude_path; +extern const vint instruction_name; +extern const vint instruction_param; + +inline WString ReadFile(const FilePath& path) +{ + WString text; + BomEncoder::Encoding encoding; + bool containsBom; + File(path).ReadAllTextWithEncodingTesting(text, encoding, containsBom); + return text; +} + +extern LazyList GetCppFiles( + const FilePath& folder, + List& exceptions + ); + +extern LazyList GetHeaderFiles( + const FilePath& folder, + List& exceptions + ); + +extern void CategorizeCodeFiles( + Ptr config, + LazyList files, + Group& categorizedFiles, + Dictionary& inputFileToCategories + ); + +extern LazyList GetIncludedFiles( + const FilePath& codeFile, + const Dictionary& skippedImportFiles, + Dictionary>& cachedFileToIncludes, + Group>& conditionOns, + Group>& conditionOffs + ); + +extern void Combine( + const Dictionary& inputFileToOutputFiles, + const Dictionary& skippedImportFiles, + Dictionary>& cachedFileToIncludes, + Group>& conditionOns, + Group>& conditionOffs, + const List& files, + FilePath outputFilePath, + FilePath outputIncludeFilePath, + SortedList& systemIncludes, + LazyList externalIncludes + ); \ No newline at end of file diff --git a/Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp b/Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp new file mode 100644 index 00000000..ed45fa2e --- /dev/null +++ b/Tools/Executables/CodePack/Codepack_CategorizeCodeFiles.cpp @@ -0,0 +1,63 @@ +#include "Codepack.h" + +void CategorizeCodeFiles( + Ptr config, + LazyList files, + Group& categorizedFiles, + Dictionary& inputFileToCategories +) +{ + for (auto e : XmlGetElements(XmlGetElement(config->rootElement, L"categories"), L"category")) + { + auto name = XmlGetAttribute(e, L"name")->value.value; + auto pattern = wupper(XmlGetAttribute(e, L"pattern")->value.value); + + List exceptions; + CopyFrom( + exceptions, + XmlGetElements(e,L"except") + .Select([](const Ptr x) + { + return XmlGetAttribute(x, L"pattern")->value.value; + }) + ); + + List filterFiles; + CopyFrom( + filterFiles, + From(files).Where([&](const FilePath& f) + { + auto path = f.GetFullPath(); + return INVLOC.FindFirst(path, pattern, Locale::IgnoreCase).key != -1 + && From(exceptions).All([&](const WString& ex) + { + return INVLOC.FindFirst(path, ex, Locale::IgnoreCase).key == -1; + }); + }) + ); + + for (auto file : filterFiles) + { + if (!categorizedFiles.Contains(name, file)) + { + categorizedFiles.Add(name, file); + inputFileToCategories.Add(file, name); + } + } + } + + for (auto a : categorizedFiles.Keys()) + { + for (auto b : categorizedFiles.Keys()) + { + if (a != b) + { + const auto& as = categorizedFiles.Get(a); + const auto& bs = categorizedFiles.Get(b); + List cs; + CopyFrom(cs, From(as).Intersect(bs)); + CHECK_ERROR(From(cs).IsEmpty(), L"A file should not appear in multiple categories."); + } + } + } +} \ No newline at end of file diff --git a/Tools/Executables/CodePack/Codepack_Combine.cpp b/Tools/Executables/CodePack/Codepack_Combine.cpp new file mode 100644 index 00000000..98fbadaa --- /dev/null +++ b/Tools/Executables/CodePack/Codepack_Combine.cpp @@ -0,0 +1,214 @@ +#include "Codepack.h" + +FilePath GetCommonFolder( + const List& paths +) +{ + auto folder = paths[0].GetFolder(); + while (true) + { + if (From(paths).All([&](const FilePath& path) + { + return INVLOC.StartsWith(path.GetFullPath(), folder.GetFullPath() + WString::FromChar(folder.Delimiter), Locale::IgnoreCase); + })) + { + return folder; + } + folder = folder.GetFolder(); + } + CHECK_FAIL(L"Cannot process files across multiple drives."); +} + +void CollectConditions( + Group& categorizedConditions, + Group>& conditions, + const FilePath& file, + const Dictionary& inputFileToOutputFiles +) +{ + vint index = conditions.Keys().IndexOf(file); + if (index != -1) + { + const auto& tuples = conditions.GetByIndex(index); + for (vint i = 0; i < tuples.Count(); i++) + { + auto condition = tuples[i].f0; + auto includeFile = inputFileToOutputFiles[tuples[i].f1]; + if (!categorizedConditions.Contains(condition, includeFile)) + { + categorizedConditions.Add(condition, includeFile); + } + } + } +} + +void CombineWriteHeader( + List& lines, + const Dictionary& inputFileToOutputFiles, + Group>& conditionOns, + Group>& conditionOffs, + const List& files, + LazyList externalIncludes +) +{ + lines.Add(L"/***********************************************************************"); + lines.Add(L"THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY"); + lines.Add(L"DEVELOPER: Zihan Chen(vczh)"); + lines.Add(L"***********************************************************************/"); + for (auto path : externalIncludes) + { + lines.Add(L"#include \"" + path + L"\""); + } + { + Group categorizedConditionOns, categorizedConditionOffs; + for (auto file : files) + { + CollectConditions(categorizedConditionOns, conditionOns, file, inputFileToOutputFiles); + CollectConditions(categorizedConditionOffs, conditionOffs, file, inputFileToOutputFiles); + } + + for (vint i = 0; i < categorizedConditionOns.Count(); i++) + { + lines.Add(L"#ifdef " + categorizedConditionOns.Keys()[i]); + const auto& onFiles = categorizedConditionOns.GetByIndex(i); + for (auto onFile : onFiles) + { + lines.Add(L"#include \"" + inputFileToOutputFiles[onFile] + L"\""); + } + lines.Add(L"#endif"); + } + + for (vint i = 0; i < categorizedConditionOffs.Count(); i++) + { + lines.Add(L"#ifndef " + categorizedConditionOffs.Keys()[i]); + const auto& offFiles = categorizedConditionOffs.GetByIndex(i); + for (auto offFile : offFiles) + { + lines.Add(L"#include \"" + offFile + L".h\""); + } + lines.Add(L"#endif"); + } + } +} + +void Combine( + const Dictionary& inputFileToOutputFiles, // (in) + const Dictionary& skippedImportFiles, // (in) + Dictionary>& cachedFileToIncludes, // (cache) + Group>& conditionOns, // (out) + Group>& conditionOffs, // (out) + const List& files, // (in) + FilePath outputFilePath, // + FilePath outputIncludeFilePath, // + SortedList& systemIncludes, // + LazyList externalIncludes // +) +{ + auto workingDir = outputFilePath.GetFolder(); + + List sortedFiles; + { + PartialOrderingProcessor popFiles; + Group depGroup; + for (auto file : files) + { + for (auto dep : GetIncludedFiles(file, skippedImportFiles, cachedFileToIncludes, conditionOns, conditionOffs)) + { + if (files.Contains(dep)) + { + depGroup.Add(file, dep); + } + } + } + + popFiles.InitWithGroup(files, depGroup); + popFiles.Sort(); + + bool needExit = false; + for (vint i = 0; i < popFiles.components.Count(); i++) + { + auto& component = popFiles.components[i]; + sortedFiles.Add(files[component.firstNode[0]]); + + if (component.nodeCount > 1) + { + Console::SetColor(true, false, false, true); + Console::WriteLine( + L"Error: Cycle dependency found in categories: " + + From(component.firstNode, component.firstNode + component.nodeCount) + .Select([&](vint nodeIndex) { return L"\r\n" + files[nodeIndex].GetFullPath(); }) + .Aggregate([](const WString& a, const WString& b) {return a + b; }) + + L"\r\n."); + Console::SetColor(true, true, true, false); + needExit = true; + } + } + CHECK_ERROR(!needExit, L"Cycle dependency is not allowed"); + } + { + List lines; + CombineWriteHeader(lines, inputFileToOutputFiles, conditionOns, conditionOffs, files, externalIncludes); + { + auto prefix = GetCommonFolder(files); + for (auto file : From(sortedFiles).Intersect(files)) + { + lines.Add(L""); + lines.Add(L"/***********************************************************************"); + lines.Add(wupper(prefix.GetRelativePathFor(file))); + lines.Add(L"***********************************************************************/"); + + StringReader reader(ReadFile(file)); + bool skip = false; + while (!reader.IsEnd()) + { + auto line = reader.ReadLine(); + Ptr match; + if ((match = regexInstruction.MatchHead(line))) + { + auto name = match->Groups()[instruction_name][0].Value(); + if (name == L"BeginIgnore") + { + skip = true; + } + else if (name == L"EndIgnore") + { + skip = false; + } + } + else if (!skip) + { + if ((match = regexSystemInclude.MatchHead(line))) + { + auto systemFile = match->Groups()[systemInclude_path][0].Value(); + if (skippedImportFiles.Keys().Contains(systemFile)) continue; + if (systemIncludes.Contains(systemFile)) continue; + systemIncludes.Add(systemFile); + lines.Add(line); + } + else if (!regexInclude.MatchHead(line)) + { + lines.Add(line); + } + } + } + } + } + File(outputFilePath).WriteAllLines(lines, true, BomEncoder::Utf8); + } + { + List lines; + CombineWriteHeader(lines, inputFileToOutputFiles, conditionOns, conditionOffs, files, externalIncludes); + lines.Add(L""); + { + auto prefix = outputIncludeFilePath.GetFolder(); + for (auto file : From(sortedFiles).Intersect(files)) + { + lines.Add(L"#include \"" + prefix.GetRelativePathFor(file) + L"\""); + } + } + File(outputIncludeFilePath).WriteAllLines(lines, true, BomEncoder::Utf8); + } + Console::SetColor(false, true, false, true); + Console::WriteLine(L"Succeeded to write: " + outputFilePath.GetFullPath()); + Console::SetColor(true, true, true, false); +} \ No newline at end of file diff --git a/Tools/Executables/CodePack/Codepack_GetFiles.cpp b/Tools/Executables/CodePack/Codepack_GetFiles.cpp new file mode 100644 index 00000000..4f5487fa --- /dev/null +++ b/Tools/Executables/CodePack/Codepack_GetFiles.cpp @@ -0,0 +1,54 @@ +#include "Codepack.h" + +LazyList SearchFiles( + const Folder& folder, + const WString& extension, + List& exceptions +) +{ + auto files = Ptr(new List); + auto folders = Ptr(new List); + folder.GetFiles(*files.Obj()); + folder.GetFolders(*folders.Obj()); + + return LazyList(files) + .Select([](const File& file) + { + return file.GetFilePath(); + }) + .Where([&](const FilePath& filePath) + { + return From(exceptions) + .All([&](const WString& except) + { + return INVLOC.FindFirst(filePath.GetFullPath(), except, Locale::IgnoreCase).key == -1; + }); + }) + .Where([=](const FilePath& path) + { + return INVLOC.EndsWith(path.GetName(), extension, Locale::IgnoreCase); + }) + .Concat( + LazyList(folders) + .SelectMany([&](const Folder& folder) + { + return SearchFiles(folder, extension, exceptions); + }) + ); +} + +LazyList GetCppFiles( + const FilePath& folder, + List& exceptions +) +{ + return SearchFiles(folder, L".cpp", exceptions); +} + +LazyList GetHeaderFiles( + const FilePath& folder, + List& exceptions +) +{ + return SearchFiles(folder, L".h", exceptions); +} \ No newline at end of file diff --git a/Tools/Executables/CodePack/Codepack_GetIncludeFiles.cpp b/Tools/Executables/CodePack/Codepack_GetIncludeFiles.cpp new file mode 100644 index 00000000..167b7546 --- /dev/null +++ b/Tools/Executables/CodePack/Codepack_GetIncludeFiles.cpp @@ -0,0 +1,130 @@ +#include "Codepack.h" + +Regex regexInclude(LR"/(^\s*#include\s*"([^"]+)"\s*$)/"); +Regex regexSystemInclude(LR"/(^\s*#include\s*<([^"]+)>\s*$)/"); +Regex regexInstruction(LR"/(^\s*\/\*\s*CodePack:(\w+)\((([^,)]+)(,\s*([^,)]+))*)?\)\s*\*\/\s*$)/"); + +const vint include_path = regexInclude.CaptureNames().IndexOf(L"path"); +const vint systemInclude_path = regexSystemInclude.CaptureNames().IndexOf(L"path"); +const vint instruction_name = regexInstruction.CaptureNames().IndexOf(L"name"); +const vint instruction_param = regexInstruction.CaptureNames().IndexOf(L"param"); + +LazyList GetIncludedFiles( + const FilePath& codeFile, // (in) + const Dictionary& skippedImportFiles, // (in) + Dictionary>& cachedFileToIncludes, // (cache) + Group>& conditionOns, // (out) + Group>& conditionOffs // (out) +) +{ + { + vint index = cachedFileToIncludes.Keys().IndexOf(codeFile); + if (index != -1) + { + return cachedFileToIncludes.Values()[index]; + } + } + Console::SetColor(true, true, false, true); + Console::WriteLine(L"Scanning file: " + codeFile.GetFullPath()); + Console::SetColor(true, true, true, false); + + List includes; + StringReader reader(ReadFile(codeFile)); + bool skip = false; + vint lineIndex = 0; + while (!reader.IsEnd()) + { + lineIndex++; + auto line = reader.ReadLine(); + Ptr match; + if ((match = regexInstruction.MatchHead(line))) + { + auto name = match->Groups()[instruction_name][0].Value(); + const List* params = nullptr; + { + vint index = match->Groups().Keys().IndexOf(instruction_param); + if (index != -1) + { + params = &match->Groups().GetByIndex(index); + } + } + + if (name == L"BeginIgnore") + { + if (params == nullptr) + { + skip = true; + continue; + } + } + else if (name == L"EndIgnore") + { + if (params == nullptr) + { + skip = false; + continue; + } + } + else if (name == L"ConditionOn") + { + if (params && params->Count() == 2) + { + conditionOns.Add(codeFile, { params->Get(0).Value(),codeFile.GetFolder() / params->Get(1).Value() }); + continue; + } + } + else if (name == L"ConditionOff") + { + if (params && params->Count() == 2) + { + conditionOffs.Add(codeFile, { params->Get(0).Value(),codeFile.GetFolder() / params->Get(1).Value() }); + continue; + } + } + Console::SetColor(true, false, false, true); + Console::WriteLine(L"Error: Unrecognizable CodePack instruction: \"" + line + L"\" in file: " + codeFile.GetFullPath() + L" (" + itow(lineIndex) + L")"); + Console::SetColor(true, true, true, false); + } + else if ((match = regexInclude.MatchHead(line))) + { + if (!skip) + { + auto path = codeFile.GetFolder() / match->Groups()[include_path][0].Value(); + if (!includes.Contains(path)) + { + includes.Add(path); + } + } + } + else if ((match = regexSystemInclude.MatchHead(line))) + { + if (!skip) + { + auto systemFile = match->Groups()[systemInclude_path][0].Value(); + vint index = skippedImportFiles.Keys().IndexOf(systemFile); + if (index != -1) + { + auto path = skippedImportFiles.Values()[index]; + if (!includes.Contains(path)) + { + includes.Add(path); + } + } + } + } + } + + auto result = Ptr(new List); + CopyFrom( + *result.Obj(), + From(includes) + .Concat(From(includes).SelectMany([&](const FilePath& includedFile) + { + return GetIncludedFiles(includedFile, skippedImportFiles, cachedFileToIncludes, conditionOns, conditionOffs); + })) + .Distinct() + ); + + cachedFileToIncludes.Add(codeFile, result); + return result; +} \ No newline at end of file diff --git a/Tools/Executables/CodePack/Main.cpp b/Tools/Executables/CodePack/Main.cpp new file mode 100644 index 00000000..8419e742 --- /dev/null +++ b/Tools/Executables/CodePack/Main.cpp @@ -0,0 +1,298 @@ +#include "Codepack.h" + +int main(int argc, char* argv[]) +{ + Console::SetTitle(L"Vczh CodePack for C++"); + if (argc != 2) + { + Console::SetColor(true, false, false, true); + Console::WriteLine(L"CodePack.exe "); + Console::SetColor(true, true, true, false); + return 0; + } + + // load configuration + auto workingDir = FilePath(atow(argv[1])).GetFolder(); + Ptr config; + { + Parser parser; + List errors; + InstallDefaultErrorMessageGenerator(parser, errors); + auto text = ReadFile(atow(argv[1])); + config = XmlParseDocument(text, parser); + + if (errors.Count() > 0) + { + Console::SetColor(true, false, false, true); + Console::WriteLine(L"Failed to read the input XML file."); + for (auto error : errors) + { + Console::WriteLine(L"[row:" + itow(error.codeRange.start.row + 1) + L"][column:" + itow(error.codeRange.start.column + 1) + L"]: " + error.message); + } + Console::SetColor(true, true, true, false); + return 1; + } + } + + Dictionary> categorizedOutput; // category -> {output file, generate?} + { + // get configuration for all categories + CopyFrom( + categorizedOutput, + XmlGetElements(XmlGetElement(config->rootElement, L"output"), L"codepair") + .Select([&](Ptr e)->Pair> + { + return { + XmlGetAttribute(e, L"category")->value.value, + { + XmlGetAttribute(e, L"filename")->value.value, + XmlGetAttribute(e, L"generate")->value.value == L"true" + } + }; + }) + ); + } + + // collect code files + List unprocessedCppFiles; // all cpp files need to combine + List unprocessedHeaderFiles; // all header files need to combine + Dictionary> cachedFileToIncludes; // file -> directly/indirectly included files + Group> conditionOns; // #ifdef -> files to include + Group> conditionOffs; // #ifndef -> files to include + { + // get included folders + List folders; + CopyFrom( + folders, + XmlGetElements(XmlGetElement(config->rootElement,L"folders"), L"folder") + .Select([&](Ptr e) + { + return workingDir / XmlGetAttribute(e, L"path")->value.value; + }) + ); + + // get excluded folders + List exceptions; + CopyFrom( + exceptions, + XmlGetElements(XmlGetElement(config->rootElement,L"folders"), L"except") + .Select([&](Ptr e) + { + return XmlGetAttribute(e, L"pattern")->value.value; + }) + ); + + // enumerate all *.cpp files in specified folders + CopyFrom( + unprocessedCppFiles, + From(folders) + .SelectMany([&](const FilePath& folder) { return GetCppFiles(folder, exceptions); }) + .Distinct() + ); + + // enumerate all *.h files in specified folders + CopyFrom( + unprocessedHeaderFiles, + From(folders) + .SelectMany([&](const FilePath& folder) { return GetHeaderFiles(folder, exceptions); }) + .Distinct() + ); + } + + // categorize code files + Group categorizedCppFiles; // category -> cpp files + Group categorizedHeaderFiles; // category -> header files + Dictionary inputFileToCategories; // input file -> category + { + // categorize all *.cpp and *.h files + CategorizeCodeFiles(config, unprocessedCppFiles, categorizedCppFiles, inputFileToCategories); + CategorizeCodeFiles(config, unprocessedHeaderFiles, categorizedHeaderFiles, inputFileToCategories); + } + + // collect import files as system include + Dictionary skippedImportFiles; // file name -> file path + for (vint i = 0; i < categorizedOutput.Count(); i++) + { + if (!categorizedOutput.Values()[i].f1) + { + auto category = categorizedOutput.Keys()[i]; + for (auto skippedImportFile : categorizedHeaderFiles[category]) + { + skippedImportFiles.Add(skippedImportFile.GetName(), skippedImportFile); + } + } + } + + // check if there any *.h file is included without assigning a category + { + List headerFiles; + CopyFrom( + headerFiles, + From(unprocessedHeaderFiles) + .Concat(unprocessedCppFiles) + .SelectMany([&](const FilePath& includedFile) + { + return GetIncludedFiles(includedFile, skippedImportFiles, cachedFileToIncludes, conditionOns, conditionOffs); + }) + .Concat(unprocessedHeaderFiles) + .Distinct() + .Except(unprocessedHeaderFiles) + ); + + if (headerFiles.Count() > 0) + { + Console::SetColor(true, false, false, true); + Console::WriteLine(L"Error: The following files are not categorized."); + for (auto headerFile : headerFiles) + { + Console::WriteLine(headerFile.GetFullPath()); + } + Console::SetColor(true, true, true, false); + CHECK_ERROR(true, L"All included files should be categorized"); + } + } + + // calculate category dependencies + PartialOrderingProcessor popCategories; // POP for category ordering + Group componentToCategoryNames; // component index to category names + { + SortedList items; + Group depGroup; + + CopyFrom(items, From(unprocessedCppFiles).Concat(unprocessedHeaderFiles)); + for (auto filePath : items) + { + for (auto includedFile : GetIncludedFiles(filePath, skippedImportFiles, cachedFileToIncludes, conditionOns, conditionOffs)) + { + depGroup.Add(filePath, includedFile); + } + } + + popCategories.InitWithSubClass(items, depGroup, inputFileToCategories); + popCategories.Sort(); + + for (vint i = 0; i < popCategories.components.Count(); i++) + { + auto& component = popCategories.components[i]; + for (vint j = 0; j < component.nodeCount; j++) + { + auto& firstNode = popCategories.nodes[component.firstNode[j]]; + auto firstFile = items[firstNode.firstSubClassItem[0]]; + componentToCategoryNames.Add(i, inputFileToCategories[firstFile]); + } + } + + bool needExit = false; + for (vint i = 0; i < componentToCategoryNames.Count(); i++) + { + const auto& cycleCategories = componentToCategoryNames.GetByIndex(i); + if (cycleCategories.Count() > 1) + { + Console::SetColor(true, false, false, true); + Console::WriteLine( + L"Error: Cycle dependency found in categories: " + + From(cycleCategories).Aggregate([](const WString& a, const WString& b) {return a + L", " + b; }) + + L"."); + Console::SetColor(true, true, true, false); + needExit = true; + } + } + CHECK_ERROR(!needExit, L"Cycle dependency is not allowed"); + } + + Dictionary inputFileToOutputFiles; // input file -> output file + { + for (vint i = 0; i < inputFileToCategories.Count(); i++) + { + auto key = inputFileToCategories.Keys()[i]; + auto value = inputFileToCategories.Values()[i]; + inputFileToOutputFiles.Add(key, categorizedOutput[value].f0); + } + } + + // prepare output folder + auto outputFolder = workingDir / (XmlGetAttribute(XmlGetElement(config->rootElement, L"output"), L"path")->value.value); + auto outputIncludeOnlyFolder = outputFolder / L"IncludeOnly"; + if (!Folder(outputIncludeOnlyFolder).Exists()) + { + Folder(outputIncludeOnlyFolder).Create(true); + } + + // generate categorized header dependencies + Dictionary> categorizedHeaderIncludes; + for (vint i = 0; i < popCategories.components.Count(); i++) + { + auto& component = popCategories.components[i]; + auto categoryName = componentToCategoryNames[i][0]; + categorizedHeaderIncludes.Add( + categoryName, + From(*popCategories.nodes[component.firstNode[0]].ins) + .Where([&](vint nodeIndex) + { + return nodeIndex != component.firstNode[0]; + }) + .Select([&](vint nodeIndex) + { + return categorizedOutput[componentToCategoryNames[popCategories.nodes[nodeIndex].component][0]].f0 + L".h"; + }) + ); + } + + // generate code pair header files + Dictionary>> categorizedSystemIncludes; + for (vint i = 0; i < popCategories.components.Count(); i++) + { + auto categoryName = componentToCategoryNames[i][0]; + auto outputPath = outputFolder / (categorizedOutput[categoryName].f0 + L".h"); + auto outputIncludeOnlyPath = outputIncludeOnlyFolder / (categorizedOutput[categoryName].f0 + L".h"); + + auto systemIncludes = Ptr(new SortedList); + categorizedSystemIncludes.Add(categoryName, systemIncludes); + + if (categorizedOutput[categoryName].f1) + { + vint headerIndex = categorizedHeaderFiles.Keys().IndexOf(categoryName); + if (headerIndex == -1) continue; + Combine( + inputFileToOutputFiles, + skippedImportFiles, + cachedFileToIncludes, + conditionOns, + conditionOffs, + categorizedHeaderFiles.GetByIndex(headerIndex), + outputPath, + outputIncludeOnlyPath, + *systemIncludes.Obj(), + categorizedHeaderIncludes[categoryName] + ); + } + } + + // generate code pair cpp files + for (vint i = 0; i < popCategories.components.Count(); i++) + { + auto categoryName = componentToCategoryNames[i][0]; + if (categorizedOutput[categoryName].f1) + { + WString outputHeader[] = { categorizedOutput[categoryName].f0 + L".h" }; + vint headerIndex = categorizedHeaderFiles.Keys().IndexOf(categoryName); + + auto outputPath = outputFolder / (categorizedOutput[categoryName].f0 + L".cpp"); + auto outputIncludeOnlyPath = outputIncludeOnlyFolder / (categorizedOutput[categoryName].f0 + L".cpp"); + Combine( + inputFileToOutputFiles, + skippedImportFiles, + cachedFileToIncludes, + conditionOns, + conditionOffs, + categorizedCppFiles[categoryName], + outputPath, + outputIncludeOnlyPath, + *categorizedSystemIncludes[categoryName].Obj(), + (headerIndex == -1 ? categorizedHeaderIncludes[categoryName] : From(outputHeader)) + ); + } + } + + return 0; +} \ No newline at end of file diff --git a/Tools/Executables/CppMerge/CppMerge.vcxproj b/Tools/Executables/CppMerge/CppMerge.vcxproj new file mode 100644 index 00000000..f7c9a16a --- /dev/null +++ b/Tools/Executables/CppMerge/CppMerge.vcxproj @@ -0,0 +1,195 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {6D22025C-1EE0-413B-A212-E53C4F90B39A} + Win32Proj + CppMerge + 10.0 + + + + Application + true + v143 + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + Application + true + v143 + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + true + true + + + + + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + + + true + true + true + true + + + + + true + true + true + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tools/Executables/CppMerge/CppMerge.vcxproj.filters b/Tools/Executables/CppMerge/CppMerge.vcxproj.filters new file mode 100644 index 00000000..5d769f82 --- /dev/null +++ b/Tools/Executables/CppMerge/CppMerge.vcxproj.filters @@ -0,0 +1,63 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {9abafbcb-8b2d-42bd-bf85-93e42b92b969} + + + + + Import + + + Source Files + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Source Files + + + + + Import + + + Import + + + Import + + + Source Files + + + \ No newline at end of file diff --git a/Tools/Executables/CppMerge/Main.cpp b/Tools/Executables/CppMerge/Main.cpp new file mode 100644 index 00000000..24e94a47 --- /dev/null +++ b/Tools/Executables/CppMerge/Main.cpp @@ -0,0 +1,62 @@ +#include "WfMergeCpp.h" + +using namespace vl; +using namespace vl::console; +using namespace vl::filesystem; +using namespace vl::stream; +using namespace vl::workflow::cppcodegen; + +#if defined VCZH_MSVC +int wmain(int argc, wchar_t* argv[]) +#elif defined VCZH_GCC +int main(int argc, char* argv[]) +#endif +{ + Console::SetTitle(L"Vczh CodeMerge for C++"); + if (argc != 4) + { + Console::SetColor(true, false, false, true); + Console::WriteLine(L"Usage: CppMerge "); + Console::SetColor(true, true, true, false); + return 0; + } +#if defined VCZH_MSVC + FilePath file32 = argv[1]; + FilePath file64 = argv[2]; + FilePath fileOutput = argv[3]; +#elif defined VCZH_GCC + FilePath file32 = atow(argv[1]); + FilePath file64 = atow(argv[2]); + FilePath fileOutput = atow(argv[3]); +#endif + + try + { + auto code = MergeCppMultiPlatform(File(file32).ReadAllTextByBom(), File(file64).ReadAllTextByBom()); + + File file(fileOutput); + if (file.Exists()) + { + code = MergeCppFileContent(file.ReadAllTextByBom(), code); + } + + if (file.Exists()) + { + auto originalCode = file.ReadAllTextByBom(); + if (originalCode == code) + { + return 0; + } + } + + file.WriteAllText(code, true, BomEncoder::Utf8); + return 0; + } + catch (const MergeCppMultiPlatformException& ex) + { + Console::SetColor(true, false, false, true); + Console::WriteLine(ex.Message()); + Console::SetColor(true, true, true, false); + throw; + } +} diff --git a/Tools/Executables/CppMerge/WfMergeCpp.cpp b/Tools/Executables/CppMerge/WfMergeCpp.cpp new file mode 100644 index 00000000..990ac671 --- /dev/null +++ b/Tools/Executables/CppMerge/WfMergeCpp.cpp @@ -0,0 +1,441 @@ +#include "WfMergeCpp.h" +#include + +namespace vl +{ + namespace workflow + { + namespace cppcodegen + { + using namespace collections; + using namespace stream; + using namespace regex; + +/*********************************************************************** +MergeCpp +***********************************************************************/ + + WString RemoveSpacePrefix(const WString& s) + { + for (vint i = 0; i < s.Length(); i++) + { + if (s[i] != L' '&&s[i] != L'\t') + { + return s.Sub(i, s.Length() - i); + } + } + return WString::Empty; + } + + const vint NORMAL = 0; + const vint WAIT_HEADER = 1; + const vint WAIT_OPEN = 2; + const vint WAIT_CLOSE = 3; + const vint USER_CONTENT = 4; + const vint UNUSED_USER_CONTENT = 5; + + template + void ProcessCppContent(const WString& code, const TCallback& callback) + { + Regex regexUserContentBegin(L"/.*?(?/{)?///* USER_CONTENT_BEGIN/(([^)]*?)/) /*//"); + vint _name = regexUserContentBegin.CaptureNames().IndexOf(L"name"); + + vint state = NORMAL; + vint counter = 0; + WString previousContent; + + StringReader reader(code); + while (!reader.IsEnd()) + { + auto line = reader.ReadLine(); + if (reader.IsEnd() && line == L"") + { + break; + } + + if (line == L"// UNUSED_USER_CONTENT:") + { + state = UNUSED_USER_CONTENT; + } + + if (state == UNUSED_USER_CONTENT) + { + callback(state, state, line, line); + } + else + { + auto content = RemoveSpacePrefix(line); + auto previousState = state; + switch (state) + { + case NORMAL: + if (auto match = regexUserContentBegin.MatchHead(content)) + { + content = L"USERIMPL(/* " + match->Groups()[_name][0].Value() + L" */)"; + if (match->Captures().Count() > 0) + { + content += previousContent; + } + state = USER_CONTENT; + } + else if (INVLOC.StartsWith(content, L"USERIMPL(",Locale::None)) + { + state = WAIT_HEADER; + } + break; + case WAIT_HEADER: + state = WAIT_OPEN; + break; + case WAIT_OPEN: + if (INVLOC.StartsWith(content, L"{", Locale::None)) + { + state = WAIT_CLOSE; + } + break; + case WAIT_CLOSE: + if (INVLOC.StartsWith(content, L"{", Locale::None)) + { + counter++; + } + else if (INVLOC.StartsWith(content, L"}", Locale::None)) + { + if (counter == 0) + { + state = NORMAL; + } + else + { + counter--; + } + } + break; + case USER_CONTENT: + if (INVLOC.EndsWith(content, L"/* USER_CONTENT_END() */", Locale::None)) + { + state = NORMAL; + } + break; + } + callback(previousState, state, line, content); + } + previousContent = RemoveSpacePrefix(line); + } + } + + template + void SplitCppContent(const WString& code, Dictionary& userContents, Dictionary& userContentsFull, const TCallback& callback) + { + WString name; + WString userImpl; + WString userImplFull; + ProcessCppContent(code, [&](vint previousState, vint state, const WString& line, const WString& content) + { + if (state == UNUSED_USER_CONTENT) + { + callback(line); + } + else + { + switch (previousState) + { + case NORMAL: + switch (state) + { + case WAIT_HEADER: + case USER_CONTENT: + name = content; + userImpl = L""; + userImplFull = L""; + break; + } + break; + case WAIT_HEADER: + name += content; + break; + case WAIT_CLOSE: + case USER_CONTENT: + switch (state) + { + case WAIT_CLOSE: + case USER_CONTENT: + userImpl += line + L"\r\n"; + break; + case NORMAL: + userImplFull += L"//" + line + L"\r\n"; + userContents.Add(name, userImpl); + userContentsFull.Add(name, userImplFull); + name = L""; + break; + } + break; + } + + if (name != L"") + { + userImplFull += L"//" + line + L"\r\n"; + } + } + }); + } + + MergeCppMultiPlatformException::MergeCppMultiPlatformException(vint _row32, vint _column32, vint _row64, vint _column64) + :Exception(L"The difference at " + L"x86 file(row:" + itow(_row32 + 1) + L", column:" + itow(_column32 + 1) + L") and " + L"x64 file(row:" + itow(_row64 + 1) + L", column:" + itow(_column64 + 1) + L") are not " + L"\"vint32_t\" and \"vint64_t\", " + L"\"vuint32_t\" and \"vuint64_t\", " + L"\"\" and \"L\", " + L"\"\" and \"UL\".") + , row32(_row32) + , column32(_column32) + , row64(_row64) + , column64(_column64) + { + } + + void CountRowAndColumn(const wchar_t* start, const wchar_t* reading, vint& row, vint& column) + { + row = 0; + column = 0; + while (start < reading) + { + if (*start++ == L'\n') + { + row++; + column = 0; + } + else + { + column++; + } + } + } + + + WString MergeCppMultiPlatform(const WString& code32, const WString& code64) + { + static wchar_t stringCast32[] = L"static_cast<::vl::vint32_t>("; + const vint lengthCast32 = sizeof(stringCast32) / sizeof(*stringCast32) - 1; + + static wchar_t stringCast64[] = L"static_cast<::vl::vint64_t>("; + const vint lengthCast64 = sizeof(stringCast64) / sizeof(*stringCast64) - 1; + + return GenerateToStream([&](StreamWriter& writer) + { + const wchar_t* reading32 = code32.Buffer(); + const wchar_t* reading64 = code64.Buffer(); + const wchar_t* start32 = reading32; + const wchar_t* start64 = reading64; + while (true) + { + vint length = 0; + while (reading32[length] && reading64[length]) + { + if (reading32[length] == reading64[length]) + { + length++; + } + else + { + break; + } + } + + writer.WriteString(reading32, length); + reading32 += length; + reading64 += length; + + if (*reading32 == 0 && *reading64 == 0) + { + break; + } + +#define IS_DIGIT(C) (L'0' <= C && C <= L'9') + + if (reading32[0] == L'3' && reading32[1] == L'2' && reading64[0] == L'6' && reading64[1] == L'4') + { + if (length >= 4) + { + if (wcsncmp(reading32 - 4, L"vint32_t", 8) == 0 && wcsncmp(reading64 - 4, L"vint64_t", 8) == 0) + { + reading32 += 4; + reading64 += 4; + goto NEXT_ROUND; + } + } + if (length >= 5) + { + if (wcsncmp(reading32 - 5, L"vuint32_t", 9) == 0 && wcsncmp(reading64 - 5, L"vuint64_t", 9) == 0) + { + reading32 += 4; + reading64 += 4; + goto NEXT_ROUND; + } + } + } + else if (reading64[0] == L'L') + { + if (reading32[0] == reading64[1] && length >= 1) + { + if (IS_DIGIT(reading32[-1]) && !IS_DIGIT(reading32[0])) + { + if (IS_DIGIT(reading64[-1]) && !IS_DIGIT(reading64[1])) + { + reading64 += 1; + goto NEXT_ROUND; + } + } + } + } + else if (reading64[0] == L'U' && reading64[1] == L'L') + { + if (reading32[0] == reading64[2] && length >= 1) + { + if (IS_DIGIT(reading32[-1]) && !IS_DIGIT(reading32[0])) + { + if (IS_DIGIT(reading64[-1]) && !IS_DIGIT(reading64[2])) + { + reading64 += 2; + goto NEXT_ROUND; + } + } + } + } + else if (wcsncmp(reading32, stringCast32, lengthCast32) == 0 && IS_DIGIT(reading32[lengthCast32]) && IS_DIGIT(reading64[0])) + { + reading32 += lengthCast32; + vint digitCount = 0; + while (IS_DIGIT(reading32[digitCount])) digitCount++; + if (wcsncmp(reading32, reading64, digitCount) == 0 && reading64[digitCount] == L'L' && reading32[digitCount] == L')') + { + writer.WriteString(L"static_cast<::vl::vint>("); + writer.WriteString(WString::CopyFrom(reading32, digitCount)); + writer.WriteChar(L')'); + reading64 += digitCount + 1; + reading32 += digitCount + 1; + goto NEXT_ROUND; + } + } + else if (wcsncmp(reading64, stringCast64, lengthCast64) == 0 && IS_DIGIT(reading64[lengthCast64]) && IS_DIGIT(reading32[0])) + { + reading64 += lengthCast64; + vint digitCount = 0; + while (IS_DIGIT(reading64[digitCount])) digitCount++; + if (wcsncmp(reading64, reading32, digitCount) == 0 && reading64[digitCount] == L'L' && reading64[digitCount + 1] == L')') + { + writer.WriteString(L"static_cast<::vl::vint>("); + writer.WriteString(WString::CopyFrom(reading64, digitCount)); + writer.WriteChar(L')'); + reading64 += digitCount + 2; + reading32 += digitCount; + goto NEXT_ROUND; + } + } + + { + vint row32 = 0; + vint column32 = 0; + vint row64 = 0; + vint column64 = 0; + CountRowAndColumn(start32, reading32, row32, column32); + CountRowAndColumn(start64, reading64, row64, column64); + throw MergeCppMultiPlatformException(row32, column32, row64, column64); + } + NEXT_ROUND:; +#undef IS_DIGIT + } + }); + } + + WString MergeCppFileContent(const WString& dst, const WString& src) + { + Dictionary userContents, userContentsFull; + WString unusedUserContent = GenerateToStream([&](StreamWriter& writer) + { + SplitCppContent(dst, userContents, userContentsFull, [&](const WString& line) + { + writer.WriteLine(line); + }); + }); + + WString processedUnusedUserContent = GenerateToStream([&](StreamWriter& writer) + { + StringReader reader(unusedUserContent); + while (!reader.IsEnd()) + { + auto line = reader.ReadLine(); + if (line != L"// UNUSED_USER_CONTENT:") + { + if (INVLOC.StartsWith(line, L"//", Locale::None)) + { + line = line.Right(line.Length() - 2); + } + writer.WriteLine(line); + } + } + }); + + SplitCppContent(processedUnusedUserContent, userContents, userContentsFull, [&](const WString& line) {}); + + return GenerateToStream([&](StreamWriter& writer) + { + WString name; + WString userImpl; + ProcessCppContent(src, [&](vint previousState, vint state, const WString& line, const WString& content) + { + switch (previousState) + { + case NORMAL: + switch (state) + { + case WAIT_HEADER: + case USER_CONTENT: + name = content; + userImpl = L""; + break; + } + break; + case WAIT_HEADER: + name += content; + break; + case WAIT_CLOSE: + case USER_CONTENT: + switch (state) + { + case WAIT_CLOSE: + case USER_CONTENT: + userImpl += line + L"\r\n"; + return; + case NORMAL: + { + vint index = userContents.Keys().IndexOf(name); + if (index == -1) + { + writer.WriteString(userImpl); + } + else + { + writer.WriteString(userContents.Values()[index]); + userContentsFull.Remove(name); + } + } + break; + } + break; + } + writer.WriteLine(line); + }); + + if (userContentsFull.Count() > 0) + { + writer.WriteLine(L"// UNUSED_USER_CONTENT:"); + for (auto content : userContentsFull.Values()) + { + writer.WriteString(content); + } + } + }); + } + } + } +} diff --git a/Tools/Executables/CppMerge/WfMergeCpp.h b/Tools/Executables/CppMerge/WfMergeCpp.h new file mode 100644 index 00000000..36007489 --- /dev/null +++ b/Tools/Executables/CppMerge/WfMergeCpp.h @@ -0,0 +1,42 @@ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) +Workflow::C++ Code Generator + +Interfaces: +**********************************************************************/ + +#ifndef VCZH_WORKFLOW_CPP_WFMERGECPP +#define VCZH_WORKFLOW_CPP_WFMERGECPP + +#include + +namespace vl +{ + namespace workflow + { + namespace cppcodegen + { + +/*********************************************************************** +MergeCpp +***********************************************************************/ + + class MergeCppMultiPlatformException : public Exception + { + public: + vint row32; + vint column32; + vint row64; + vint column64; + + MergeCppMultiPlatformException(vint _row32, vint _column32, vint _row64, vint _column64); + }; + + extern WString MergeCppMultiPlatform(const WString& code32, const WString& code64); + extern WString MergeCppFileContent(const WString& dst, const WString& src); + } + } +} + +#endif \ No newline at end of file diff --git a/Tools/Executables/Executables.sln b/Tools/Executables/Executables.sln new file mode 100644 index 00000000..2e1ee4b6 --- /dev/null +++ b/Tools/Executables/Executables.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33205.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CodePack", "CodePack\CodePack.vcxproj", "{EFF0F387-7C0A-46C5-A544-62A24A6BC978}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CppMerge", "CppMerge\CppMerge.vcxproj", "{6D22025C-1EE0-413B-A212-E53C4F90B39A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GacGen", "GacGen\GacGen.vcxproj", "{07328A1E-E71E-4F90-9BD7-7F0FD7B73053}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GlrParserGen", "GlrParserGen\GlrParserGen.vcxproj", "{F5A36ED2-78E7-4776-B1AB-63DE18376F30}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Debug|x64.ActiveCfg = Debug|x64 + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Debug|x64.Build.0 = Debug|x64 + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Debug|x86.ActiveCfg = Debug|Win32 + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Debug|x86.Build.0 = Debug|Win32 + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Release|x64.ActiveCfg = Release|x64 + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Release|x64.Build.0 = Release|x64 + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Release|x86.ActiveCfg = Release|Win32 + {EFF0F387-7C0A-46C5-A544-62A24A6BC978}.Release|x86.Build.0 = Release|Win32 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Debug|x64.ActiveCfg = Debug|x64 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Debug|x64.Build.0 = Debug|x64 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Debug|x86.ActiveCfg = Debug|Win32 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Debug|x86.Build.0 = Debug|Win32 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Release|x64.ActiveCfg = Release|x64 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Release|x64.Build.0 = Release|x64 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Release|x86.ActiveCfg = Release|Win32 + {6D22025C-1EE0-413B-A212-E53C4F90B39A}.Release|x86.Build.0 = Release|Win32 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Debug|x64.ActiveCfg = Debug|x64 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Debug|x64.Build.0 = Debug|x64 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Debug|x86.ActiveCfg = Debug|Win32 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Debug|x86.Build.0 = Debug|Win32 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Release|x64.ActiveCfg = Release|x64 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Release|x64.Build.0 = Release|x64 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Release|x86.ActiveCfg = Release|Win32 + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053}.Release|x86.Build.0 = Release|Win32 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Debug|x64.ActiveCfg = Debug|x64 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Debug|x64.Build.0 = Debug|x64 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Debug|x86.ActiveCfg = Debug|Win32 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Debug|x86.Build.0 = Debug|Win32 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Release|x64.ActiveCfg = Release|x64 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Release|x64.Build.0 = Release|x64 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Release|x86.ActiveCfg = Release|Win32 + {F5A36ED2-78E7-4776-B1AB-63DE18376F30}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CD9CCEAC-DFF5-43CF-9415-3F5D36CAB308} + EndGlobalSection +EndGlobal diff --git a/Tools/Executables/GacGen/GacGen.cpp b/Tools/Executables/GacGen/GacGen.cpp new file mode 100644 index 00000000..5acfebc8 --- /dev/null +++ b/Tools/Executables/GacGen/GacGen.cpp @@ -0,0 +1,128 @@ +#include "GacGen.h" + +void PrintErrorMessage(const WString& message) +{ + Console::SetColor(true, false, false, true); + Console::WriteLine(message); + Console::SetColor(true, true, true, false); +} + +void PrintSuccessMessage(const WString& message) +{ + Console::SetColor(false, true, false, true); + Console::WriteLine(message); + Console::SetColor(true, true, true, false); +} + +void PrintInformationMessage(const WString& message) +{ + Console::SetColor(true, true, false, true); + Console::WriteLine(message); + Console::SetColor(true, true, true, false); +} + +/*********************************************************************** +CodegenConfig +***********************************************************************/ + +WString CodegenConfig::NormalizeFolder(const WString& folder) +{ + if (folder.Length() == 0) + { + return L".\\"; + } + else if (folder[folder.Length() - 1] != L'\\') + { + return folder + L"\\"; + } + else + { + return folder; + } +} + +bool CodegenConfig::LoadConfigString(Ptr folder, const WString& path, WString& value, bool optional) +{ + if (auto includeItem = folder->GetValueByPath(path).Cast()) + { + value = includeItem->GetText(); + return true; + } + else if (!optional) + { + PrintErrorMessage(L"error> Cannot find configuration in resource \"" + folder->GetName() + L"/" + path + L"\"."); + return false; + } + else + { + return true; + } +} + +bool CodegenConfig::LoadConfigString(Ptr folder, const WString& path, List& value, bool optional) +{ + WString text; + if (LoadConfigString(folder, path, text, optional)) + { + SplitBySemicolon(text, value); + return true; + } + return false; +} + +Ptr CodegenConfig::LoadConfig(Ptr resource, GuiResourceError::List& errors) +{ + auto config = Ptr(new CodegenConfig); + config->resource = resource; + config->metadata = resource->GetMetadata(); + + if (auto folder = resource->GetFolderByPath(L"GacGenConfig/Cpp/")) + { + auto out = Ptr(new CodegenConfig::CppOutput); + LoadConfigString(folder, L"Resource", out->resource); + LoadConfigString(folder, L"Compressed", out->compressed); + LoadConfigString(folder, L"SourceFolder", out->sourceFolder, false); + LoadConfigString(folder, L"NormalInclude", out->normalIncludes, false); + LoadConfigString(folder, L"ReflectionInclude", out->reflectionIncludes); + LoadConfigString(folder, L"Name", out->name); + LoadConfigString(folder, L"CppResource", out->cppResource); + LoadConfigString(folder, L"CppCompressed", out->cppCompressed); + + NormalizeFolder(out->sourceFolder); + if (out->name == L"") + { + out->name = L"GacUIApplication"; + } + config->cppOutput = out; + } + + if (auto folder = resource->GetFolderByPath(L"GacGenConfig/ResX86/")) + { + auto out = Ptr(new CodegenConfig::ResOutput); + LoadConfigString(folder, L"Resource", out->resource); + LoadConfigString(folder, L"Compressed", out->compressed); + LoadConfigString(folder, L"Assembly", out->assembly); + + config->resOutputx32 = out; + } + + if (auto folder = resource->GetFolderByPath(L"GacGenConfig/ResX64/")) + { + auto out = Ptr(new CodegenConfig::ResOutput); + LoadConfigString(folder, L"Resource", out->resource); + LoadConfigString(folder, L"Compressed", out->compressed); + LoadConfigString(folder, L"Assembly", out->assembly); + + config->resOutputx64 = out; + } + + if (auto item = resource->GetValueByPath(L"GacGenConfig/Metadata")) + { + if (auto xml = item.Cast()) + { + resource->GetMetadata()->LoadFromXml(xml, { resource }, errors); + } + } + + return config; +} \ No newline at end of file diff --git a/Tools/Executables/GacGen/GacGen.h b/Tools/Executables/GacGen/GacGen.h new file mode 100644 index 00000000..a2c3a217 --- /dev/null +++ b/Tools/Executables/GacGen/GacGen.h @@ -0,0 +1,69 @@ +#ifndef VCZH_GACGEN +#define VCZH_GACGEN + +#include "GacUICompiler.h" + +using namespace vl; +using namespace vl::console; +using namespace vl::collections; +using namespace vl::regex; +using namespace vl::stream; +using namespace vl::reflection::description; +using namespace vl::glr::xml; +using namespace vl::workflow; +using namespace vl::workflow::analyzer; +using namespace vl::workflow::runtime; +using namespace vl::workflow::cppcodegen; +using namespace vl::presentation; +using namespace vl::presentation::templates; + +/*********************************************************************** +Console +***********************************************************************/ + +extern void PrintErrorMessage(const WString& message); +extern void PrintSuccessMessage(const WString& message); +extern void PrintInformationMessage(const WString& message); + +/*********************************************************************** +Object Model +***********************************************************************/ + +class CodegenConfig +{ +public: + class ResOutput : public Object + { + public: + WString resource; + WString compressed; + WString assembly; + }; + + class CppOutput : public Object + { + public: + WString sourceFolder; + List normalIncludes; + List reflectionIncludes; + WString name; + + WString resource; + WString compressed; + WString cppResource; + WString cppCompressed; + }; + + Ptr resource; + Ptr metadata; + Ptr cppOutput; + Ptr resOutputx32; + Ptr resOutputx64; + + static WString NormalizeFolder(const WString& folder); + static bool LoadConfigString(Ptr folder, const WString& path, WString& value, bool optional = true); + static bool LoadConfigString(Ptr folder, const WString& path, List& value, bool optional = true); + static Ptr LoadConfig(Ptr resource, GuiResourceError::List& errors); +}; + +#endif \ No newline at end of file diff --git a/Tools/Executables/GacGen/GacGen.vcxproj b/Tools/Executables/GacGen/GacGen.vcxproj new file mode 100644 index 00000000..f71d21cc --- /dev/null +++ b/Tools/Executables/GacGen/GacGen.vcxproj @@ -0,0 +1,239 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {07328A1E-E71E-4F90-9BD7-7F0FD7B73053} + Win32Proj + GacGen + 10.0 + + + + Application + true + v143 + Unicode + x64 + + + Application + true + v143 + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)Bin\ + $(ProjectName)D + + + true + $(SolutionDir)Bin\ + $(ProjectName)D + + + false + $(SolutionDir)Bin\ + $(ProjectName) + + + false + $(SolutionDir)Bin\ + $(ProjectName) + + + + + + Level3 + Disabled + VCZH_DEBUG_METAONLY_REFLECTION;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import\;$(ProjectDir)..\..\..\Release\;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + + + + + + + Level3 + Disabled + VCZH_DEBUG_METAONLY_REFLECTION;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import\;$(ProjectDir)..\..\..\Release\;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + VCZH_DEBUG_METAONLY_REFLECTION;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import\;$(ProjectDir)..\..\..\Release\;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + VCZH_DEBUG_METAONLY_REFLECTION;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\Import\;$(ProjectDir)..\..\..\Release\;%(AdditionalIncludeDirectories) + stdcpp20 + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + + + true + true + true + true + + + + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + + + + true + true + true + true + + + + + + + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + + + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + + + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + /bigobj %(AdditionalOptions) + + + + + + + + \ No newline at end of file diff --git a/Tools/Executables/GacGen/GacGen.vcxproj.filters b/Tools/Executables/GacGen/GacGen.vcxproj.filters new file mode 100644 index 00000000..de6e1903 --- /dev/null +++ b/Tools/Executables/GacGen/GacGen.vcxproj.filters @@ -0,0 +1,117 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8632b5b4-2334-4f34-96fa-fc76cb48960d} + + + + + Import + + + Header Files + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + + + Import + + + Source Files + + + Source Files + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + \ No newline at end of file diff --git a/Tools/Executables/GacGen/Main.cpp b/Tools/Executables/GacGen/Main.cpp new file mode 100644 index 00000000..72e74e9f --- /dev/null +++ b/Tools/Executables/GacGen/Main.cpp @@ -0,0 +1,788 @@ +#include "GacGen.h" + +using namespace vl::filesystem; +using namespace vl::presentation::controls; + +GuiResourceCpuArchitecture targetCpuArchitecture = GuiResourceCpuArchitecture::Unspecified; +Array* arguments = 0; +WString executablePath; + +void SetTargetCpuArchitecture() +{ + if (arguments->Count() > 0) + { + if (arguments->Get(0) == L"/P32" || arguments->Get(0) == L"/D32" || arguments->Get(0) == L"/C32") + { + targetCpuArchitecture = GuiResourceCpuArchitecture::x86; + } + else if (arguments->Get(0) == L"/P64" || arguments->Get(0) == L"/D64" || arguments->Get(0) == L"/C64") + { + targetCpuArchitecture = GuiResourceCpuArchitecture::x64; + } + } +} + +#if defined VCZH_MSVC +int wmain(int argc, wchar_t* argv[]) +{ + executablePath = argv[0]; + Array _arguments(argc - 1); + for (vint i = 1; i < argc; i++) + { + _arguments[i - 1] = argv[i]; + } + arguments = &_arguments; + SetTargetCpuArchitecture(); + return SetupGacGenNativeController(); +} +#elif defined VCZH_GCC +int main(int argc, char* argv[]) +{ + executablePath = atow(argv[0]); + Array _arguments(argc - 1); + for (vint i = 1; i < argc; i++) + { + _arguments[i - 1] = atow(argv[i]); + } + arguments = &_arguments; + SetTargetCpuArchitecture(); + return SetupGacGenNativeController(); +} +#endif + +void PrintErrors(List& errors) +{ + List output; + GuiResourceError::SortAndLog(errors, output); + for (auto line : output) + { + PrintErrorMessage(line); + } +} + +void SaveErrors(FilePath errorFilePath, List& errors) +{ + PrintErrors(errors); + if (WriteErrors(errors, errorFilePath)) + { + PrintErrorMessage(L"error> Error information is saved at : " + errorFilePath.GetFullPath()); + } + else + { + PrintErrorMessage(L"error> Unable to write : " + errorFilePath.GetFullPath()); + } +} + +class Callback : public Object, public IGuiResourcePrecompileCallback, public IWfCompilerCallback +{ +public: + vint lastPass = -1; + + void OnLoadEnvironment()override + { + PrintInformationMessage(L" Workflow: Loading metadata from registered types ..."); + } + + void OnInitialize(analyzer::WfLexicalScopeManager* manager)override + { + PrintInformationMessage(L" Workflow: Creating metadata from declarations ..."); + } + + void OnValidateModule(Ptr module)override + { + PrintInformationMessage(L" Workflow: Validating module " + module->name.value + L" ..."); + } + + void OnGenerateMetadata()override + { + PrintInformationMessage(L" Workflow: Generating metadata ..."); + } + + void OnGenerateCode(Ptr module)override + { + PrintInformationMessage(L" Workflow: Generating code for module " + module->name.value + L" ..."); + } + + void OnGenerateDebugInfo()override + { + PrintInformationMessage(L" Workflow: Generating debug information ..."); + } + + IWfCompilerCallback* GetCompilerCallback()override + { + return this; + } + + void PrintPass(vint passIndex) + { + if (lastPass != passIndex) + { + lastPass = passIndex; + switch (passIndex) + { + case IGuiResourceTypeResolver_Precompile::Workflow_Collect: + PrintInformationMessage(L"Pass: (1/8) Collect workflow scripts"); + break; + case IGuiResourceTypeResolver_Precompile::Workflow_Compile: + PrintInformationMessage(L"Pass: (2/8) Compile view model scripts"); + break; + case IGuiResourceTypeResolver_Precompile::Instance_CollectInstanceTypes: + PrintInformationMessage(L"Pass: (3/8) Collect instances"); + break; + case IGuiResourceTypeResolver_Precompile::Instance_CompileInstanceTypes: + PrintInformationMessage(L"Pass: (4/8) Validate instances"); + break; + case IGuiResourceTypeResolver_Precompile::Instance_CollectEventHandlers: + PrintInformationMessage(L"Pass: (5/8) Generate instance type stubs"); + break; + case IGuiResourceTypeResolver_Precompile::Instance_CompileEventHandlers: + PrintInformationMessage(L"Pass: (6/8) Compile instance type stubs"); + break; + case IGuiResourceTypeResolver_Precompile::Instance_GenerateInstanceClass: + PrintInformationMessage(L"Pass: (7/8) Generate instance types"); + break; + case IGuiResourceTypeResolver_Precompile::Instance_CompileInstanceClass: + PrintInformationMessage(L"Pass: (8/8) Compile instance types"); + break; + } + } + } + + void OnPerPass(vint passIndex)override + { + PrintPass(passIndex); + } + + void OnPerResource(vint passIndex, Ptr resource)override + { + PrintPass(passIndex); + PrintInformationMessage(L" " + resource->GetResourcePath()); + } +}; + +struct LoadConfigResult +{ + Ptr resource; + Ptr config; +}; + +LoadConfigResult LoadConfig(FilePath inputPath) +{ + List errors; + auto resource = GuiResource::LoadFromXml(inputPath.GetFullPath(), errors); + + for (vint i = errors.Count() - 1; i >= 0; i--) + { + auto error = errors[i]; + if (INVLOC.FindFirst(error.message, L"Path of imported folder does not exist:", Locale::None).key == 0) + { + errors.RemoveAt(i); + } + } + + if (errors.Count() > 0) + { + PrintErrorMessage(L"error> Failed to load resource."); + PrintErrors(errors); + return {}; + } + + auto config = CodegenConfig::LoadConfig(resource, errors); + if (!config) + { + PrintErrorMessage(L"error> Failed to load config."); + return {}; + } + if (errors.Count() > 0) + { + PrintErrorMessage(L"error> Failed to load resource metadata."); + PrintErrors(errors); + return {}; + } + + LoadConfigResult result; + result.resource = resource; + result.config = config; + return result; +} + +bool LoadDependencies(Ptr config, Dictionary& resourceMappings, const WString& logFolderPostfix, FilePath errorFilePath) +{ + if (config->metadata->version == L"") + { + config->metadata->version = GuiResource::CurrentVersionString; + } + + if (config->metadata->version != GuiResource::CurrentVersionString) + { + PrintErrorMessage(L"error> Version of the resource should be \"" + WString(GuiResource::CurrentVersionString) + L"\""); + return false; + } + + if (config->metadata->dependencies.Count() > 0 && config->metadata->name == L"") + { + PrintErrorMessage(L"error> Name of the resource should not be empty if it has dependencies."); + return false; + } + + SortedList requireds, loaded; + CopyFrom(requireds, config->metadata->dependencies); + + while (requireds.Count() > 0) + { + auto required = requireds[requireds.Count() - 1]; + requireds.RemoveAt(requireds.Count() - 1); + if (loaded.Contains(required)) continue; + + vint index = resourceMappings.Keys().IndexOf(required); + if (index == -1) + { + PrintErrorMessage(L"error> Cannot find the resource file of name: " + required); + return false; + } + + FilePath resourceBinary = FilePath(resourceMappings.Values()[index].GetFullPath() + logFolderPostfix) / L"ScriptedResource.bin"; + MemoryStream resourceStream; + { + FileStream fileStream(resourceBinary.GetFullPath(), FileStream::ReadOnly); + if (!fileStream.IsAvailable()) + { + PrintErrorMessage(L"error> Cannot find binary resource file, please make sure the required resource \"" + required + L"\" has been properly compiled: " + required); + return false; + } + CopyStream(fileStream, resourceStream); + } + + loaded.Add(required); + { + resourceStream.SeekFromBegin(0); + stream::internal::ContextFreeReader reader(resourceStream); + + WString metadataText; + reader << metadataText; + + GuiResourceError::List errors; + auto parser = GetParserManager()->GetParser(L"XML"); + auto xmlMetadata = parser->Parse({}, metadataText, errors); + if (!xmlMetadata || errors.Count() > 0) + { + PrintErrorMessage(L"error> The binary resource file has an invalid metadata, please make sure the required resource \"" + required + L"\" has been properly compiled: " + required); + return false; + } + + auto metadata = Ptr(new GuiResourceMetadata); + metadata->LoadFromXml(xmlMetadata, {}, errors); + if (errors.Count() > 0) + { + PrintErrorMessage(L"error> The binary resource file has an invalid metadata, please make sure the required resource \"" + required + L"\" has been properly compiled: " + required); + return false; + } + + CopyFrom(requireds, metadata->dependencies, true); + } + + resourceStream.SeekFromBegin(0); + GuiResourceError::List errors; + GetResourceManager()->LoadResourceOrPending(resourceStream, errors, GuiResourceUsage::InstanceClass); + if (errors.Count() > 0) + { + SaveErrors(errorFilePath, errors); + return false; + } + } + + List pendings; + GetResourceManager()->GetPendingResourceNames(pendings); + if (pendings.Count() > 0) + { + PrintErrorMessage(L"error> Unable to find dependencies: " + + From(pendings) + .Aggregate(WString::Empty, [](const WString& a, const WString& b) {return a == L"" ? b : a + L", " + b; }) + ); + return false; + } + return true; +} + +void CompileResource(bool partialMode, FilePath inputPath, Nullable mappingPath) +{ + PrintSuccessMessage(L"gacgen> Clearning logs ... : " + inputPath.GetFullPath()); + + WString logFolderPostfix; + switch (targetCpuArchitecture) + { + case GuiResourceCpuArchitecture::x86: + logFolderPostfix = L".log/x32"; + break; + case GuiResourceCpuArchitecture::x64: + logFolderPostfix = L".log/x64"; + break; + default:; + } + + FilePath logFolderPath = inputPath.GetFullPath() + logFolderPostfix; + FilePath scriptFilePath = logFolderPath / L"Workflow.txt"; + FilePath errorFilePath = logFolderPath / L"Errors.txt"; + FilePath workingDir = inputPath.GetFolder(); + + { + auto loadConfigResult = LoadConfig(inputPath); + if (!loadConfigResult.resource) return; + + Dictionary resourceMappings; + if (mappingPath) + { + FileStream fileStream(mappingPath.Value().GetFullPath(), FileStream::ReadOnly); + if (!fileStream.IsAvailable()) + { + PrintErrorMessage(L"error> Failed to load mapping file: " + mappingPath.Value().GetFullPath()); + return; + } + BomDecoder decoder; + DecoderStream decoderStream(fileStream, decoder); + StreamReader reader(decoderStream); + while (!reader.IsEnd()) + { + auto line = reader.ReadLine(); + if (line != L"") + { + auto arrow = INVLOC.FindFirst(line, L"=>", Locale::None); + if (arrow.key == -1) + { + PrintErrorMessage(L"warning> Unable to parse mapping information: " + line); + return; + } + auto name = line.Left(arrow.key); + auto value = line.Right(line.Length() - arrow.key - arrow.value); + if (resourceMappings.Keys().Contains(name)) + { + PrintErrorMessage(L"warning> Find duplicate mapping information: " + line); + return; + } + resourceMappings.Add(name, value); + } + } + } + + if (!LoadDependencies(loadConfigResult.config, resourceMappings, logFolderPostfix, errorFilePath)) + { + return; + } + } + + if (partialMode) + { + PrintInformationMessage(L"gacgen> Partial mode activated, all output files will be put under " + logFolderPath.GetFullPath()); + } + + { + Folder logFolder(logFolderPath); + if (logFolder.Exists()) + { + if (!logFolder.Delete(true)) + { + PrintErrorMessage(L"gacgen> Unable to delete file in the log folder : " + logFolderPath.GetFullPath()); + return; + } + } + if (!logFolder.Create(true)) + { + Thread::Sleep(500); + if (!logFolder.Create(true)) + { + PrintErrorMessage(L"gacgen> Unable to create log folder : " + logFolderPath.GetFullPath()); + return; + } + } + } + + Ptr resource; + { + PrintSuccessMessage(L"gacgen> Making : " + inputPath.GetFullPath()); + List errors; + resource = GuiResource::LoadFromXml(inputPath.GetFullPath(), errors); + if (errors.Count() > 0) + { + PrintErrorMessage(L"error> Failed to load resource."); + SaveErrors(errorFilePath, errors); + return; + } + } + + List cppResourcePaths; + List cppCompressedPaths; + List resResourcePaths; + List resCompressedPaths; + List resAssemblyPaths; + Ptr cppOutput; + Ptr resOutput; + { + Ptr config; + { + List errors; + config = CodegenConfig::LoadConfig(resource, errors); + if (!config) + { + PrintErrorMessage(L"error> Failed to load config."); + return; + } + if (errors.Count() > 0) + { + PrintErrorMessage(L"error> Failed to load resource metadata."); + SaveErrors(errorFilePath, errors); + return; + } + } + { + cppResourcePaths.Add(logFolderPath / L"Resource.bin"); + cppCompressedPaths.Add(logFolderPath / L"Compressed.bin"); + resResourcePaths.Add(logFolderPath / L"ScriptedResource.bin"); + resCompressedPaths.Add(logFolderPath / L"ScriptedCompressed.bin"); + resAssemblyPaths.Add(logFolderPath / L"Assembly.bin"); + } + + cppOutput = config->cppOutput; + switch (targetCpuArchitecture) + { + case GuiResourceCpuArchitecture::x86: + resOutput = config->resOutputx32; + break; + case GuiResourceCpuArchitecture::x64: + resOutput = config->resOutputx64; + break; + default:; + } + + if (cppOutput) + { + if (config->cppOutput->resource != L"") cppResourcePaths.Add(workingDir / config->cppOutput->resource); + if (config->cppOutput->compressed != L"") resAssemblyPaths.Add(workingDir / config->cppOutput->compressed); + } + + if (resOutput) + { + if (resOutput->resource != L"") resResourcePaths.Add(workingDir / resOutput->resource); + if (resOutput->compressed != L"") resCompressedPaths.Add(workingDir / resOutput->compressed); + if (resOutput->assembly != L"") resAssemblyPaths.Add(workingDir / resOutput->assembly); + } + } + + PrintSuccessMessage(L"gacgen> Compiling..."); + List errors; + Callback callback; + + auto precompiledFolder = PrecompileResource(resource, targetCpuArchitecture, &callback, errors); + if (errors.Count() > 0) + { + SaveErrors(errorFilePath, errors); + return; + } + + if (auto compiled = WriteWorkflowScript(precompiledFolder, L"Workflow/InstanceClass", scriptFilePath)) + { + if (cppOutput) + { + PrintSuccessMessage(L"gacgen> Generating C++ source code ..."); + auto input = Ptr(new WfCppInput(cppOutput->name)); + input->multiFile = WfCppFileSwitch::Enabled; + input->reflection = WfCppFileSwitch::Enabled; + input->comment = L"GacGen.exe " + FilePath(inputPath).GetName(); + input->defaultFileName = cppOutput->name + L"PartialClasses"; + input->includeFileName = cppOutput->name; + CopyFrom(input->normalIncludes, cppOutput->normalIncludes); + CopyFrom(input->reflectionIncludes, cppOutput->reflectionIncludes); + + FilePath cppFolder = workingDir / cppOutput->sourceFolder; + if (partialMode) + { + File(logFolderPath / L"CppOutput.txt").WriteAllText(cppFolder.GetFullPath(), false, BomEncoder::Mbcs); + + cppFolder = logFolderPath / L"Source"; + if (!Folder(cppFolder).Create(true)) + { + PrintSuccessMessage(L"gacgen> Unable to create source folder : " + cppFolder.GetFullPath()); + } + } + + auto output = WriteCppCodesToFile(resource, compiled, input, cppFolder, errors); + if (errors.Count() > 0) + { + SaveErrors(errorFilePath, errors); + return; + } + + if (cppOutput->cppResource != L"") + { + WriteEmbeddedResource(resource, input, output, false, cppFolder / cppOutput->cppResource); + } + if (cppOutput->cppCompressed != L"") + { + WriteEmbeddedResource(resource, input, output, true, cppFolder / cppOutput->cppCompressed); + } + } + + for (auto filePath : cppResourcePaths) + { + PrintSuccessMessage(L"Generating binary resource file (no script): " + filePath.GetFullPath()); + WriteBinaryResource(resource, false, false, filePath, {}); + } + for (auto filePath : cppCompressedPaths) + { + PrintSuccessMessage(L"Generating compressed resource file (no script): " + filePath.GetFullPath()); + WriteBinaryResource(resource, true, false, filePath, {}); + } + for (auto filePath : resResourcePaths) + { + PrintSuccessMessage(L"Generating binary resource files : " + filePath.GetFullPath()); + WriteBinaryResource(resource, false, true, filePath, {}); + } + for (auto filePath : resCompressedPaths) + { + PrintSuccessMessage(L"Generating compressed resource files : " + filePath.GetFullPath()); + WriteBinaryResource(resource, true, true, filePath, {}); + } + for (auto filePath : resAssemblyPaths) + { + PrintSuccessMessage(L"Generating assembly files : " + filePath.GetFullPath()); + WriteBinaryResource(resource, false, false, {}, filePath); + } + + if (partialMode) + { + List lines; + List* outputPaths[] = + { + &cppResourcePaths, + &cppCompressedPaths, + &resResourcePaths, + &resCompressedPaths, + &resAssemblyPaths, + }; + + for (vint i = 0; i < sizeof(outputPaths) / sizeof(*outputPaths); i++) + { + auto& paths = *outputPaths[i]; + if (paths.Count() == 2) + { + lines.Add(L"copy \"" + paths[0].GetFullPath() + L"\" \"" + paths[1].GetFullPath() + L"\""); + } + } + + if (lines.Count() > 0) + { + File(logFolderPath / L"Deploy.bat").WriteAllLines(lines, false, BomEncoder::Mbcs); + } + } + } +} + +void DumpResource(FilePath inputPath, FilePath outputPath) +{ + PrintSuccessMessage(L"gacgen> Dumping : " + inputPath.GetFullPath()); + auto loadConfigResult = LoadConfig(inputPath); + if (!loadConfigResult.resource) return; + + auto doc = Ptr(new XmlDocument); + auto xmlRoot = Ptr(new XmlElement); + xmlRoot->name.value = L"ResourceMetadata"; + doc->rootElement = xmlRoot; + + xmlRoot->subNodes.Add(loadConfigResult.resource->GetMetadata()->SaveToXml()); + { + SortedList paths; + List> folders; + folders.Add(loadConfigResult.resource); + for (vint i = 0; i < folders.Count(); i++) + { + auto currentFolder = folders[i]; + if (currentFolder->GetFileAbsolutePath() != L"" && !paths.Contains(currentFolder->GetFileAbsolutePath())) + { + paths.Add(currentFolder->GetFileAbsolutePath()); + } + + for (auto item : currentFolder->GetItems()) + { + if (item->GetFileAbsolutePath() != L"" && !paths.Contains(item->GetFileAbsolutePath())) + { + paths.Add(item->GetFileAbsolutePath()); + } + } + + for (auto folder : currentFolder->GetFolders()) + { + folders.Add(folder); + } + } + + auto xmlInputs = Ptr(new XmlElement); + xmlInputs->name.value = L"Inputs"; + xmlRoot->subNodes.Add(xmlInputs); + + for (auto path : paths) + { + auto xmlInput = Ptr(new XmlElement); + xmlInput->name.value = L"Input"; + xmlInputs->subNodes.Add(xmlInput); + { + auto attr = Ptr(new XmlAttribute); + attr->name.value = L"Path"; + attr->value.value = path; + xmlInput->attributes.Add(attr); + } + } + } + { + SortedList paths; + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"Resource.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"Compressed.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"ScriptedResource.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"ScriptedCompressed.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"Assembly.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"Resource.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"Compressed.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"ScriptedResource.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"ScriptedCompressed.bin").GetFullPath()); + paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"Assembly.bin").GetFullPath()); + + auto xmlOutputs = Ptr(new XmlElement); + xmlOutputs->name.value = L"Outputs"; + xmlRoot->subNodes.Add(xmlOutputs); + + for (auto path : paths) + { + auto xmlOutput = Ptr(new XmlElement); + xmlOutput->name.value = L"Output"; + xmlOutputs->subNodes.Add(xmlOutput); + { + auto attr = Ptr(new XmlAttribute); + attr->name.value = L"Path"; + attr->value.value = path; + xmlOutput->attributes.Add(attr); + } + } + } + + { + FileStream fileStream(outputPath.GetFullPath(), FileStream::WriteOnly); + if (!fileStream.IsAvailable()) + { + PrintErrorMessage(L"error> Failed to write file: " + outputPath.GetFullPath()); + return; + } + + BomEncoder encoder(BomEncoder::Utf8); + EncoderStream encoderStream(fileStream, encoder); + StreamWriter writer(encoderStream); + XmlPrint(doc, writer); + } +} + +class GuiReflectionPlugin : public Object, public IGuiPlugin +{ +public: + + GUI_PLUGIN_NAME(GacUI_Instance_Reflection) + { + } + + void Load()override + { + const wchar_t* BINARY_NAME = nullptr; + switch (targetCpuArchitecture) + { + case GuiResourceCpuArchitecture::x86: + BINARY_NAME = L"Reflection32.bin"; + break; + case GuiResourceCpuArchitecture::x64: + BINARY_NAME = L"Reflection64.bin"; + break; + default:; + } + + FilePath exeFolder = FilePath(executablePath).GetFolder(); + FilePath metadataFolder = exeFolder; + { + File metadataOverride = exeFolder / L"Metadata.txt"; + if (metadataOverride.Exists()) + { + auto path = metadataOverride.ReadAllTextByBom(); + metadataFolder = exeFolder / path; + } + } + FilePath binaryPath = metadataFolder / BINARY_NAME; + + if (!File(binaryPath).Exists()) + { + Console::WriteLine(L"Unable to find the GacUI type metadata file at: " + binaryPath.GetFullPath()); + CHECK_FAIL(L"Unable to find the GacUI type metadata file!"); + } + +#define INSTALL_SERIALIZABLE_TYPE(TYPE) serializableTypes.Add(TypeInfo::content.typeName, Ptr(new SerializableType)); + collections::Dictionary> serializableTypes; + REFLECTION_PREDEFINED_SERIALIZABLE_TYPES(INSTALL_SERIALIZABLE_TYPE) + INSTALL_SERIALIZABLE_TYPE(Color) + INSTALL_SERIALIZABLE_TYPE(GlobalStringKey) + INSTALL_SERIALIZABLE_TYPE(DocumentFontSize) + + FileStream fileStream(binaryPath.GetFullPath(), FileStream::ReadOnly); + auto typeLoader = LoadMetaonlyTypes(fileStream, serializableTypes); + auto tm = GetGlobalTypeManager(); + tm->AddTypeLoader(typeLoader); +#undef INSTALL_SERIALIZABLE_TYPE + } + + void Unload()override + { + } +}; +GUI_REGISTER_PLUGIN(GuiReflectionPlugin) + +void GuiMain() +{ + Console::SetTitle(L"Vczh GacUI Resource Code Generator for C++"); + + if (arguments->Count() > 0) + { + if (arguments->Get(0) == L"/P32" || arguments->Get(0) == L"/P64") + { + switch (arguments->Count()) + { + case 2: + CompileResource(true, arguments->Get(1), {}); + return; + case 3: + CompileResource(true, arguments->Get(1), { arguments->Get(2) }); + return; + } + } + else if (arguments->Get(0) == L"/D32" || arguments->Get(0) == L"/D64") + { + switch (arguments->Count()) + { + case 3: + DumpResource(arguments->Get(1), arguments->Get(2)); + return; + } + } + else if (arguments->Get(0) == L"/C32" || arguments->Get(0) == L"/C64") + { + switch (arguments->Count()) + { + case 2: + CompileResource(false, arguments->Get(1), {}); + return; + } + } + } + + PrintErrorMessage(L"Usage"); + PrintErrorMessage(L" Compile for x86 or x64 for GacBuild.ps1:"); + PrintErrorMessage(L" GacGen.exe /P32 []"); + PrintErrorMessage(L" GacGen.exe /P64 []"); + PrintErrorMessage(L" Dump for x86 or x64 for GacBuild.ps1:"); + PrintErrorMessage(L" GacGen.exe /D32 "); + PrintErrorMessage(L" GacGen.exe /D64 "); + PrintErrorMessage(L" Compile for x86 or x64 if you only want one architecture:"); + PrintErrorMessage(L" GacGen.exe /C32 "); + PrintErrorMessage(L" GacGen.exe /C64 "); +} diff --git a/Tools/Executables/GlrParserGen/GlrParserGen.vcxproj b/Tools/Executables/GlrParserGen/GlrParserGen.vcxproj new file mode 100644 index 00000000..eab45b6e --- /dev/null +++ b/Tools/Executables/GlrParserGen/GlrParserGen.vcxproj @@ -0,0 +1,187 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {f5a36ed2-78e7-4776-b1ab-63de18376f30} + GlrParserGen + 10.0 + + + + Application + true + v143 + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + Application + true + v143 + Unicode + x64 + + + Application + false + v143 + true + Unicode + x64 + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + false + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + true + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + false + $(ProjectDir)..\..\..\Import;$(ProjectDir)..\..\..\Release;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + true + true + + + + + + true + true + true + true + + + + + + + true + true + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tools/Executables/GlrParserGen/GlrParserGen.vcxproj.filters b/Tools/Executables/GlrParserGen/GlrParserGen.vcxproj.filters new file mode 100644 index 00000000..2f650b41 --- /dev/null +++ b/Tools/Executables/GlrParserGen/GlrParserGen.vcxproj.filters @@ -0,0 +1,75 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {75a6cb7c-b54a-4fdd-9697-0f429173e83a} + + + + + Source Files + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + + + Import + + + Import + + + Import + + + Import + + + Import + + + Import + + + \ No newline at end of file diff --git a/Tools/Executables/GlrParserGen/Main.cpp b/Tools/Executables/GlrParserGen/Main.cpp new file mode 100644 index 00000000..3cfb3325 --- /dev/null +++ b/Tools/Executables/GlrParserGen/Main.cpp @@ -0,0 +1,440 @@ +#include + +using namespace vl; +using namespace vl::console; +using namespace vl::collections; +using namespace vl::stream; +using namespace vl::filesystem; +using namespace vl::regex; +using namespace vl::glr; +using namespace vl::glr::automaton; +using namespace vl::glr::parsergen; +using namespace vl::glr::xml; + +#define EXIT_ERROR(MESSAGE)\ + do\ + {\ + Console::SetColor(true, false, false, true);\ + Console::WriteLine(MESSAGE);\ + Console::SetColor(true, true, true, false);\ + return 1;\ + } while(false) + +#define EXIT_IF_PARSER_FAIL(ERRORS, TITLE)\ + do\ + {\ + if (ERRORS.Count() > 0)\ + {\ + Console::SetColor(true, false, false, true);\ + Console::WriteLine(TITLE);\ + PrintParsingErrors(ERRORS);\ + Console::SetColor(true, true, true, false);\ + return 1;\ + }\ + } while(false) + +#define EXIT_IF_COMPILE_FAIL(GLOBAL)\ + do\ + {\ + if (GLOBAL.Errors().Count() > 0)\ + {\ + Console::SetColor(true, false, false, true);\ + Console::WriteLine(L"Failed to compile the syntax:");\ + PrintCompileErrors(GLOBAL);\ + Console::SetColor(true, true, true, false);\ + return 1;\ + }\ + } while(false) + +#define READ_ATTRIBUTE(STORAGE, ELEMENT, NAME, PATH)\ + do\ + {\ + if (auto attributeToRead = XmlGetAttribute(ELEMENT, NAME))\ + {\ + STORAGE = attributeToRead->value.value;\ + }\ + else\ + {\ + EXIT_ERROR(L"Missing " PATH L".");\ + }\ + } while(false)\ + +#define READ_ELEMENT(STORAGE, ELEMENT, NAME, PATH)\ + do\ + {\ + if (auto elementToRead = XmlGetElement(ELEMENT, NAME))\ + {\ + STORAGE = XmlGetValue(elementToRead);\ + }\ + else\ + {\ + EXIT_ERROR(L"Missing " PATH L".");\ + }\ + } while(false)\ + +#define READ_ELEMENT_ITEMS(STORAGE, REGEX, ELEMENT, NAME, PATH)\ + do\ + {\ + if (auto elementToRead = XmlGetElement(ELEMENT, NAME))\ + {\ + auto value = XmlGetValue(elementToRead);\ + if (auto match = REGEX.MatchHead(value))\ + {\ + for (auto item : match->Groups()[REGEX.CaptureNames().IndexOf(L"item")])\ + {\ + STORAGE.Add(item.Value());\ + }\ + }\ + else\ + {\ + EXIT_ERROR(L"Incorrect namespace format in: " PATH L".");\ + }\ + }\ + else\ + {\ + EXIT_ERROR(L"Missing " PATH L".");\ + }\ + } while(false)\ + +void PrintParsingErrors(List& errors) +{ + for (auto error : errors) + { + Console::WriteLine( + L"[row:" + itow(error.codeRange.start.row + 1) + L"]" + L"[column:" + itow(error.codeRange.start.column + 1) + L"]" + L": " + error.message); + } +} + +void PrintCompileErrors(ParserSymbolManager& global) +{ + for (auto error : global.Errors()) + { + switch (error.location.type) + { + case ParserDefFileType::Ast: + Console::Write(L"[Ast:" + error.location.name + L"]"); + break; + case ParserDefFileType::Lexer: + Console::Write(L"[Lexer]"); + break; + case ParserDefFileType::Syntax: + Console::Write(L"[Syntax:" + error.location.name + L"]"); + break; + } + + Console::Write( + L"[row:" + itow(error.location.codeRange.start.row + 1) + L"]" + L"[column:" + itow(error.location.codeRange.start.column + 1) + L"]"); + +#define CASE_1(LABEL, P1, ...)\ + Console::WriteLine(\ + L"[" L ## #LABEL L"]"\ + L"[" L ## #P1 L":" + error.arg1 + L"]"\ + );\ + +#define CASE_2(LABEL, P1, P2, ...)\ + Console::WriteLine(\ + L"[" L ## #LABEL L"]"\ + L"[" L ## #P1 L":" + error.arg1 + L"]"\ + L"[" L ## #P2 L":" + error.arg2 + L"]"\ + );\ + +#define CASE_3(LABEL, P1, P2, P3, ...)\ + Console::WriteLine(\ + L"[" L ## #LABEL L"]"\ + L"[" L ## #P1 L":" + error.arg1 + L"]"\ + L"[" L ## #P2 L":" + error.arg2 + L"]"\ + L"[" L ## #P3 L":" + error.arg3 + L"]"\ + );\ + +#define CASE_4(LABEL, P1, P2, P3, P4, ...)\ + Console::WriteLine(\ + L"[" L ## #LABEL L"]"\ + L"[" L ## #P1 L":" + error.arg1 + L"]"\ + L"[" L ## #P2 L":" + error.arg2 + L"]"\ + L"[" L ## #P3 L":" + error.arg3 + L"]"\ + L"[" L ## #P4 L":" + error.arg4 + L"]"\ + );\ + +#define CASE_CALL2(ARG1, ARG2, ARG3, ARG4, ARG5, FUNC, ...)\ + FUNC(ARG1, ARG2, ARG3, ARG4, ARG5) + +#define CASE_CALL(ARGS)\ + CASE_CALL2 ARGS + +#define CASE(LABEL, ...)\ + case ParserErrorType::LABEL:\ + CASE_CALL((LABEL, __VA_ARGS__, CASE_4, CASE_3, CASE_2, CASE_1))\ + break;\ + + + switch (error.type) + { + GLR_PARSER_ERROR_LIST(CASE) + default: + Console::WriteLine(L""); + } + +#undef CASE +#undef CASE_CALL +#undef CASE_CALL2 +#undef CASE_4 +#undef CASE_3 +#undef CASE_2 +#undef CASE_1 + } +} + +int main(int argc, char* argv[]) +{ + Console::SetTitle(L"Vczh GLR ParserGen for C++"); + if (argc != 2) + { + Console::SetColor(true, false, false, true); + Console::WriteLine(L"GlrParserGen.exe "); + Console::SetColor(true, true, true, false); + return 0; + } + + auto workingDir = FilePath(atow(argv[1])).GetFolder(); + Ptr config; + { + Parser parser; + List errors; + InstallDefaultErrorMessageGenerator(parser, errors); + auto text = File(atow(argv[1])).ReadAllTextByBom(); + config = XmlParseDocument(text, parser); + EXIT_IF_PARSER_FAIL(errors, L"Failed to read the input XML file."); + } + + if (config->rootElement->name.value != L"Parser") EXIT_ERROR(L"Missing /Parser."); + + ParserSymbolManager global; + AstSymbolManager astManager(global); + LexerSymbolManager lexerManager(global); + SyntaxSymbolManager syntaxManager(global); + Executable executable; + Metadata metadata; + + FilePath generatedDir; + Dictionary files; + + Regex regexNamespace(L"^([^:]+)(::([^:]+))*$"); + Regex regexIncludes(L"^([^;]+)(;([^;]+))*$"); + auto indexItem = regexNamespace.CaptureNames().IndexOf(L"item"); + + READ_ATTRIBUTE(global.name, config->rootElement, L"name", L"/Parser@name"); + READ_ELEMENT_ITEMS(global.includes, regexIncludes, config->rootElement, L"Includes", L"/Parser/Includes"); + READ_ELEMENT_ITEMS(global.cppNss, regexNamespace, config->rootElement, L"CppNamespace", L"/Parser/CppNamespace"); + READ_ELEMENT(global.headerGuard, config->rootElement, L"HeaderGuard", L"/Parser/HeaderGuard"); + { + WString outputDir; + READ_ELEMENT(outputDir, config->rootElement, L"OutputDir", L"/Parser/OutputDir"); + generatedDir = workingDir / outputDir; + } + + auto output = GenerateParserFileNames(global); + + TypeParser typeParser; + RuleParser ruleParser; + List errors; + InstallDefaultErrorMessageGenerator(typeParser, errors); + InstallDefaultErrorMessageGenerator(ruleParser, errors); + + if (auto elementAsts = XmlGetElement(config->rootElement, L"Asts")) + { + List> asts; + CopyFrom(asts, XmlGetElements(elementAsts, L"Ast")); + if (asts.Count() == 0) EXIT_ERROR(L"Missing /Parser/Asts/Ast"); + + for (auto elementAst : asts) + { + WString name, file; + READ_ATTRIBUTE(name, elementAst, L"name", L"/Parser/Asts/Ast@name"); + READ_ATTRIBUTE(file, elementAst, L"file", L"/Parser/Asts/Ast@file[@name=\"" + name + L"\"]"); + Console::WriteLine(L"Processing " + file + L" ..."); + + File astFile = workingDir / file; + if (!astFile.Exists()) EXIT_ERROR(L"Missing ast definition file: " + astFile.GetFilePath().GetFullPath()); + auto astInput = astFile.ReadAllTextByBom(); + auto ast = typeParser.ParseFile(astInput); + EXIT_IF_PARSER_FAIL(errors, L"Syntax errors found in file: " + astFile.GetFilePath().GetFullPath()); + + auto astDefFile = astManager.CreateFile(name); + READ_ELEMENT_ITEMS(astDefFile->cppNss, regexNamespace, elementAst, L"CppNamespace", L"/Parser/Asts/Ast@file[@name=\"" + name + L"\"]/CppNamespace"); + READ_ELEMENT_ITEMS(astDefFile->refNss, regexNamespace, elementAst, L"ReflectionNamespace", L"/Parser/Asts/Ast@file[@name=\"" + name + L"\"]/ReflectionNamespace"); + READ_ELEMENT(astDefFile->classPrefix, elementAst, L"ClassPrefix", L"/Parser/Asts/Ast@file[@name=\"" + name + L"\"]/ClassPrefix"); + CompileAst(astManager, astDefFile, ast); + } + + EXIT_IF_COMPILE_FAIL(global); + GenerateAstFileNames(astManager, output); + WriteAstFiles(astManager, output, files); + } + else + { + EXIT_ERROR(L"Missing /Parser/Asts"); + } + + if (auto elementLexer = XmlGetElement(config->rootElement, L"Lexer")) + { + WString file; + READ_ATTRIBUTE(file, elementLexer, L"file", L"/Parser/Lexer@file"); + Console::WriteLine(L"Processing " + file + L" ..."); + + File lexerFile = workingDir / file; + auto lexerInput = lexerFile.ReadAllTextByBom(); + CompileLexer(lexerManager, lexerInput); + + EXIT_IF_COMPILE_FAIL(global); + WriteLexerFiles(lexerManager, output, files); + } + else + { + EXIT_ERROR(L"Missing /Parser/Asts"); + } + + if (auto elementSyntax = XmlGetElement(config->rootElement, L"Syntax")) + { + WString name, file; + READ_ATTRIBUTE(name, elementSyntax, L"name", L"/Parser/Syntax@name"); + READ_ATTRIBUTE(file, elementSyntax, L"file", L"/Parser/Syntax@file[@name=\"" + name + L"\"]"); + Console::WriteLine(L"Processing " + file + L" ..."); + + File syntaxFile = workingDir / file; + auto syntaxInput = syntaxFile.ReadAllTextByBom(); + auto syntax = ruleParser.ParseFile(syntaxInput); + EXIT_IF_PARSER_FAIL(errors, L"Syntax errors found in file: " + syntaxFile.GetFilePath().GetFullPath()); + + List> syntaxFiles; + syntaxFiles.Add(syntax); + syntaxManager.name = name; + CompileSyntax(astManager, lexerManager, syntaxManager, output, syntaxFiles); + EXIT_IF_COMPILE_FAIL(global); + + syntaxManager.BuildCompactNFA(); + EXIT_IF_COMPILE_FAIL(global); + syntaxManager.BuildCrossReferencedNFA(); + EXIT_IF_COMPILE_FAIL(global); + syntaxManager.BuildAutomaton(lexerManager.Tokens().Count(), executable, metadata); + EXIT_IF_COMPILE_FAIL(global); + + for (auto elementExport : XmlGetElements(elementSyntax, L"Export")) + { + WString rule; + READ_ATTRIBUTE(rule, elementExport, L"rule", L"/Parser/Syntax@file[@name=\"" + name + L"\"]/Export@rule"); + + vint index = syntaxManager.Rules().Keys().IndexOf(rule); + if (index == -1) + { + EXIT_ERROR(L"Rule \"" + rule + L"\" is not defined in the syntax."); + } + + auto ruleSymbol = syntaxManager.Rules().Values()[index]; + syntaxManager.parsableRules.Add(ruleSymbol); + + if (auto attType = XmlGetAttribute(elementExport, L"type")) + { + syntaxManager.ruleTypes.Add(ruleSymbol, attType->value.value); + } + else + { + auto classSymbol = ruleSymbol->ruleType; + auto classFile = classSymbol->Owner(); + auto type = + From(classFile->cppNss) + .Reverse() + .Aggregate( + classFile->classPrefix + classSymbol->Name(), + [](auto&& a, auto&& b) { return b + L"::" + a; } + ); + syntaxManager.ruleTypes.Add(ruleSymbol, type); + } + } + + GenerateSyntaxFileNames(syntaxManager, output); + WriteSyntaxFiles(syntaxManager, executable, metadata, output, files); + } + else + { + EXIT_ERROR(L"Missing /Parser/Asts"); + } + + { + auto elementAsts = XmlGetElement(config->rootElement, L"Asts"); + for (auto elementAst : XmlGetElements(elementAsts, L"Ast")) + { + auto name = XmlGetAttribute(elementAst, L"name")->value.value; + auto astOutput = output->astOutputs[astManager.Files()[name]]; + + if (auto elementBlocked = XmlGetElement(elementAst, L"BlockedUtilities")) + { + for (auto elementUtility : XmlGetElements(elementBlocked)) + { + auto utility = elementUtility->name.value; + if (utility == L"Empty") + { + files.Remove(astOutput->emptyH); + files.Remove(astOutput->emptyCpp); + } + else if (utility == L"Copy") + { + files.Remove(astOutput->copyH); + files.Remove(astOutput->copyCpp); + } + else if (utility == L"Traverse") + { + files.Remove(astOutput->traverseH); + files.Remove(astOutput->traverseCpp); + } + else if (utility == L"Json") + { + files.Remove(astOutput->jsonH); + files.Remove(astOutput->jsonCpp); + } + else if (utility == L"Builder") + { + files.Remove(astOutput->builderH); + files.Remove(astOutput->builderCpp); + } + else + { + EXIT_ERROR(L"Unknown utility \"" + utility + L"\" in /Parser/Asts/Ast[@name=\"" + name + L"\"]/BlockedUtilities/*"); + } + } + } + } + } + + { + if (!Folder(generatedDir).Exists()) + { + Folder(generatedDir).Create(true); + } + + for (auto [key, index] : indexed(files.Keys())) + { + File outputFile = generatedDir / key; + auto content = files.Values()[index]; + if (outputFile.Exists()) + { + auto existing = outputFile.ReadAllTextByBom(); + if (content == existing) + { + Console::SetColor(true, true, false, true); + Console::WriteLine(outputFile.GetFilePath().GetFullPath()); + Console::SetColor(true, true, true, false); + continue; + } + } + + Console::SetColor(false, true, false, true); + Console::WriteLine(outputFile.GetFilePath().GetFullPath()); + Console::SetColor(true, true, true, false); + outputFile.WriteAllText(content, false, BomEncoder::Utf8); + } + } + return 0; +} \ No newline at end of file