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 {
TextFragment,
QuestionEnd,
MatchGroupEnd,
ElementDashStart,
ElementPlusStart,
ElementOrderModifier,
Identifier,
IdentifierStart,
IdentifierEnd,

View File

@@ -6,32 +6,44 @@
#include "lexer.h"
#include "result.h"
struct Question {
virtual std::string ToString() const = 0;
virtual ~Question() = default;
virtual ~Question() = default;
};
struct Choice {
std::string Answer;
bool IsCorrect;
bool IsCorrect;
};
struct SingleAnswerQuestion : public Question {
std::string ID;
std::string QuestionText;
std::string Answer;
std::string Section;
std::string ToString() const override;
enum MultiElementType {
Regular = 0,
MultiChoice,
Order
};
struct MultipleChoiceQuestion : public Question {
std::string ID;
std::string QuestionText;
std::vector<Choice> Choices;
std::string Section;
struct MultiElementQuestion : public Question {
std::string ID;
std::string QuestionText;
std::vector<Choice> Choices;
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);

View File

@@ -199,17 +199,7 @@ void CreateMdems(std::vector<Question*>& questions) {
};
for (size_t i = 0; i < questions.size(); ++i) {
if (SingleAnswerQuestion* sa = dynamic_cast<SingleAnswerQuestion*>(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])) {
if (AnswerQuestion* mw = dynamic_cast<AnswerQuestion*>(questions[i])) {
mdems[i]->wFrontText->setText(
QString::fromStdString(mw->QuestionText)
);

View File

@@ -199,6 +199,26 @@ Result<std::vector<Token>> TokenizeMdem(const std::string& fileRunes) {
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,
@@ -255,7 +275,9 @@ 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::Identifier: return "identifier";
case TokenType::IdentifierStart: return "start of identifier";

View File

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