mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-02-06 03:42:11 +08:00
427 lines
12 KiB
C++
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;
|
|
} |