#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::AstGroup: Console::Write(L"[AstGroup:" + error.location.name + L"]"); break; 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.astIncludes, 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; READ_ATTRIBUTE(name, elementAst, L"name", L"/Parser/Asts/Ast@name"); auto astDefFileGroup = astManager.CreateFileGroup(name); READ_ELEMENT_ITEMS(astDefFileGroup->cppNss, regexNamespace, elementAst, L"CppNamespace", L"/Parser/Asts/Ast@file[@name=\"" + name + L"\"]/CppNamespace"); READ_ELEMENT_ITEMS(astDefFileGroup->refNss, regexNamespace, elementAst, L"ReflectionNamespace", L"/Parser/Asts/Ast@file[@name=\"" + name + L"\"]/ReflectionNamespace"); READ_ELEMENT(astDefFileGroup->classPrefix, elementAst, L"ClassPrefix", L"/Parser/Asts/Ast@file[@name=\"" + name + L"\"]/ClassPrefix"); List>> astFiles; for (auto elementFile : XmlGetElements(elementAst, L"File")) { WString file; READ_ATTRIBUTE(file, elementFile, L"file", L"/Parser/Asts/Ast/File@file"); 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 = astDefFileGroup->CreateFile(file); astFiles.Add({ astDefFile,ast }); } if (astFiles.Count() == 0) EXIT_ERROR(L"Missing /Parser/Asts/Ast/File"); CompileAst(astManager, astFiles); } 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/Lexer"); } if (auto elementSyntax = XmlGetElement(config->rootElement, L"Syntax")) { WString name; READ_ATTRIBUTE(name, elementSyntax, L"name", L"/Parser/Syntax@name"); syntaxManager.name = name; List> syntaxFiles; for (auto elementFile : XmlGetElements(elementSyntax, L"File")) { WString file; READ_ATTRIBUTE(file, elementFile, L"file", L"/Parser/Syntax/File@file"); 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()); syntaxFiles.Add(syntax); } if (syntaxFiles.Count() == 0) EXIT_ERROR(L"Missing /Parser/Syntax/File"); 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); GenerateSyntaxFileNames(syntaxManager, output); WriteSyntaxFiles(syntaxManager, executable, metadata, output, files); } else { EXIT_ERROR(L"Missing /Parser/Syntax"); } { 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.FileGroups()[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; }