Files
GacUI/Tools/Executables/GlrParserGen/Main.cpp
2025-12-22 05:29:21 -08:00

427 lines
12 KiB
C++

#include <VlppGlrParserCompiler.h>
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<ParsingError>& 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"<UNKNOWN-ERROR>");
}
#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 <config-xml>");
Console::SetColor(true, true, true, false);
return 0;
}
auto workingDir = FilePath(atow(argv[1])).GetFolder();
Ptr<XmlDocument> config;
{
Parser parser;
List<ParsingError> 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<WString, WString> files;
Regex regexNamespace(L"^(<item>[^:]+)(::(<item>[^:]+))*$");
Regex regexIncludes(L"^(<item>[^;]+)(;(<item>[^;]+))*$");
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<ParsingError> errors;
InstallDefaultErrorMessageGenerator(typeParser, errors);
InstallDefaultErrorMessageGenerator(ruleParser, errors);
if (auto elementAsts = XmlGetElement(config->rootElement, L"Asts"))
{
List<Ptr<XmlElement>> 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<Pair<AstDefFile*, Ptr<GlrAstFile>>> 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<Ptr<GlrSyntaxFile>> 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;
}