mirror of
https://github.com/jorenchik/mdemory.git
synced 2026-03-22 00:26:21 +00:00
restrutured removing the go source
This commit is contained in:
3
src/transpiler/.gitignore
vendored
Normal file
3
src/transpiler/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Debug
|
||||
Release
|
||||
.cache
|
||||
16
src/transpiler/.vscode/launch.json
vendored
Normal file
16
src/transpiler/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"name": "C/C++: gcc.exe build and debug active file",
|
||||
"request": "launch",
|
||||
"program": "./Debug/transpiler",
|
||||
"args": ["input.mdem"],
|
||||
"cwd": "${fileDirname}"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
src/transpiler/.vscode/settings.json
vendored
Normal file
5
src/transpiler/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"cmake.configureOnOpen": true,
|
||||
"cmake.generator": "Unix Makefiles", // might use Ninja too
|
||||
"cmake.buildDirectory": "${workspaceFolder}/build"
|
||||
}
|
||||
12
src/transpiler/.vscode/tasks.json
vendored
Normal file
12
src/transpiler/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build transpiler",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build Debug",
|
||||
],
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
22
src/transpiler/CMakeLists.txt
Normal file
22
src/transpiler/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
# Set sources
|
||||
set(
|
||||
SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_library(
|
||||
api
|
||||
lexer.cpp
|
||||
parser.cpp
|
||||
time.cpp
|
||||
api.cpp
|
||||
stringUtils.cpp
|
||||
)
|
||||
|
||||
add_executable(transpiler ${SOURCES})
|
||||
target_link_libraries(transpiler api)
|
||||
|
||||
target_compile_options(transpiler PRIVATE -Wall -Wextra -Wpedantic)
|
||||
|
||||
target_include_directories(transpiler PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_include_directories(api PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
127
src/transpiler/api.cpp
Normal file
127
src/transpiler/api.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include "api.h"
|
||||
#include "result.h"
|
||||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
#include "time.h"
|
||||
#include "config.h"
|
||||
|
||||
#define TABWIDTH 4
|
||||
|
||||
bool debug;
|
||||
std::chrono::high_resolution_clock::time_point start;
|
||||
std::chrono::high_resolution_clock::time_point end;
|
||||
|
||||
std::string wrapText(std::string text, size_t width) {
|
||||
std::string result;
|
||||
size_t currentLineLength = 0;
|
||||
size_t wordStart = 0;
|
||||
|
||||
size_t preservedSpaceCount = 0;
|
||||
for (size_t i = 0; i < text.length(); ++i) {
|
||||
if (text[i] == '\t') {
|
||||
preservedSpaceCount += TABWIDTH;
|
||||
} else if (!std::isalnum(text[i])) {
|
||||
preservedSpaceCount++;
|
||||
} else {
|
||||
wordStart = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result += text.substr(0, wordStart);
|
||||
currentLineLength = preservedSpaceCount;
|
||||
|
||||
for (size_t i = wordStart; i < text.length(); ++i) {
|
||||
if (text[i] == ' ' || i == text.length() - 1) {
|
||||
size_t wordEnd = (i == text.length() - 1) ? i + 1 : i; // Handle the last word
|
||||
size_t wordLength = wordEnd - wordStart;
|
||||
if (currentLineLength + wordLength > width) {
|
||||
result += '\n';
|
||||
result.append(preservedSpaceCount / TABWIDTH, '\t');
|
||||
result.append(preservedSpaceCount % TABWIDTH, ' ');
|
||||
currentLineLength = preservedSpaceCount;
|
||||
}
|
||||
result += text.substr(wordStart, wordLength) + ' ';
|
||||
currentLineLength += wordLength + 1;
|
||||
wordStart = i + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string escapeText(std::string text) {
|
||||
std::stringstream ss;
|
||||
for (auto c: text) {
|
||||
switch(c) {
|
||||
case '\\':
|
||||
ss << "\\\\";
|
||||
break;
|
||||
case '[':
|
||||
ss << "\\[";
|
||||
break;
|
||||
case ']':
|
||||
ss << "\\]";
|
||||
break;
|
||||
case '-':
|
||||
ss << "\\-";
|
||||
break;
|
||||
case '^':
|
||||
ss << "\\^";
|
||||
break;
|
||||
case ':':
|
||||
ss << "\\:";
|
||||
break;
|
||||
case '>':
|
||||
ss << "\\>";
|
||||
break;
|
||||
case '+':
|
||||
ss << "\\+";
|
||||
break;
|
||||
default:
|
||||
ss << c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Result<ParseInfo> transpile(std::string fileContent, bool isDebug) {
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
debug = isDebug;
|
||||
|
||||
auto lexRes = tokenizeMdem(fileContent);
|
||||
auto tokens = lexRes.value;
|
||||
if (lexRes.error.length() > 0) {
|
||||
return {
|
||||
{},
|
||||
std::format(
|
||||
"Lexical analysis error: {}",
|
||||
lexRes.error
|
||||
),
|
||||
lexRes.row,
|
||||
lexRes.column
|
||||
};
|
||||
}
|
||||
|
||||
auto parseRes = parseQuestions(tokens);
|
||||
auto questions = parseRes.value;
|
||||
if (parseRes.error.length() > 0) {
|
||||
return {
|
||||
{},
|
||||
std::format(
|
||||
"Parsing error: {}",
|
||||
parseRes.error
|
||||
),
|
||||
parseRes.row,
|
||||
parseRes.column
|
||||
};
|
||||
}
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
showTime("Transpilation time");
|
||||
return {questions};
|
||||
}
|
||||
32
src/transpiler/compile_commands.json
Normal file
32
src/transpiler/compile_commands.json
Normal file
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"directory": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/transpiler",
|
||||
"command": "/usr/bin/g++ -I/home/jorenchik/Code/mdemory/src/cpp/include -std=gnu++20 -Wall -Wextra -Wpedantic -o CMakeFiles/transpiler.dir/main.cpp.o -c /home/jorenchik/Code/mdemory/src/cpp/transpiler/main.cpp",
|
||||
"file": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/main.cpp",
|
||||
"output": "transpiler/CMakeFiles/transpiler.dir/main.cpp.o"
|
||||
},
|
||||
{
|
||||
"directory": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/transpiler",
|
||||
"command": "/usr/bin/g++ -I/home/jorenchik/Code/mdemory/src/cpp/include -std=gnu++20 -Wall -Wextra -Wpedantic -o CMakeFiles/transpiler.dir/lexer.cpp.o -c /home/jorenchik/Code/mdemory/src/cpp/transpiler/lexer.cpp",
|
||||
"file": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/lexer.cpp",
|
||||
"output": "transpiler/CMakeFiles/transpiler.dir/lexer.cpp.o"
|
||||
},
|
||||
{
|
||||
"directory": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/transpiler",
|
||||
"command": "/usr/bin/g++ -I/home/jorenchik/Code/mdemory/src/cpp/include -std=gnu++20 -Wall -Wextra -Wpedantic -o CMakeFiles/transpiler.dir/parser.cpp.o -c /home/jorenchik/Code/mdemory/src/cpp/transpiler/parser.cpp",
|
||||
"file": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/parser.cpp",
|
||||
"output": "transpiler/CMakeFiles/transpiler.dir/parser.cpp.o"
|
||||
},
|
||||
{
|
||||
"directory": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/transpiler",
|
||||
"command": "/usr/bin/g++ -I/home/jorenchik/Code/mdemory/src/cpp/include -std=gnu++20 -Wall -Wextra -Wpedantic -o CMakeFiles/transpiler.dir/time.cpp.o -c /home/jorenchik/Code/mdemory/src/cpp/transpiler/time.cpp",
|
||||
"file": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/time.cpp",
|
||||
"output": "transpiler/CMakeFiles/transpiler.dir/time.cpp.o"
|
||||
},
|
||||
{
|
||||
"directory": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/transpiler",
|
||||
"command": "/usr/bin/g++ -I/home/jorenchik/Code/mdemory/src/cpp/include -std=gnu++20 -Wall -Wextra -Wpedantic -o CMakeFiles/transpiler.dir/api.cpp.o -c /home/jorenchik/Code/mdemory/src/cpp/transpiler/api.cpp",
|
||||
"file": "/home/jorenchik/Code/mdemory/src/cpp/transpiler/api.cpp",
|
||||
"output": "transpiler/CMakeFiles/transpiler.dir/api.cpp.o"
|
||||
}
|
||||
]
|
||||
284
src/transpiler/lexer.cpp
Normal file
284
src/transpiler/lexer.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <format>
|
||||
#include <regex>
|
||||
|
||||
#include "lexer.h"
|
||||
#include "config.h"
|
||||
#include "result.h"
|
||||
|
||||
std::vector<Token> tokens;
|
||||
std::vector<char> buffer;
|
||||
int32_t row;
|
||||
int32_t column;
|
||||
int32_t previousRow;
|
||||
int32_t previousColumn;
|
||||
bool textStarted = false;
|
||||
bool identifierStarted = false;
|
||||
bool sof;
|
||||
|
||||
void trimString(std::string &str, std::string trimChars) {
|
||||
int padSize = 0;
|
||||
bool pad = false;
|
||||
for (size_t i = 0; i < str.size(); ++i) {
|
||||
for (size_t k = 0; k < trimChars.size(); ++k) {
|
||||
if (str[i] == trimChars[k]) {
|
||||
padSize++;
|
||||
pad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pad) {
|
||||
break;
|
||||
}
|
||||
pad = false;
|
||||
}
|
||||
if (padSize > 0) {
|
||||
str.erase(0, padSize);
|
||||
}
|
||||
padSize = 0;
|
||||
pad = false;
|
||||
for (size_t i = str.size(); i-- > 0;) {
|
||||
for (size_t k = 0; k < trimChars.size(); ++k) {
|
||||
if (str[i] == trimChars[k]) {
|
||||
padSize++;
|
||||
pad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pad) {
|
||||
break;
|
||||
}
|
||||
pad = false;
|
||||
}
|
||||
if (padSize > 0) {
|
||||
str.erase(str.end() - padSize, str.end());
|
||||
}
|
||||
}
|
||||
|
||||
void makeTokenWithTokenBuffer(
|
||||
TokenType ttype,
|
||||
size_t tokenLen,
|
||||
TokenType textType
|
||||
) {
|
||||
std::string token(buffer.end() - tokenLen, buffer.end());
|
||||
if (buffer.size() > tokenLen) {
|
||||
std::string prevFragment(buffer.begin(), buffer.end() - tokenLen);
|
||||
trimString(prevFragment, " \n\t");
|
||||
if (prevFragment.length() > 0) {
|
||||
tokens.push_back(Token{
|
||||
textType,
|
||||
prevFragment,
|
||||
previousRow,
|
||||
previousColumn
|
||||
});
|
||||
}
|
||||
}
|
||||
buffer.clear();
|
||||
|
||||
tokens.push_back(Token{
|
||||
ttype,
|
||||
token,
|
||||
row,
|
||||
column
|
||||
});
|
||||
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
Result<std::vector<Token>> tokenizeMdem(const std::string& fileRunes) {
|
||||
row = 1;
|
||||
column = 1;
|
||||
previousRow = 1;
|
||||
previousColumn = 1;
|
||||
textStarted = false;
|
||||
tokens.clear();
|
||||
buffer.clear();
|
||||
|
||||
if (fileRunes.find_first_not_of(" \n\t") == std::string::npos) {
|
||||
return {tokens, ""};
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < fileRunes.size(); ++i) {
|
||||
char c = fileRunes[i];
|
||||
|
||||
// AdvancePointer
|
||||
if (c == '\n') {
|
||||
row += 1;
|
||||
column = 0;
|
||||
}
|
||||
|
||||
if (c == '\\') {
|
||||
i += 1;
|
||||
if (i < fileRunes.size()) {
|
||||
buffer.push_back(fileRunes[i]);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
buffer.push_back(c);
|
||||
}
|
||||
|
||||
// SkipWhitetext
|
||||
if (!textStarted) {
|
||||
if (c == '\n') {
|
||||
previousRow += 1;
|
||||
previousColumn = 1;
|
||||
} else if (c == ' ') {
|
||||
previousColumn += 1;
|
||||
} else if (c == '\t') {
|
||||
previousColumn += 4;
|
||||
} else {
|
||||
textStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// EmitTokens
|
||||
switch (c) {
|
||||
case '[':
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::CooldownStart,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
identifierStarted = true;
|
||||
break;
|
||||
case ']':
|
||||
if (!identifierStarted) {
|
||||
return {
|
||||
tokens,
|
||||
"Cannot end identifier if it is not started",
|
||||
tokens[i].row,
|
||||
tokens[i].column
|
||||
};
|
||||
}
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::CooldownEnd,
|
||||
1,
|
||||
TokenType::Cooldown
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
identifierStarted = false;
|
||||
break;
|
||||
case '-':
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::ElementDashStart,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
break;
|
||||
case '^':
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::ElementOrderModifier,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
break;
|
||||
case ':':
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::MatchGroupEnd,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
break;
|
||||
case '>':
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::QuestionEnd,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
break;
|
||||
case '+':
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::ElementPlusStart,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
column += 1;
|
||||
}
|
||||
|
||||
makeTokenWithTokenBuffer(
|
||||
TokenType::EndOfFile,
|
||||
0,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
|
||||
if (debug) {
|
||||
std::cout << "SECTION: Lexer output:\n";
|
||||
std::cout << std::format("Token count: {}", tokens.size()) << std::endl;
|
||||
for (const Token& token : tokens) {
|
||||
std::cout << token.ToString();
|
||||
}
|
||||
std::cout << "SECTION END: Lexer output\n";
|
||||
}
|
||||
|
||||
return {tokens, ""};
|
||||
}
|
||||
|
||||
std::regex nextLineExp(
|
||||
"\n",
|
||||
std::regex_constants::ECMAScript
|
||||
);
|
||||
|
||||
std::regex doubleSpaceExp(
|
||||
"\\s\\s+",
|
||||
std::regex_constants::ECMAScript
|
||||
);
|
||||
|
||||
std::string Token::ToString(const TokenType* ttype) {
|
||||
switch (*ttype) {
|
||||
case TokenType::TextFragment: return "text fragment";
|
||||
case TokenType::QuestionEnd: return "question end symbol";
|
||||
case TokenType::MatchGroupEnd: return "match group end";
|
||||
case TokenType::ElementDashStart: return "dash element start";
|
||||
case TokenType::ElementOrderModifier: return "order element modifier";
|
||||
case TokenType::ElementPlusStart: return "plus element start";
|
||||
case TokenType::Cooldown: return "cooldown";
|
||||
case TokenType::CooldownStart: return "start of cooldown";
|
||||
case TokenType::CooldownEnd: return "end of cooldown";
|
||||
case TokenType::StartOfFile: return "start of the file";
|
||||
case TokenType::EndOfFile: return "end of file";
|
||||
default: return "unrecognized token";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Token::ToString() const {
|
||||
std::string contentStr = content;
|
||||
if (tokenType == TokenType::TextFragment) {
|
||||
contentStr = std::regex_replace(contentStr, nextLineExp, "");
|
||||
contentStr = std::regex_replace(contentStr, doubleSpaceExp, " ");
|
||||
}
|
||||
return std::format(
|
||||
"{}: \"{}\" ({}:{})\n",
|
||||
ToString(&tokenType),
|
||||
contentStr,
|
||||
row,
|
||||
column
|
||||
);
|
||||
}
|
||||
74
src/transpiler/main.cpp
Normal file
74
src/transpiler/main.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
#include "time.h"
|
||||
#include "api.h"
|
||||
|
||||
std::string readFile(const std::string& filePath) {
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Cannot open file: " + filePath);
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
content += line + '\n';
|
||||
}
|
||||
|
||||
file.close();
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::string filePath;
|
||||
|
||||
bool debug = false;
|
||||
if (argc == 3) {
|
||||
auto option = std::string(argv[1]);
|
||||
if (option == "--debug") {
|
||||
debug = true;
|
||||
} else {
|
||||
std::cout << std::format("Unrecognized option: {}", option) << std::endl;
|
||||
return 1;
|
||||
}
|
||||
filePath = argv[2];
|
||||
} else if (argc == 2) {
|
||||
filePath = argv[1];
|
||||
} else {
|
||||
std::cerr << "Usage: " << argv[0] << " <file-path>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
std::string fileContent = readFile(filePath);
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
showTime("I/O time");
|
||||
|
||||
auto res = transpile(fileContent, debug);
|
||||
auto questions = res.value.questions;
|
||||
if (res.error.length() > 0) {
|
||||
std::cout << std::format(
|
||||
"{} ({}:{})\n",
|
||||
res.error,
|
||||
res.row,
|
||||
res.column
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (Question* question: questions) {
|
||||
delete question;
|
||||
}
|
||||
|
||||
} catch (std::exception &e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
387
src/transpiler/parser.cpp
Normal file
387
src/transpiler/parser.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <format>
|
||||
|
||||
#include "config.h"
|
||||
#include "lexer.h"
|
||||
#include "result.h"
|
||||
#include "parser.h"
|
||||
#include "stringUtils.h"
|
||||
|
||||
struct QuestionElement {
|
||||
bool isDash;
|
||||
bool isGroup;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
std::string MultiElementQuestion::ToString() const {
|
||||
std::stringstream ss;
|
||||
for (const auto& choice : Choices) {
|
||||
char opener;
|
||||
if (type == MultiElementType::Order) {
|
||||
opener = '^';
|
||||
} else if (choice.IsCorrect) {
|
||||
opener = '+';
|
||||
} else {
|
||||
opener = '-';
|
||||
}
|
||||
ss << opener << " " << choice.Answer << "; ";
|
||||
}
|
||||
return std::format(
|
||||
"<Multiple element>\nsection:{}\nid:{}\n{}\n{}",
|
||||
Section,
|
||||
Cooldown,
|
||||
QuestionText,
|
||||
ss.str()
|
||||
);
|
||||
}
|
||||
|
||||
std::string GroupQuestion::ToString() const {
|
||||
std::stringstream ss;
|
||||
for (auto group: Groups) {
|
||||
ss << group.name << ": ";
|
||||
for (auto el: group.elements) {
|
||||
ss << el << ", ";
|
||||
}
|
||||
ss << "; ";
|
||||
}
|
||||
return std::format(
|
||||
"<GroupQuestion>\nsection:{}\nid:{}\n{}\n{}",
|
||||
Section,
|
||||
Cooldown,
|
||||
QuestionText,
|
||||
ss.str()
|
||||
);
|
||||
}
|
||||
|
||||
// Automaton for validating token transitions
|
||||
std::map<TokenType, std::vector<TokenType>> automata;
|
||||
|
||||
bool contains(const std::vector<TokenType>& vec, TokenType element) {
|
||||
return std::find(vec.begin(), vec.end(), element) != vec.end();
|
||||
}
|
||||
|
||||
// Automata for validating the parser state
|
||||
std::map<TokenType, std::vector<TokenType>> parserAutomata() {
|
||||
std::map<TokenType, std::vector<TokenType>> automata;
|
||||
automata[TokenType::TextFragment] = {
|
||||
TokenType::QuestionEnd,
|
||||
TokenType::ElementDashStart,
|
||||
TokenType::ElementPlusStart,
|
||||
TokenType::MatchGroupEnd,
|
||||
TokenType::EndOfFile,
|
||||
};
|
||||
automata[TokenType::MatchGroupEnd] = {
|
||||
TokenType::ElementDashStart
|
||||
};
|
||||
automata[TokenType::QuestionEnd] = {
|
||||
TokenType::ElementDashStart,
|
||||
TokenType::ElementPlusStart
|
||||
};
|
||||
automata[TokenType::ElementDashStart] = {
|
||||
TokenType::CooldownStart,
|
||||
TokenType::TextFragment,
|
||||
TokenType::ElementOrderModifier
|
||||
};
|
||||
automata[TokenType::ElementOrderModifier] = {
|
||||
TokenType::TextFragment
|
||||
};
|
||||
automata[TokenType::ElementPlusStart] = {
|
||||
TokenType::TextFragment
|
||||
};
|
||||
automata[TokenType::Cooldown] = {
|
||||
TokenType::CooldownEnd,
|
||||
};
|
||||
automata[TokenType::CooldownStart] = {
|
||||
TokenType::Cooldown
|
||||
};
|
||||
automata[TokenType::CooldownEnd] = {
|
||||
TokenType::TextFragment
|
||||
};
|
||||
automata[TokenType::StartOfFile] = {
|
||||
TokenType::TextFragment,
|
||||
TokenType::ElementDashStart,
|
||||
TokenType::EndOfFile
|
||||
};
|
||||
automata[TokenType::EndOfFile] = {};
|
||||
return automata;
|
||||
}
|
||||
|
||||
std::string capitalize(const std::string& str) {
|
||||
if (str.empty()) return str;
|
||||
std::string result = str;
|
||||
result[0] = std::towupper(result[0]);
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<NoneType> ValidateGrammar(const std::vector<Token>& tokens) {
|
||||
automata = parserAutomata();
|
||||
for (size_t i = 0; i < tokens.size() - 1; ++i) {
|
||||
Token token = tokens[i];
|
||||
Token nextToken = tokens[i + 1];
|
||||
if (!contains(automata[token.tokenType], nextToken.tokenType)) {
|
||||
return {
|
||||
.error=std::format(
|
||||
"Invalid token sequence: {} cannot precede {}",
|
||||
std::string(capitalize(Token::ToString(&token.tokenType))),
|
||||
std::string(capitalize(Token::ToString(&nextToken.tokenType)))
|
||||
),
|
||||
.row=token.row,
|
||||
.column=token.column
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
|
||||
auto questions = std::vector<Question*>();
|
||||
time_t time = 0;
|
||||
|
||||
auto makeResult = [&questions, &time](std::string error, Token token) -> Result<ParseInfo> {
|
||||
return {
|
||||
{ questions, time },
|
||||
error,
|
||||
token.row,
|
||||
token.column
|
||||
};
|
||||
};
|
||||
|
||||
if (tokens.size() == 0) {
|
||||
return makeResult("", Token());
|
||||
}
|
||||
|
||||
auto result = ValidateGrammar(tokens);
|
||||
if (result.error.length() > 0) {
|
||||
return makeResult(
|
||||
result.error,
|
||||
Token{.row=result.row, .column=result.column}
|
||||
);
|
||||
}
|
||||
|
||||
std::string section;
|
||||
size_t i = 0;
|
||||
|
||||
if (debug) {
|
||||
std::cout << "SECTION: Parser output:\n";
|
||||
}
|
||||
|
||||
auto isInBounds = [tokens](size_t i) {
|
||||
return i < tokens.size() && tokens[i].tokenType != TokenType::EndOfFile;
|
||||
};
|
||||
|
||||
if (isInBounds(i) && tokens[i].tokenType == TokenType::TextFragment) {
|
||||
std::tm tm = {};
|
||||
try {
|
||||
strptime(tokens[i].content.c_str(), "%d.%m.%Y %H:%M", &tm);
|
||||
} catch (std::exception e) {
|
||||
return makeResult(
|
||||
std::format("cannot parse the time - {}", e.what()),
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
time = mktime(&tm);
|
||||
i++;
|
||||
}
|
||||
|
||||
while (i < tokens.size()) {
|
||||
if (tokens[i].tokenType == TokenType::ElementDashStart) {
|
||||
std::string questionText;
|
||||
std::vector<QuestionElement> questionElements;
|
||||
double cooldown;
|
||||
bool isOrderQuestion = false;
|
||||
bool isGroupQuestion = false;
|
||||
bool isPlusQuestion = false;
|
||||
|
||||
// Start element parsing & add to the offset.
|
||||
if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
|
||||
return makeResult(
|
||||
"cannot have order modifier ('^') in the question definition",
|
||||
tokens[i + 1]
|
||||
);
|
||||
}
|
||||
if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::CooldownStart) {
|
||||
try {
|
||||
cooldown = std::stod(tokens[i + 2].content);
|
||||
} catch (std::exception e) {
|
||||
return makeResult(
|
||||
"error parsing cooldown",
|
||||
tokens[i + 1]
|
||||
);
|
||||
}
|
||||
questionText = tokens[i + 4].content;
|
||||
i += 6;
|
||||
} else {
|
||||
cooldown = 0;
|
||||
questionText = tokens[i + 1].content;
|
||||
i += 3;
|
||||
}
|
||||
|
||||
// Parse elements of a question.
|
||||
while (isInBounds(i)) {
|
||||
|
||||
// Check question end.
|
||||
if (isInBounds(i + 3) && tokens[i].tokenType == TokenType::ElementDashStart) {
|
||||
// Distance to the possible question end.
|
||||
size_t offset;
|
||||
if (tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
|
||||
offset = tokens[i + 2].tokenType == TokenType::CooldownStart ? 6 : 3;
|
||||
} else {
|
||||
offset = tokens[i + 1].tokenType == TokenType::CooldownStart ? 5 : 2;
|
||||
}
|
||||
if (isInBounds(i + offset) && tokens[i + offset].tokenType == TokenType::QuestionEnd) {
|
||||
break;
|
||||
}
|
||||
if (offset == 5 && tokens[i + 5].tokenType != TokenType::QuestionEnd) {
|
||||
// Cannot place the identifier on the ordinary element.
|
||||
return makeResult(
|
||||
"Invalid identifier placement",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine element type.
|
||||
bool isDash;
|
||||
bool isGroup = false;
|
||||
bool isOrder = false;
|
||||
if (tokens[i].tokenType == TokenType::ElementDashStart) {
|
||||
isDash = true;
|
||||
} else {
|
||||
isDash = false;
|
||||
isPlusQuestion = true;
|
||||
}
|
||||
if (isInBounds(i+1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
|
||||
isOrder = true;
|
||||
isOrderQuestion = true;
|
||||
if (!isDash) {
|
||||
return makeResult(
|
||||
"order questions can only be used with dashes ('-')",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
if (isGroupQuestion) {
|
||||
return makeResult(
|
||||
"question with groups cannot be ordered ('-^' and ':')",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
if (isInBounds(i + 3) && tokens[i + 3].tokenType == TokenType::MatchGroupEnd) {
|
||||
return makeResult(
|
||||
"cannot have groups in order question('-^' and ':')",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (isInBounds(i + 2) && tokens[i + 2].tokenType == TokenType::MatchGroupEnd) {
|
||||
isGroup = true;
|
||||
isGroupQuestion = true;
|
||||
if (!isDash) {
|
||||
return makeResult(
|
||||
"group questions can only be used with dashes ('-')",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QuestionElement questionElement;
|
||||
questionElement.isDash = isDash;
|
||||
questionElement.isGroup = isGroup;
|
||||
if (isOrder) {
|
||||
questionElement.content = tokens[i + 2].content;
|
||||
} else {
|
||||
questionElement.content = tokens[i + 1].content;
|
||||
}
|
||||
questionElements.push_back(questionElement);
|
||||
|
||||
size_t offset = 2;
|
||||
if (isOrder) {
|
||||
offset += 1;
|
||||
}
|
||||
if (isGroup) {
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
i += offset;
|
||||
}
|
||||
|
||||
if (questionElements.size() > 0) {
|
||||
if (isGroupQuestion) {
|
||||
auto *question = new GroupQuestion();
|
||||
question->Cooldown = cooldown;
|
||||
question->QuestionText = questionText;
|
||||
question->Section = section;
|
||||
int32_t k = -1;
|
||||
for (size_t i = 0; i < questionElements.size(); ++i) {
|
||||
auto questionElement = questionElements[i];
|
||||
if (questionElement.isGroup) {
|
||||
++k;
|
||||
auto group = Group();
|
||||
group.name = cleanContent(questionElement.content);
|
||||
question->Groups.push_back(group);
|
||||
} else {
|
||||
if (k >= 0) {
|
||||
question->Groups[k].elements.push_back(
|
||||
cleanContent(
|
||||
questionElement.content
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
questions.push_back(question);
|
||||
if (debug) {
|
||||
std::cout << question->ToString() << "\n";
|
||||
}
|
||||
} else {
|
||||
auto *question = new MultiElementQuestion();
|
||||
question->Cooldown = cooldown;
|
||||
question->QuestionText = cleanContent(questionText);
|
||||
question->Section = section;
|
||||
for (const auto& elem : questionElements) {
|
||||
Choice choice;
|
||||
choice.Answer = cleanContent(elem.content);
|
||||
choice.IsCorrect = !elem.isDash;
|
||||
question->Choices.push_back(choice);
|
||||
}
|
||||
questions.push_back(question);
|
||||
if (isPlusQuestion) {
|
||||
question->type = MultiElementType::MultiChoice;
|
||||
} else if (isOrderQuestion) {
|
||||
question->type = MultiElementType::Order;
|
||||
} else {
|
||||
question->type = MultiElementType::Regular;
|
||||
}
|
||||
if (debug) {
|
||||
std::cout << question->ToString() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (tokens[i].tokenType == TokenType::EndOfFile) {
|
||||
if (debug) {
|
||||
std::cout << "File terminated: EndOfFile\n";
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
return makeResult(
|
||||
"Unexpected token encountered",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
std::cout << "SECTION END: Parser output:\n";
|
||||
}
|
||||
return makeResult(
|
||||
"",
|
||||
Token()
|
||||
);
|
||||
}
|
||||
25
src/transpiler/stringUtils.cpp
Normal file
25
src/transpiler/stringUtils.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <regex>
|
||||
|
||||
#include "stringUtils.h"
|
||||
|
||||
const std::regex doubleOrMoreSpaceExp(
|
||||
"\\s\\s+",
|
||||
std::regex_constants::ECMAScript | std::regex_constants::icase
|
||||
);
|
||||
|
||||
const std::regex tabExp(
|
||||
"\\t+",
|
||||
std::regex_constants::ECMAScript | std::regex_constants::icase
|
||||
);
|
||||
|
||||
const std::regex newLineExp(
|
||||
"\\n+",
|
||||
std::regex_constants::ECMAScript | std::regex_constants::icase
|
||||
);
|
||||
|
||||
std::string cleanContent(std::string answer) {
|
||||
answer = std::regex_replace(answer, newLineExp, "");
|
||||
answer = std::regex_replace(answer, tabExp, " ");
|
||||
answer = std::regex_replace(answer, doubleOrMoreSpaceExp, " ");
|
||||
return answer;
|
||||
}
|
||||
10
src/transpiler/time.cpp
Normal file
10
src/transpiler/time.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include "time.h"
|
||||
|
||||
void showTime(std::string label) {
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
double_t msDuration = (double_t) duration.count() / 1000;
|
||||
std::cout << std::format("{}: {:.3f} ms", label, msDuration) << std::endl;
|
||||
}
|
||||
8
src/transpiler/time.h
Normal file
8
src/transpiler/time.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include<chrono>
|
||||
|
||||
extern std::chrono::high_resolution_clock::time_point start;
|
||||
extern std::chrono::high_resolution_clock::time_point end;
|
||||
|
||||
void showTime(std::string label);
|
||||
Reference in New Issue
Block a user