mirror of
https://github.com/jorenchik/mdemory.git
synced 2026-03-22 00:26:21 +00:00
docs, added help window, CMakeLists in root dir, other small changes
This commit is contained in:
780
docs/main.typst
780
docs/main.typst
@@ -50,6 +50,11 @@
|
||||
īpašībām, piemēram, saturu, izcelsmi un struktūru;
|
||||
|
||||
*CSV* -- formāts, kas satur vērtības, kas ir atdalīti ar komatiem;
|
||||
|
||||
*Parsēšana* -- TODO;
|
||||
|
||||
*Buferis* -- TODO;
|
||||
|
||||
],
|
||||
)
|
||||
|
||||
@@ -67,17 +72,6 @@
|
||||
|
||||
== Darbības sfēra
|
||||
|
||||
// 1) identificēt programmatūras produkta nosaukumu, piemēram, "Pārskatu ģenerators", utt.,
|
||||
// 2) paskaidrot kas programmatūras produktam jādara un, ja nepieciešams, kas
|
||||
// nav jādara;
|
||||
// 3) aprakstīt programmatūras produkta pielietojumu.
|
||||
// a) Aprakstīt, cik iespējams precīzi, iespējas, centienus un mērķi. Piemēram,
|
||||
// teikums: iespēja efektīvi izstrādāt pārskatus nav tik labs, kā parametrizēti,
|
||||
// lietotāja vadīti un definēti pārskati divu stundu laikā, ar iespēju ievadīt
|
||||
// lietotāja parametrus dialoga režīmā.
|
||||
// b) Nepieļaut pretrunas, lietojot terminus, kas līdzīgi terminiem augstāka
|
||||
// līmeņa LVS 68:1996 lpp. 15. (22) specifikācijā (piemēram, Sistēmprasību
|
||||
|
||||
#indent-par([
|
||||
// Kas ir atmiņas kartes
|
||||
Atmiņas kartītes (angl. flashcards) ir izplatīts veids, kā skolēni,
|
||||
@@ -218,14 +212,34 @@ slēptā formātā, kas apgrūtina kartīšu pārvaldību un pārnešanu.
|
||||
|
||||
== Risinājuma lietotāji
|
||||
|
||||
// TODO Spelling
|
||||
#indent-par([
|
||||
Risinājumam ir viena lietotāju grupa, kam ir pieejamas visas risinājuma
|
||||
iespējas un funkcijas.
|
||||
Risinājuma augsta līmeņa datu plūsmas ir attēlota 0. līmeņa datu plūsmas
|
||||
diagrammā (DPD; skat. @fig:dpd0 attēls). Lietotāju galvenā datu apmaiņa ir
|
||||
atmiņas kartīšu dati un dati par saskarni ar kartītēm. DPD ir iekļautas trīs
|
||||
datu glabātuves: failu glabātuve, pagaidu kartīšu glabātuve un konfigurācijas
|
||||
glabātuve.
|
||||
])
|
||||
|
||||
Risinājuma augsta līmeņa datu plūsmas ir attēlota 0. līmeņa datu plūsmas
|
||||
diagrammā (DPD; skat. @fig:dpd0 attēls). Lietotāju galvenā datu apmaiņa ir
|
||||
atmiņas kartīšu dati un dati par saskarni ar kartītēm.
|
||||
Failu glabātuve ir vieta, kur tiek glabātas kartītes vienkārša teskta formātā.
|
||||
Tas ir direktorijs ar noteikta formāta failiem (skat. attēlu @fig:conceptual-er
|
||||
un tabulas 2.1-2.6). Šo glabātuvi pārvalda lietotājs -- brīvi pievieno,
|
||||
modificē un pārstrukturē direktorijā esošos failus un apakšdirektorijus.
|
||||
|
||||
Pagaidu kartīšu glabātuve glabā apskatāmās kartītes un visus izmainītos atmiņas
|
||||
failiem atbilstošus kartīšu sarakstus. Glabātuve ir spēkā risinājuma programmas
|
||||
darba laikā. Sākot darbu ar risinājumu, kartītes tiek ielādētas no failu
|
||||
glabātuves pagaidu glabātuvē. Darbu beidzot vai pēc lietotāja izvēles, kartītes
|
||||
tiek saglabātas failos, no kuriem katītes tika ielādētas. Izmaiņas pagaidu
|
||||
glabātuves datos notiek ar risinājuma saskarni.
|
||||
|
||||
Konfigurācijas glabātuve ir fails(/-i), kuros tiek glabātas konfigurācijas
|
||||
vērtības, ko uzstāda un saglabā lietotājs. Konfigurācijas glabātuvi pārvalda
|
||||
lietotājs -- ir iespējams mainīt konfigurācijas faila saturu ar nosacījumu, ka
|
||||
faila formāts paliek korekts un ievadītās vertības ir korektas un ir pieejamā
|
||||
vērtību diapazonā.
|
||||
|
||||
#figure(
|
||||
caption: "0.līmeņa datu plūsmas diagramma",
|
||||
@@ -260,6 +274,42 @@ atmiņas kartīšu dati un dati par saskarni ar kartītēm.
|
||||
#pagebreak(weak: true)
|
||||
= Programmatūras prasību specifikācija
|
||||
|
||||
// Šajā sadaļā tiks aprakstīts TODO??
|
||||
|
||||
== Konceptuālais entitāšu-relāciju modelis
|
||||
|
||||
// TODO spelling
|
||||
Risinājuma glabātie dati iekļauj atmiņas kartīšu un konfigurācijas datus, kas
|
||||
tiek glabāti vienkāršos failos. Risinājums neizmantos datubāzi datu glabāšanai.
|
||||
Risinājumā entitātes ir attēlotas konceptuālā entitāšu-relāciju modelī (skat. attēlu
|
||||
@fig:conceptual-er).
|
||||
|
||||
Atmiņas kartītes tiek organizētas noteikta paplašinājuma failos, kas satur
|
||||
kartītes noteiktā formātā. Kartītes ir dažādu veidu jautājumi, kas ietver
|
||||
sākuma -- jautājuma tekstu un vairākus saistītus jautājuma atbildes elementus.
|
||||
Ir atbildes jautājumi, kas satur vairākus vienlīdzīgus elementus, un ir grupas
|
||||
jautājumi, kas satur vairākas grupas ar vairākiem tai piederošiem elementiem.
|
||||
Atbildes elementiem piemīt veids, katrs atšķiras ar to, kā elements sākas, ko
|
||||
nosaka sākuma simboli. Grupas elementi sastāv no teksta un pieder kādai grupai,
|
||||
kam ir nosaukums.
|
||||
|
||||
Modelī ir iekļauti metadati, kā pēdējās mācīšanās laiks, jautājuma pārtraukuma
|
||||
laiks un faila nosaukums. Pēdējās mācīšanās laiks tiek izmantots intervālu
|
||||
metodē, lai reģistrētu laiku un pielietot intervālu metodi turpmākās mācību
|
||||
reizēs. Jautājuma pārtraukuma laiks nosaka, cik ilgam laikam jāpaiet, lai
|
||||
jautājums atkal būtu iekļauts intervālu metodes mācīšanās.
|
||||
Faila nosaukums tiek izmantots kā kartīšu saraksta nosaukums -- to var izmanot,
|
||||
lai nosauktu failā esošās kartītes, piemēram, to tēmu vai nolūku.
|
||||
|
||||
|
||||
Papildus datetime TODO
|
||||
|
||||
#figure(
|
||||
caption: "Konceptuālais entitāšu-relāciju modelis",
|
||||
placement: auto,
|
||||
image("img/conceptualER.svg"),
|
||||
) <conceptual-er>
|
||||
|
||||
== Funkcionālās prasības
|
||||
|
||||
#let ref_df01 = [(skat. tabulu 2.1)]
|
||||
@@ -1165,8 +1215,6 @@ Pirmkodam jābūt izstrādātam ar sekojošiem nosacījumiem:
|
||||
|
||||
+ Risinājuma pirmkods ir realizēts galvenokārt procedurālā stilā;
|
||||
+ Transpilatora funkcionalitātes testu pārklājums ir 90%;
|
||||
// + Saskarnes funkcionalitāte ir notestēta svarīgākās vietās (svarīgākās vietas
|
||||
// ir subjektīvs jēdziens, tāpēc to definēsim kā 20%).
|
||||
|
||||
==== Pārnesamība
|
||||
|
||||
@@ -1190,10 +1238,22 @@ Uz drošību risinājumam ir sekojošas prasības:
|
||||
== Daļējs funkciju projektējums
|
||||
|
||||
#figure(
|
||||
caption: "Leksiskās analīzes aktivitāšu diagramma",
|
||||
caption: "Leksiskās analīzes aktivitātes diagramma",
|
||||
placement: none,
|
||||
image("img/lexer_activity.svg"),
|
||||
) <mdem-list-view>
|
||||
) <lexing-activity>
|
||||
|
||||
#figure(
|
||||
caption: "Parsēšanas aktivitātes diagramma",
|
||||
placement: none,
|
||||
image("img/parsing_activity.svg"),
|
||||
) <parsing-acticity>
|
||||
|
||||
#figure(
|
||||
caption: "Atbildes parsēšanas aktivitātes diagramma",
|
||||
placement: none,
|
||||
image("img/parsing_answer_activity.svg"),
|
||||
) <parsing-answer-activity>
|
||||
|
||||
== Saskarnes projektējums
|
||||
|
||||
@@ -1233,6 +1293,29 @@ Uz drošību risinājumam ir sekojošas prasības:
|
||||
image("img/screens/config.png"),
|
||||
) <config-view>
|
||||
|
||||
#figure(
|
||||
caption: "Apmācības skats",
|
||||
placement: none,
|
||||
image("img/screens/help.png"),
|
||||
) <help-view>
|
||||
|
||||
#pagebreak(weak: true)
|
||||
= Testēšanas dokumentācija
|
||||
|
||||
#pagebreak(weak: true)
|
||||
= Projekta organizācija
|
||||
|
||||
#pagebreak(weak: true)
|
||||
= Kvalitātes nodrošināšana
|
||||
|
||||
#pagebreak(weak: true)
|
||||
= Konfigurācijas pārvaldība
|
||||
|
||||
#pagebreak(weak: true)
|
||||
= Darbietiplības novērtējums
|
||||
|
||||
#pagebreak(weak: true)
|
||||
= Secinājumi
|
||||
|
||||
#pagebreak(weak: true)
|
||||
#heading(numbering: none, "Izmantotā literatūra un avoti")
|
||||
@@ -1284,3 +1367,664 @@ Uz drošību risinājumam ir sekojošas prasības:
|
||||
day: 21,
|
||||
),
|
||||
)<quizlet>
|
||||
|
||||
|
||||
+ TODO UML 2.5. spec
|
||||
|
||||
#pagebreak(weak: true)
|
||||
#heading(numbering: none, "Pielikumi")
|
||||
|
||||
// TODO caption
|
||||
|
||||
```cpp
|
||||
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;
|
||||
|
||||
/*
|
||||
* TODO
|
||||
*/
|
||||
void trimString(std::string &str, std::string trimChars) {
|
||||
|
||||
// Noņem kreisās puses simbolus.
|
||||
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);
|
||||
}
|
||||
|
||||
// Noņem labās puses simbolus.
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Izveido tekstvienību, iegūstot to no bufera beigām.
|
||||
* Ja buferī ir teksta vienība pirms tekstvienības, pievieno to pirms beigu
|
||||
* tekstvienības.
|
||||
*/
|
||||
void tokenWithBuffer(
|
||||
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();
|
||||
}
|
||||
|
||||
/*
|
||||
* Pārveido simbolu virkni tekstvienību sarakstā.
|
||||
* */
|
||||
Result<std::vector<Token>> tokenizeMdem(const std::string& content) {
|
||||
row = 1;
|
||||
column = 1;
|
||||
previousRow = 1;
|
||||
previousColumn = 1;
|
||||
textStarted = false;
|
||||
tokens.clear();
|
||||
buffer.clear();
|
||||
|
||||
// Beidz, ja satur tikai tukšumus vai neko.
|
||||
if (content.find_first_not_of(" \n\t") == std::string::npos) {
|
||||
return {tokens, ""};
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < content.size(); ++i) {
|
||||
char c = content[i];
|
||||
|
||||
// Apstrādā īpašos simbolus un tekstu.
|
||||
if (c == '\n') {
|
||||
row += 1;
|
||||
column = 0;
|
||||
}
|
||||
if (c == '\\') {
|
||||
i += 1;
|
||||
if (i < content.size()) {
|
||||
buffer.push_back(content[i]);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
buffer.push_back(c);
|
||||
}
|
||||
if (!textStarted) {
|
||||
if (c == '\n') {
|
||||
previousRow += 1;
|
||||
previousColumn = 1;
|
||||
} else if (c == ' ') {
|
||||
previousColumn += 1;
|
||||
} else if (c == '\t') {
|
||||
previousColumn += 4;
|
||||
} else {
|
||||
textStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Emitē tekstvienības.
|
||||
switch (c) {
|
||||
case '[': {
|
||||
tokenWithBuffer(
|
||||
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
|
||||
};
|
||||
}
|
||||
tokenWithBuffer(
|
||||
TokenType::CooldownEnd,
|
||||
1,
|
||||
TokenType::Cooldown
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
identifierStarted = false;
|
||||
} break;
|
||||
case '-': {
|
||||
tokenWithBuffer(
|
||||
TokenType::ElementDashStart,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
} break;
|
||||
case '^': {
|
||||
tokenWithBuffer(
|
||||
TokenType::ElementOrderModifier,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
} break;
|
||||
case ':': {
|
||||
tokenWithBuffer(
|
||||
TokenType::MatchGroupEnd,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
} break;
|
||||
case '>': {
|
||||
tokenWithBuffer(
|
||||
TokenType::QuestionEnd,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
} break;
|
||||
case '+': {
|
||||
tokenWithBuffer(
|
||||
TokenType::ElementPlusStart,
|
||||
1,
|
||||
TokenType::TextFragment
|
||||
);
|
||||
previousRow = row;
|
||||
previousColumn = column;
|
||||
textStarted = false;
|
||||
} break;
|
||||
}
|
||||
|
||||
column += 1;
|
||||
}
|
||||
|
||||
// Pievieno beigu simbolu, lai atvieglotu parsēšanu.
|
||||
tokenWithBuffer(
|
||||
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::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";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
// TODO caption
|
||||
|
||||
```cpp
|
||||
typedef std::map<TokenType, std::vector<TokenType>> TokenAutomata;
|
||||
|
||||
TokenAutomata *automata = nullptr;
|
||||
/*
|
||||
* Tekstvienību secības pārejas, kas nosaka, kādā secībā tekstvienības var būt.
|
||||
* */
|
||||
void initParserAutomata() {
|
||||
automata = new TokenAutomata;
|
||||
(*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] = {};
|
||||
}
|
||||
|
||||
/*
|
||||
* Pārbauda, vai vai tekstvienību sarakstu akceptē atbilst atbilst valodas
|
||||
* automāts.
|
||||
* */
|
||||
Result<NoneType> ValidateGrammar(const std::vector<Token>& tokens) {
|
||||
if (!automata) {
|
||||
initParserAutomata();
|
||||
}
|
||||
for (size_t i = 0; i < tokens.size() - 1; ++i) {
|
||||
Token token = tokens[i];
|
||||
Token nextToken = tokens[i + 1];
|
||||
if (
|
||||
std::find(
|
||||
(*automata)[token.tokenType].begin(),
|
||||
(*automata)[token.tokenType].end(),
|
||||
nextToken.tokenType
|
||||
) == (*automata)[token.tokenType].end()) {
|
||||
|
||||
auto capitalize = [](const std::string& str) {
|
||||
if (str.empty()) return str;
|
||||
std::string result = str;
|
||||
result[0] = std::towupper(result[0]);
|
||||
return result;
|
||||
};
|
||||
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 {};
|
||||
}
|
||||
|
||||
// @Fix: Prevent duplicate group names and questions in ordered question (to
|
||||
// simplify checking in practice).
|
||||
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) {
|
||||
try {
|
||||
auto parseToUTCTime = [](const std::string datetime, std::string format) {
|
||||
std::tm tm = {};
|
||||
std::istringstream ss(datetime);
|
||||
ss >> std::get_time(&tm, format.c_str());
|
||||
if (ss.fail()) {
|
||||
throw std::runtime_error("Failed to parse datetime string");
|
||||
}
|
||||
std::time_t time = timegm(&tm);
|
||||
return time;
|
||||
};
|
||||
|
||||
time = parseToUTCTime(tokens[i].content.c_str(), "%d.%m.%Y %H:%M");
|
||||
} catch (std::exception e) {
|
||||
return makeResult(
|
||||
std::format("cannot parse the time - {}", e.what()),
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user