partial implementation of all question types

This commit is contained in:
jorenchik
2024-09-28 17:21:14 +03:00
parent bad73f4f35
commit 2c3b904853
7 changed files with 184 additions and 76 deletions

View File

@@ -8,8 +8,10 @@
enum class TokenType { enum class TokenType {
TextFragment, TextFragment,
QuestionEnd, QuestionEnd,
MatchGroupEnd,
ElementDashStart, ElementDashStart,
ElementPlusStart, ElementPlusStart,
ElementOrderModifier,
Identifier, Identifier,
IdentifierStart, IdentifierStart,
IdentifierEnd, IdentifierEnd,

View File

@@ -6,32 +6,44 @@
#include "lexer.h" #include "lexer.h"
#include "result.h" #include "result.h"
struct Question { struct Question {
virtual std::string ToString() const = 0; virtual std::string ToString() const = 0;
virtual ~Question() = default; virtual ~Question() = default;
}; };
struct Choice { struct Choice {
std::string Answer; std::string Answer;
bool IsCorrect; bool IsCorrect;
}; };
struct SingleAnswerQuestion : public Question { enum MultiElementType {
std::string ID; Regular = 0,
std::string QuestionText; MultiChoice,
std::string Answer; Order
std::string Section;
std::string ToString() const override;
}; };
struct MultipleChoiceQuestion : public Question { struct MultiElementQuestion : public Question {
std::string ID; std::string ID;
std::string QuestionText; std::string QuestionText;
std::vector<Choice> Choices; std::vector<Choice> Choices;
std::string Section; std::string Section;
MultiElementType type;
std::string ToString() const override; std::string ToString() const override;
};
struct Group {
std::string name;
std::string elements;
};
struct GroupQuestion : public Question {
std::string ID;
std::string QuestionText;
std::vector<Group> Groups;
std::string ToString() const override;
}; };
Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens); Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens);

View File

