parser supports all needed question types

This commit is contained in:
jorenchik
2024-09-29 11:13:56 +03:00
parent 2c3b904853
commit 031803cbad
19 changed files with 143 additions and 49 deletions

View File

@@ -8,6 +8,10 @@
struct Question {
std::string ID;
std::string QuestionText;
std::string Section;
virtual std::string ToString() const = 0;
virtual ~Question() = default;
};
@@ -24,23 +28,18 @@ enum MultiElementType {
};
struct MultiElementQuestion : public Question {
std::string ID;
std::string QuestionText;
std::vector<Choice> Choices;
std::string Section;
MultiElementType type;
std::string ToString() const override;
};
struct Group {
std::string name;
std::string elements;
std::string name;
std::vector<std::string> elements;
};
struct GroupQuestion : public Question {
std::string ID;
std::string QuestionText;
std::vector<Group> Groups;
std::string ToString() const override;

View File

@@ -1,2 +1,3 @@
Debug
Release
.cache

View File

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

View File

@@ -1,2 +1,3 @@
Debug
Release
.cache

View File

@@ -111,7 +111,16 @@ Result<std::vector<Token>> TokenizeMdem(const std::string& fileRunes) {
row += 1;
column = 0;
}
buffer.push_back(c);
if (c == '\\') {
i += 1;
if (i < fileRunes.size()) {
buffer.push_back(fileRunes[i]);
}
continue;
} else {
buffer.push_back(c);
}
// SkipWhitetext
if (!textStarted) {

View File

@@ -19,25 +19,45 @@ struct QuestionElement {
};
std::string MultiElementQuestion::ToString() const {
std::stringstream choiceOut;
std::stringstream ss;
for (const auto& choice : Choices) {
char opener;
if (choice.IsCorrect) {
if (type == MultiElementType::Order) {
opener = '^';
} else if (choice.IsCorrect) {
opener = '+';
} else {
opener = '-';
}
choiceOut << opener << " " << choice.Answer << "; ";
ss << opener << " " << choice.Answer << "; ";
}
return std::format(
"<Multiple choice>:{} section: {} id: {}\n{}",
QuestionText,
Section,
ID,
choiceOut.str()
"<Multiple element>\nsection:{}\nid:{}\n{}\n{}",
Section,
ID,
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,
ID,
QuestionText,
ss.str()
);
}
// Automaton for validating token transitions
std::map<TokenType, std::vector<TokenType>> automata;
@@ -166,9 +186,22 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
std::vector<QuestionElement> questionElements;
bool isOrderQuestion = false;
bool isGroupQuestion = false;
bool isPlusQuestion = false;
auto isInBounds = [tokens](size_t i) {
return i < tokens.size() && tokens[i].tokenType != TokenType::EndOfFile;
};
// Start element parsing & add to the offset.
if (tokens[i + 1].tokenType == TokenType::IdentifierStart) {
if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
return {
questions,
"cannot have order modifier ('^') in the question definition",
tokens[i + 1].row,
tokens[i + 1].column
};
}
if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::IdentifierStart) {
id = tokens[i + 2].content;
questionText = tokens[i + 4].content;
i += 6;
@@ -178,10 +211,6 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
i += 3;
}
auto isInBounds = [tokens](size_t i) {
return i < tokens.size() && tokens[i].tokenType != TokenType::EndOfFile;
};
// Parse elements of a question.
while (isInBounds(i)) {
@@ -194,12 +223,17 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
}
// Check question end.
if (tokens[i].tokenType == TokenType::ElementDashStart && isInBounds(i + 3)) {
if (isInBounds(i + 3) && tokens[i].tokenType == TokenType::ElementDashStart) {
// Distance to the possible question end.
size_t offset = tokens[i + 1].tokenType == TokenType::IdentifierStart ? 5 : 2;
size_t offset;
if (tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
offset = tokens[i + 2].tokenType == TokenType::IdentifierStart ? 6 : 3;
} else {
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) {
// Cannot place the identifier on the ordinary element.
return {
@@ -214,39 +248,61 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
// Determine element type.
bool isDash;
bool isGroup = false;
if (isInBounds(i+1) && tokens[i].tokenType == TokenType::ElementOrderModifier) {
isOrderQuestion = true;
if (!isDash) {
// TODO: err
}
}
bool isOrder = false;
if (tokens[i].tokenType == TokenType::ElementDashStart) {
isDash = true;
if (isOrderQuestion) {
// TODO: err
}
} else {
isDash = false;
isPlusQuestion = true;
}
if (isInBounds(i+2) && tokens[i].tokenType == TokenType::MatchGroupEnd) {
if (isInBounds(i+1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) {
isOrder = true;
isOrderQuestion = true;
if (!isDash) {
return {
questions,
"order questions can only be used with dashes ('-')",
tokens[i].row,
tokens[i].column
};
}
if (isGroupQuestion) {
return {
questions,
"question with groups cannot be ordered ('-^' and ':')",
tokens[i].row,
tokens[i].column
};
}
if (isInBounds(i + 3) && tokens[i + 3].tokenType == TokenType::MatchGroupEnd) {
return {
questions,
"cannot have groups in order question('-^' and ':')",
tokens[i].row,
tokens[i].column
};
}
}
if (isInBounds(i + 2) && tokens[i + 2].tokenType == TokenType::MatchGroupEnd) {
isGroup = true;
isGroupQuestion = true;
if (!isDash) {
// TODO: err
}
if (isOrderQuestion) {
// TODO: err
return {questions, "group questions can only be used with dashes ('-')"};
}
}
QuestionElement questionElement;
questionElement.isDash = isDash;
questionElement.isGroup = isGroup;
questionElement.content = tokens[i + 1].content;
if (isOrder) {
questionElement.content = tokens[i + 2].content;
} else {
questionElement.content = tokens[i + 1].content;
}
questionElements.push_back(questionElement);
size_t offset = 2;
if (isOrderQuestion) {
if (isOrder) {
offset += 1;
}
if (isGroup) {
@@ -256,25 +312,53 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
i += offset;
}
if (questionElements.size() > 1) {
if (questionElements.size() > 0) {
if (isGroupQuestion) {
GroupQuestion *question = new GroupQuestion();
// TODO
} if (isOrderQuestion) {
auto *question = new GroupQuestion();
question->ID = id;
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 = questionElement.content;
question->Groups.push_back(group);
} else {
if (k >= 0) {
question->Groups[k].elements.push_back(
questionElement.content
);
}
}
}
questions.push_back(question);
if (debug) {
std::cout << question->ToString() << "\n";
}
} else {
auto *question = new MultiElementQuestion();
question->ID = id;
question->QuestionText = questionText;
question->Section = section;
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 (isPlusQuestion) {
question->type = MultiElementType::MultiChoice;
} else if (isOrderQuestion) {
question->type = MultiElementType::Order;
} else {
question->type = MultiElementType::Regular;
}
if (debug) {
std::cout << question->ToString() << "\n";
std::cout << question->ToString() << "\n";
}
}
}