diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ba7a67..777a5df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) set( CMAKE_CXX_FLAGS_DEBUG - "${CMAKE_CXX_FLAGS_DEBUG} -g3 -O0 -ggdb -fvar-tracking-assignments -fno-inline" + "${CMAKE_CXX_FLAGS_DEBUG} -g3 -O0 -ggdb -fno-inline" ) add_subdirectory(src/transpiler) add_subdirectory(src/qtapp) +add_subdirectory(src/test) diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt new file mode 100644 index 0000000..7126adc --- /dev/null +++ b/src/test/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.14) +project(MdemoryTest) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(GTest REQUIRED) +enable_testing() + +add_executable( + testTranspiler + testTranspiler.cpp + +) +target_link_libraries( + testTranspiler + GTest::gtest_main + api +) + +include(GoogleTest) +gtest_discover_tests(testTranspiler) + diff --git a/src/test/testTranspiler.cpp b/src/test/testTranspiler.cpp new file mode 100644 index 0000000..48b1113 --- /dev/null +++ b/src/test/testTranspiler.cpp @@ -0,0 +1,363 @@ +#include "api.h" +#include "parser.h" +#include "result.h" + +#include + +TEST(TranspilerTest, TestSimpleQuestion) { + std::string questionText = "- A question front >\n" + "- An answer\n" + "- Another answer"; + + auto res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + auto questions = res.value.questions; + EXPECT_EQ(questions.size(), 1); + EXPECT_EQ(questions[0]->questionText, "A question front"); + + auto meQuestion = dynamic_cast(questions[0]); + EXPECT_EQ(meQuestion->type, Regular); + EXPECT_EQ(meQuestion->choices.size(), 2); + EXPECT_EQ(meQuestion->choices[0].answer, "An answer"); + EXPECT_EQ(meQuestion->choices[1].answer, "Another answer"); + EXPECT_EQ(meQuestion->choices[0].isCorrect, false); + EXPECT_EQ(meQuestion->choices[1].isCorrect, false); + delete meQuestion; +} + +TEST(TranspilerTest, TestChoiceQuestion) { + std::string questionText = "- A question front >\n" + "+ An answer\n" + "- Another answer"; + + auto res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + auto questions = res.value.questions; + EXPECT_EQ(questions.size(), 1); + EXPECT_EQ(questions[0]->questionText, "A question front"); + + auto meQuestion = dynamic_cast(questions[0]); + EXPECT_EQ(meQuestion->type, MultiChoice); + EXPECT_EQ(meQuestion->choices.size(), 2); + EXPECT_EQ(meQuestion->choices[0].answer, "An answer"); + EXPECT_EQ(meQuestion->choices[1].answer, "Another answer"); + EXPECT_EQ(meQuestion->choices[0].isCorrect, true); + EXPECT_EQ(meQuestion->choices[1].isCorrect, false); + delete meQuestion; +} + +TEST(TranspilerTest, TestOrderQuestion) { + std::string questionText = "- A question front >\n" + "- An answer\n" + "-^ Another answer"; + + auto res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + auto questions = res.value.questions; + EXPECT_EQ(questions.size(), 1); + EXPECT_EQ(questions[0]->questionText, "A question front"); + + auto meQuestion = dynamic_cast(questions[0]); + EXPECT_EQ(meQuestion->type, Order); + EXPECT_EQ(meQuestion->choices.size(), 2); + EXPECT_EQ(meQuestion->choices[0].answer, "An answer"); + EXPECT_EQ(meQuestion->choices[1].answer, "Another answer"); + EXPECT_EQ(meQuestion->choices[0].isCorrect, false); + EXPECT_EQ(meQuestion->choices[1].isCorrect, false); + delete meQuestion; +} + +TEST(TranspilerTest, TestGroupQuestion) { + std::string questionText = "- A question front >\n" + "- Group 1:\n" + "- Element 1" + "- Element 2" + "- Group 2:\n" + "- Group 3:\n" + "- Element 1"; + auto res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + auto questions = res.value.questions; + EXPECT_EQ(questions.size(), 1); + EXPECT_EQ(questions[0]->questionText, "A question front"); + + auto grQuestion = dynamic_cast(questions[0]); + EXPECT_EQ(grQuestion->groups.size(), 3); + EXPECT_EQ(grQuestion->groups[0].name, "Group 1"); + EXPECT_EQ(grQuestion->groups[1].name, "Group 2"); + EXPECT_EQ(grQuestion->groups[2].name, "Group 3"); + EXPECT_EQ(grQuestion->groups[0].elements.size(), 2); + EXPECT_EQ(grQuestion->groups[1].elements.size(), 0); + EXPECT_EQ(grQuestion->groups[2].elements.size(), 1); + delete grQuestion; +} + +TEST(TranspilerTest, TestConflictingElements) { + std::string questionText; + Result res; + + // Iespējamie elementu modifikatori: + // -^ secības + // + izvēles (pareiza atbilde) + // : grupas + // Apskatīsim visas konfliktējošas kombinācijas. + + // -^ un : + questionText = "- A question front >\n" + "- An answer 1\n" + "-^ An answer 2\n" + "- An answer 3\n" + "- An answer 4:\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); + + // : un -^ + questionText = "- A question front >\n" + "- An answer 1\n" + "- An answer 2:\n" + "- An answer 3\n" + "-^ An answer 4\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); + + // + un : + questionText = "- A question front >\n" + "- An answer 1\n" + "+ An answer 2\n" + "- An answer 3\n" + "- An answer 4:\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); + + // : un + + questionText = "- A question front >\n" + "- An answer 1\n" + "- An answer 2:\n" + "- An answer 3\n" + "+ An answer 4\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); + + // -^ un + + questionText = "- A question front >\n" + "- An answer 1\n" + "-^ An answer 2\n" + "- An answer 3\n" + "+ An answer 4\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); + + // + un -^ + questionText = "- A question front >\n" + "- An answer 1\n" + "+ An answer 2\n" + "- An answer 3\n" + "-^ An answer 4\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); +} + +TEST(TranspilerTest, TestGroupQuestionWithLeadingElements) { + std::string questionText = "- A question front >\n" + "- Element 1\n" + "- Element 2\n" + "- Group 1:\n" + "- Element 1\n" + "- Element 2\n" + "- Element 3\n"; + auto res = transpile(questionText); + EXPECT_NE(res.error, ""); +} + +TEST(TranspilerTest, TestIncorrectTimestamp) { + // Kontrole. + std::string questionText = "- A question front >\n" + "- Element 1\n" + "- Element 2\n"; + auto res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + // Pārbaude. + questionText = "Incorrect timestamp\n" + "- A question front >\n" + "- Element 1\n" + "- Element 2\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); +} + +TEST(TranspilerTest, TestUnexpectedCooldown) { + std::string questionText; + Result res; + + // Kontrole. + questionText = "- A question front >\n" + "- Element 1\n" + "- Element 2\n"; + res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + // Pārbaude. + questionText = "- A question front >\n" + "- Element 1\n" + "- Element 2 [0.12]\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); +} + +TEST(TranspilerTest, TestCooldownParse) { + std::string questionText; + Result res; + Question *question; + + // Ar punktu. + questionText = "- [12.21] A question front >\n" + "- Element 1\n" + "- Element 2\n"; + res = transpile(questionText); + question = res.value.questions[0]; + EXPECT_EQ(res.error, ""); + EXPECT_EQ(question->cooldown, 12.21); + delete question; + + // Bez punkta. + questionText = "- [12] A question front >\n" + "- Element 1\n" + "- Element 2\n"; + res = transpile(questionText); + question = res.value.questions[0]; + EXPECT_EQ(res.error, ""); + EXPECT_EQ(question->cooldown, 12); + delete question; + + // Negatīvs. + questionText = "- [-12.21] A question front >\n" + "- Element 1\n" + "- Element 2\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); + + // Nav skaitlis. + questionText = "- [NotANumber] A question front >\n" + "- Element 1\n" + "- Element 2\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); +} + +TEST(TranspilerTest, TestParsesTime) { + std::string questionText = "12.02.2022 12\\:25\n" + "- A question front >\n" + "- Element 1\n" + "- Element 2\n"; + const time_t expectedTime = 1644668700; + auto res = transpile(questionText); + EXPECT_EQ(res.error, ""); + EXPECT_EQ(res.value.lastTrainedAt, expectedTime); +} + + +TEST(TranspilerTest, TestOrderQuestionDuplicates) { + std::string questionText; + Result res; + + // Kontrole. + questionText = "- A question front >\n" + "- Element 1\n" + "- Element 2\n" + "- Element 3\n" + "- Element 4\n" + "-^ Element 5\n"; + res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + // Pārbaude. + questionText = "- A question front >\n" + "- Element 1\n" + "- Element 2\n" + "- Element 3\n" + "- Element 1\n" + "-^ Element 5\n"; + res = transpile(questionText); + EXPECT_NE(res.error, ""); +} + +TEST(TranspilerTest, TestRemovesExcessiveWhitespace) { + std::string questionText = "- \n\t A question\n\t front\n\t >\n" + "-\n\t An\n\t answer\n\t " + "-\n\t Another\n\t answer\n\t "; + auto res = transpile(questionText); + + auto questions = res.value.questions; + EXPECT_EQ(questions[0]->questionText, "A question front"); + + auto meQuestion = dynamic_cast(questions[0]); + EXPECT_EQ(meQuestion->choices[0].answer, "An answer"); + EXPECT_EQ(meQuestion->choices[1].answer, "Another answer"); + delete meQuestion; +} + +TEST(TranspilerTest, TestTranspilesMultipleQuestions) { + std::string questionText = "- Question 1 >\n" + "- Answer 1\n" + "- Answer 2\n" + "- Question 2 >\n" + "- Answer 1\n" + "+ Answer 2\n" + "- Question 3 >\n" + "- Answer 1\n" + "-^ Answer 2\n" + "- Question 4 >\n" + "- Group 1:\n" + "- Element 1\n" + "- Element 2\n"; + auto res = transpile(questionText); + EXPECT_EQ(res.error, ""); + + auto questions = res.value.questions; + EXPECT_EQ(questions.size(), 4); + + EXPECT_EQ(questions[0]->questionText, "Question 1"); + EXPECT_EQ(questions[1]->questionText, "Question 2"); + EXPECT_EQ(questions[2]->questionText, "Question 3"); + EXPECT_EQ(questions[3]->questionText, "Question 4"); + + auto question = dynamic_cast(questions[0]); + EXPECT_EQ(question->type, Regular); + EXPECT_EQ(question->choices.size(), 2); + EXPECT_EQ(question->choices[0].answer, "Answer 1"); + EXPECT_EQ(question->choices[1].answer, "Answer 2"); + EXPECT_EQ(question->choices[0].isCorrect, false); + EXPECT_EQ(question->choices[1].isCorrect, false); + delete question; + + question = dynamic_cast(questions[1]); + EXPECT_EQ(question->type, MultiChoice); + EXPECT_EQ(question->choices.size(), 2); + EXPECT_EQ(question->choices[0].answer, "Answer 1"); + EXPECT_EQ(question->choices[1].answer, "Answer 2"); + EXPECT_EQ(question->choices[0].isCorrect, false); + EXPECT_EQ(question->choices[1].isCorrect, true); + delete question; + + question = dynamic_cast(questions[2]); + EXPECT_EQ(question->type, Order); + EXPECT_EQ(question->choices.size(), 2); + EXPECT_EQ(question->choices[0].answer, "Answer 1"); + EXPECT_EQ(question->choices[1].answer, "Answer 2"); + EXPECT_EQ(question->choices[0].isCorrect, false); + EXPECT_EQ(question->choices[1].isCorrect, false); + delete question; + + auto groupQuestion = dynamic_cast(questions[3]); + EXPECT_EQ(groupQuestion->groups.size(), 1); + auto group = groupQuestion->groups[0]; + EXPECT_EQ(group.name, "Group 1"); + EXPECT_EQ(group.elements[0], "Element 1"); + EXPECT_EQ(group.elements[1], "Element 2"); + delete groupQuestion; +} diff --git a/src/transpiler/lexer.cpp b/src/transpiler/lexer.cpp index 545ca55..d798181 100644 --- a/src/transpiler/lexer.cpp +++ b/src/transpiler/lexer.cpp @@ -23,14 +23,14 @@ bool sof; /* * TODO */ -void trimString(std::string &str, std::string trimChars) { +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 i = 0; i < str->size(); ++i) { for (size_t k = 0; k < trimChars.size(); ++k) { - if (str[i] == trimChars[k]) { + if ((*str)[i] == trimChars[k]) { padSize++; pad = true; break; @@ -42,15 +42,15 @@ void trimString(std::string &str, std::string trimChars) { pad = false; } if (padSize > 0) { - str.erase(0, padSize); + 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 i = str->size(); i-- > 0;) { for (size_t k = 0; k < trimChars.size(); ++k) { - if (str[i] == trimChars[k]) { + if ((*str)[i] == trimChars[k]) { padSize++; pad = true; break; @@ -62,7 +62,7 @@ void trimString(std::string &str, std::string trimChars) { pad = false; } if (padSize > 0) { - str.erase(str.end() - padSize, str.end()); + str->erase(str->end() - padSize, str->end()); } } @@ -79,7 +79,7 @@ void tokenWithBuffer( std::string token(buffer.end() - tokenLen, buffer.end()); if (buffer.size() > tokenLen) { std::string prevFragment(buffer.begin(), buffer.end() - tokenLen); - trimString(prevFragment, " \n\t"); + trimString(&prevFragment, " \n\t"); if (prevFragment.length() > 0) { tokens.push_back(Token{ textType, diff --git a/src/transpiler/lexer.plist b/src/transpiler/lexer.plist new file mode 100644 index 0000000..4897ca0 --- /dev/null +++ b/src/transpiler/lexer.plist @@ -0,0 +1,14 @@ + + + + + clang_version +clang version 18.1.8 + diagnostics + + + files + + + + diff --git a/src/transpiler/main.plist b/src/transpiler/main.plist new file mode 100644 index 0000000..4897ca0 --- /dev/null +++ b/src/transpiler/main.plist @@ -0,0 +1,14 @@ + + + + + clang_version +clang version 18.1.8 + diagnostics + + + files + + + + diff --git a/src/transpiler/parser.cpp b/src/transpiler/parser.cpp index 49b7ac6..136e49f 100644 --- a/src/transpiler/parser.cpp +++ b/src/transpiler/parser.cpp @@ -144,25 +144,18 @@ Result parseQuestions(const std::vector& tokens) { }; 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("Neizdevās nolasīt datuma un laiku"); - } - std::time_t time = timegm(&tm); - return time; - }; - - time = parseToUTCTime(tokens[i].content.c_str(), "%d.%m.%Y %H:%M"); - } catch (std::exception e) { + const std::string format = "%d.%m.%Y %H:%M"; + const std::string datetime = tokens[i].content.c_str(); + std::tm tm = {}; + std::istringstream ss(datetime); + ss >> std::get_time(&tm, format.c_str()); + if (ss.fail()) { return makeResult( - e.what(), + "Neizdevās nolasīt datuma un laiku", tokens[i] ); } + time = timegm(&tm); i++; } @@ -171,17 +164,17 @@ Result parseQuestions(const std::vector& tokens) { std::string questionText; std::vector questionElements; double cooldown; - bool isOrderQuestion = false; - bool isGroupQuestion = false; - bool isPlusQuestion = false; + bool isOrderQuestion = false; + bool isGroupQuestion = false; + bool isPlusQuestion = false; + bool hasGroupEncountered = false; Token questionStartToken; // Start element parsing & add to the offset. if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) { return makeResult( "Nevar izmantot secības modifikatoru ('^') jautājuma sākumā", - tokens[i + 1] - ); + tokens[i + 1]); } questionStartToken = tokens[i]; @@ -235,6 +228,18 @@ Result parseQuestions(const std::vector& tokens) { } else { isDash = false; isPlusQuestion = true; + if (isGroupQuestion) { + return makeResult( + "jautājumos ar grupām nevar būt secības elementu ('+' and ':')", + tokens[i] + ); + } + if (isOrderQuestion) { + return makeResult( + "secības jautājumos nevar būt izvēles elementu ('-^' and '+')", + tokens[i] + ); + } } if (isInBounds(i+1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) { isOrder = true; @@ -247,7 +252,13 @@ Result parseQuestions(const std::vector& tokens) { } if (isGroupQuestion) { return makeResult( - "jautājumos ar grupām nevar būt secības elementu ('-^' and ':')", + "jautājumos ar grupām nevar būt secības elementu ('-^' and ':')", + tokens[i] + ); + } + if (isPlusQuestion) { + return makeResult( + "izvēles jautājumos nevar būt secības elementu ('+' and '-^')", tokens[i] ); } @@ -259,14 +270,23 @@ Result parseQuestions(const std::vector& tokens) { } } if (isInBounds(i + 2) && tokens[i + 2].tokenType == TokenType::MatchGroupEnd) { - isGroup = true; - isGroupQuestion = true; + isGroup = true; + isGroupQuestion = true; if (!isDash) { return makeResult( "grupas jautājumus var definēt tikai ar svītru elementiem ('-')", tokens[i] ); } + if (!hasGroupEncountered) { + if (questionElements.size() > 0) { + return makeResult( + "elementi grupas jautājumā nevar eksistēt bez grupas", + tokens[i] + ); + } + } + hasGroupEncountered = true; } QuestionElement questionElement; diff --git a/src/transpiler/parser.plist b/src/transpiler/parser.plist new file mode 100644 index 0000000..4897ca0 --- /dev/null +++ b/src/transpiler/parser.plist @@ -0,0 +1,14 @@ + + + + + clang_version +clang version 18.1.8 + diagnostics + + + files + + + +