@@ -199,17 +199,7 @@ void CreateMdems(std::vector<Question*>& questions) {
}; };
for (size_t i = 0; i < questions.size(); ++i) { for (size_t i = 0; i < questions.size(); ++i) {
if (SingleAnswerQuestion* sa = dynamic_cast<SingleAnswerQuestion*>(questions[i])) { if (AnswerQuestion* mw = dynamic_cast<AnswerQuestion*>(questions[i])) {
mdems[i]->wFrontText->setText(QString::fromStdString(sa->QuestionText));
auto answer = sa->Answer;
answer = transformAnswer(answer);
answer = std::format("- {}", answer);
mdems[i]->backLabels[0]->setText(QString::fromStdString(answer));
if (mdems[i]->wBack->isVisible()) {
mdems[i]->wBack->hide();
}
mdems[i]->labelCount = 1;
} else if (MultipleChoiceQuestion* mw = dynamic_cast<MultipleChoiceQuestion*>(questions[i])) {
mdems[i]->wFrontText->setText( mdems[i]->wFrontText->setText(
QString::fromStdString(mw->QuestionText) QString::fromStdString(mw->QuestionText)
); );

View File

@@ -199,6 +199,26 @@ Result<std::vector<Token>> TokenizeMdem(const std::string& fileRunes) {
previousColumn = column; previousColumn = column;
textStarted = false; textStarted = false;
break; 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 '>': case '>':
makeTokenWithTokenBuffer( makeTokenWithTokenBuffer(
TokenType::QuestionEnd, TokenType::QuestionEnd,
@@ -255,7 +275,9 @@ std::string Token::ToString(const TokenType* ttype) {
switch (*ttype) { switch (*ttype) {
case TokenType::TextFragment: return "text fragment"; case TokenType::TextFragment: return "text fragment";
case TokenType::QuestionEnd: return "question end symbol"; case TokenType::QuestionEnd: return "question end symbol";
case TokenType::MatchGroupEnd: return "match group end";
case TokenType::ElementDashStart: return "dash element start"; case TokenType::ElementDashStart: return "dash element start";
case TokenType::ElementOrderModifier: return "order element modifier";
case TokenType::ElementPlusStart: return "plus element start"; case TokenType::ElementPlusStart: return "plus element start";
case TokenType::Identifier: return "identifier"; case TokenType::Identifier: return "identifier";
case TokenType::IdentifierStart: return "start of identifier"; case TokenType::IdentifierStart: return "start of identifier";

View File

@@ -14,20 +14,11 @@
struct QuestionElement { struct QuestionElement {
bool isDash; bool isDash;
bool isGroup;
std::string content; std::string content;
}; };
std::string SingleAnswerQuestion::ToString() const { std::string MultiElementQuestion::ToString() const {
return std::format(
"<Single choice>:{} section:{} id:{} answer:{}",
QuestionText,
Section,
ID,
Answer
);
}
std::string MultipleChoiceQuestion::ToString() const {
std::stringstream choiceOut; std::stringstream choiceOut;
for (const auto& choice : Choices) { for (const auto& choice : Choices) {
char opener; char opener;
@@ -61,18 +52,26 @@ std::map<TokenType, std::vector<TokenType>> parserAutomata() {
TokenType::QuestionEnd, TokenType::QuestionEnd,
TokenType::ElementDashStart, TokenType::ElementDashStart,
TokenType::ElementPlusStart, TokenType::ElementPlusStart,
TokenType::MatchGroupEnd,
TokenType::SectionIdentifierStart, TokenType::SectionIdentifierStart,
TokenType::SectionStart, TokenType::SectionStart,
TokenType::EndOfFile, TokenType::EndOfFile,
TokenType::SectionEnd TokenType::SectionEnd
}; };
automata[TokenType::MatchGroupEnd] = {
TokenType::ElementDashStart
};
automata[TokenType::QuestionEnd] = { automata[TokenType::QuestionEnd] = {
TokenType::ElementDashStart, TokenType::ElementDashStart,
TokenType::ElementPlusStart TokenType::ElementPlusStart
}; };
automata[TokenType::ElementDashStart] = { automata[TokenType::ElementDashStart] = {
TokenType::IdentifierStart, TokenType::IdentifierStart,
TokenType::TextFragment TokenType::TextFragment,
TokenType::ElementOrderModifier
};
automata[TokenType::ElementOrderModifier] = {
TokenType::TextFragment
}; };
automata[TokenType::ElementPlusStart] = { automata[TokenType::ElementPlusStart] = {
TokenType::TextFragment TokenType::TextFragment
@@ -165,7 +164,10 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
std::string id, questionText; std::string id, questionText;
std::vector<QuestionElement> questionElements; std::vector<QuestionElement> questionElements;
// Parsing for a single question or multiple choice question bool isOrderQuestion = false;
bool isGroupQuestion = false;
// Start element parsing & add to the offset.
if (tokens[i + 1].tokenType == TokenType::IdentifierStart) { if (tokens[i + 1].tokenType == TokenType::IdentifierStart) {
id = tokens[i + 2].content; id = tokens[i + 2].content;
questionText = tokens[i + 4].content; questionText = tokens[i + 4].content;
@@ -176,17 +178,30 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
i += 3; i += 3;
} }
while (true) { auto isInBounds = [tokens](size_t i) {
if (i + 3 < tokens.size() && tokens[i + 3].tokenType != TokenType::EndOfFile) { return i < tokens.size() && tokens[i].tokenType != TokenType::EndOfFile;
size_t offset = tokens[i + 1].tokenType == TokenType::IdentifierStart ? 5 : 2; };
if (tokens[i].tokenType == TokenType::SectionIdentifierStart ||
tokens[i].tokenType == TokenType::SectionEnd) { // Parse elements of a question.
break; while (isInBounds(i)) {
}
if (i + offset < tokens.size() && tokens[i + offset].tokenType == TokenType::QuestionEnd) { // Handle other constructs.
if (tokens[i].tokenType == TokenType::SectionIdentifierStart) {
break;
}
if (tokens[i].tokenType == TokenType::SectionEnd) {
break;
}
// Check question end.
if (tokens[i].tokenType == TokenType::ElementDashStart && isInBounds(i + 3)) {
// Distance to the possible question end.
size_t offset = tokens[i + 1].tokenType == TokenType::IdentifierStart ? 5 : 2;
if (isInBounds(i + offset) && tokens[i + offset].tokenType == TokenType::QuestionEnd) {
break; break;
} }
if (offset == 5 && tokens[i + 5].tokenType != TokenType::QuestionEnd) { if (offset == 5 && tokens[i + 5].tokenType != TokenType::QuestionEnd) {
// Cannot place the identifier on the ordinary element.
return { return {
questions, questions,
"Invalid identifier placement", "Invalid identifier placement",
@@ -195,42 +210,72 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
}; };
} }
} }
if (i + 2 >= tokens.size()) {
break;
}
// Create question elements // Determine element type.
bool isDash;
bool isGroup = false;
if (isInBounds(i+1) && tokens[i].tokenType == TokenType::ElementOrderModifier) {
isOrderQuestion = true;
if (!isDash) {
// TODO: err
}
}
if (tokens[i].tokenType == TokenType::ElementDashStart) {
isDash = true;
if (isOrderQuestion) {
// TODO: err
}
} else {
isDash = false;
}
if (isInBounds(i+2) && tokens[i].tokenType == TokenType::MatchGroupEnd) {
isGroup = true;
isGroupQuestion = true;
if (!isDash) {
// TODO: err
}
if (isOrderQuestion) {
// TODO: err
}
}
QuestionElement questionElement; QuestionElement questionElement;
questionElement.isDash = (tokens[i].tokenType == TokenType::ElementDashStart); questionElement.isDash = isDash;
questionElement.isGroup = isGroup;
questionElement.content = tokens[i + 1].content; questionElement.content = tokens[i + 1].content;
questionElements.push_back(questionElement); questionElements.push_back(questionElement);
i += 2;
size_t offset = 2;
if (isOrderQuestion) {
offset += 1;
}
if (isGroup) {
offset += 1;
}
i += offset;
} }
if (questionElements.size() > 1) { if (questionElements.size() > 1) {
auto* mcq = new MultipleChoiceQuestion(); if (isGroupQuestion) {
mcq->ID = id; GroupQuestion *question = new GroupQuestion();
mcq->QuestionText = questionText; // TODO
for (const auto& elem : questionElements) {
Choice choice; } if (isOrderQuestion) {
choice.Answer = elem.content; auto *question = new MultiElementQuestion();
choice.IsCorrect = !elem.isDash; question->ID = id;
mcq->Choices.push_back(choice); question->QuestionText = questionText;
} for (const auto& elem : questionElements) {
mcq->Section = section; Choice choice;
questions.push_back(mcq); choice.Answer = elem.content;
if (debug) { choice.IsCorrect = !elem.isDash;
std::cout << mcq->ToString() << "\n"; question->Choices.push_back(choice);
} }
} else if (questionElements.size() == 1) { question->Section = section;
auto* saq = new SingleAnswerQuestion(); questions.push_back(question);
saq->ID = id; if (debug) {
saq->QuestionText = questionText; std::cout << question->ToString() << "\n";
saq->Answer = questionElements[0].content; }
saq->Section = section;
questions.push_back(saq);
if (debug) {
std::cout << saq->ToString() << "\n";
} }
} }
} else if (tokens[i].tokenType == TokenType::SectionIdentifierStart) { } else if (tokens[i].tokenType == TokenType::SectionIdentifierStart) {

37
tasks.md Normal file
View File

@@ -0,0 +1,37 @@
## 1-N Answer question
- kfoewf >
- fiewfiwe
- kfoewf >
- fiewfiwe
- fewpfowe
- fweofopew
- ofpewpofkew
## Order question
- ewjpfwe >
^- ewijfew
^- ewijfew
^- ewijfew
^- ewijfew
^- ewijfew
## Match question
- iowjefew >
- fiwfo:
- fewfew
- ifoewf
- ejpfe:
- _
- fewfw:
- fewjpfe
- fioewf
- [ ] Augment the lexer for Order and match question;
- [ ] Parse the questions accordingly;
- [ ] Escape character;