mirror of
https://github.com/jorenchik/mdemory.git
synced 2026-03-22 00:26:21 +00:00
parser supports all needed question types
This commit is contained in:
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
|
|
||||||
struct Question {
|
struct Question {
|
||||||
|
std::string ID;
|
||||||
|
std::string QuestionText;
|
||||||
|
std::string Section;
|
||||||
|
|
||||||
virtual std::string ToString() const = 0;
|
virtual std::string ToString() const = 0;
|
||||||
virtual ~Question() = default;
|
virtual ~Question() = default;
|
||||||
};
|
};
|
||||||
@@ -24,23 +28,18 @@ enum MultiElementType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct MultiElementQuestion : public Question {
|
struct MultiElementQuestion : public Question {
|
||||||
std::string ID;
|
|
||||||
std::string QuestionText;
|
|
||||||
std::vector<Choice> Choices;
|
std::vector<Choice> Choices;
|
||||||
std::string Section;
|
|
||||||
MultiElementType type;
|
MultiElementType type;
|
||||||
|
|
||||||
std::string ToString() const override;
|
std::string ToString() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Group {
|
struct Group {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string elements;
|
std::vector<std::string> elements;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GroupQuestion : public Question {
|
struct GroupQuestion : public Question {
|
||||||
std::string ID;
|
|
||||||
std::string QuestionText;
|
|
||||||
std::vector<Group> Groups;
|
std::vector<Group> Groups;
|
||||||
|
|
||||||
std::string ToString() const override;
|
std::string ToString() const override;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
1
src/cpp/qtapp/.gitignore
vendored
1
src/cpp/qtapp/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
Debug
|
Debug
|
||||||
Release
|
Release
|
||||||
|
.cache
|
||||||
|
|||||||
@@ -199,7 +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 (AnswerQuestion* mw = dynamic_cast<AnswerQuestion*>(questions[i])) {
|
if (MultiElementQuestion* mw = dynamic_cast<MultiElementQuestion*>(questions[i])) {
|
||||||
mdems[i]->wFrontText->setText(
|
mdems[i]->wFrontText->setText(
|
||||||
QString::fromStdString(mw->QuestionText)
|
QString::fromStdString(mw->QuestionText)
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
src/cpp/transpiler/.gitignore
vendored
1
src/cpp/transpiler/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
Debug
|
Debug
|
||||||
Release
|
Release
|
||||||
|
.cache
|
||||||
|
|||||||
@@ -111,7 +111,16 @@ Result<std::vector<Token>> TokenizeMdem(const std::string& fileRunes) {
|
|||||||
row += 1;
|
row += 1;
|
||||||
column = 0;
|
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
|
// SkipWhitetext
|
||||||
if (!textStarted) {
|
if (!textStarted) {
|
||||||
|
|||||||
@@ -19,25 +19,45 @@ struct QuestionElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::string MultiElementQuestion::ToString() const {
|
std::string MultiElementQuestion::ToString() const {
|
||||||
std::stringstream choiceOut;
|
std::stringstream ss;
|
||||||
for (const auto& choice : Choices) {
|
for (const auto& choice : Choices) {
|
||||||
char opener;
|
char opener;
|
||||||
if (choice.IsCorrect) {
|
if (type == MultiElementType::Order) {
|
||||||
|
opener = '^';
|
||||||
|
} else if (choice.IsCorrect) {
|
||||||
opener = '+';
|
opener = '+';
|
||||||
} else {
|
} else {
|
||||||
opener = '-';
|
opener = '-';
|
||||||
}
|
}
|
||||||
choiceOut << opener << " " << choice.Answer << "; ";
|
ss << opener << " " << choice.Answer << "; ";
|
||||||
}
|
}
|
||||||
return std::format(
|
return std::format(
|
||||||
"<Multiple choice>:{} section: {} id: {}\n{}",
|
"<Multiple element>\nsection:{}\nid:{}\n{}\n{}",
|
||||||
QuestionText,
|
Section,
|
||||||
Section,
|
ID,
|
||||||
ID,
|
QuestionText,
|
||||||
choiceOut.str()
|
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
|
// Automaton for validating token transitions
|
||||||
std::map<TokenType, std::vector<TokenType>> automata;
|
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;
|
std::vector<QuestionElement> questionElements;
|
||||||
bool isOrderQuestion = false;
|
bool isOrderQuestion = false;
|
||||||
bool isGroupQuestion = 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.
|
// 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;
|
id = tokens[i + 2].content;
|
||||||
questionText = tokens[i + 4].content;
|
questionText = tokens[i + 4].content;
|
||||||
i += 6;
|
i += 6;
|
||||||
@@ -178,10 +211,6 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
|
|||||||
i += 3;
|
i += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto isInBounds = [tokens](size_t i) {
|
|
||||||
return i < tokens.size() && tokens[i].tokenType != TokenType::EndOfFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse elements of a question.
|
// Parse elements of a question.
|
||||||
while (isInBounds(i)) {
|
while (isInBounds(i)) {
|
||||||
|
|
||||||
@@ -194,12 +223,17 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check question end.
|
// 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.
|
// 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) {
|
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.
|
// Cannot place the identifier on the ordinary element.
|
||||||
return {
|
return {
|
||||||
@@ -214,39 +248,61 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
|
|||||||
// Determine element type.
|
// Determine element type.
|
||||||
bool isDash;
|
bool isDash;
|
||||||
bool isGroup = false;
|
bool isGroup = false;
|
||||||
if (isInBounds(i+1) && tokens[i].tokenType == TokenType::ElementOrderModifier) {
|
bool isOrder = false;
|
||||||
isOrderQuestion = true;
|
|
||||||
if (!isDash) {
|
|
||||||
// TODO: err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tokens[i].tokenType == TokenType::ElementDashStart) {
|
if (tokens[i].tokenType == TokenType::ElementDashStart) {
|
||||||
isDash = true;
|
isDash = true;
|
||||||
if (isOrderQuestion) {
|
|
||||||
// TODO: err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
isDash = false;
|
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;
|
isGroup = true;
|
||||||
isGroupQuestion = true;
|
isGroupQuestion = true;
|
||||||
if (!isDash) {
|
if (!isDash) {
|
||||||
// TODO: err
|
return {questions, "group questions can only be used with dashes ('-')"};
|
||||||
}
|
|
||||||
if (isOrderQuestion) {
|
|
||||||
// TODO: err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QuestionElement questionElement;
|
QuestionElement questionElement;
|
||||||
questionElement.isDash = isDash;
|
questionElement.isDash = isDash;
|
||||||
questionElement.isGroup = isGroup;
|
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);
|
questionElements.push_back(questionElement);
|
||||||
|
|
||||||
size_t offset = 2;
|
size_t offset = 2;
|
||||||
if (isOrderQuestion) {
|
if (isOrder) {
|
||||||
offset += 1;
|
offset += 1;
|
||||||
}
|
}
|
||||||
if (isGroup) {
|
if (isGroup) {
|
||||||
@@ -256,25 +312,53 @@ Result<std::vector<Question*>> ParseQuestions(const std::vector<Token>& tokens)
|
|||||||
i += offset;
|
i += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (questionElements.size() > 1) {
|
if (questionElements.size() > 0) {
|
||||||
if (isGroupQuestion) {
|
if (isGroupQuestion) {
|
||||||
GroupQuestion *question = new GroupQuestion();
|
auto *question = new GroupQuestion();
|
||||||
// TODO
|
question->ID = id;
|
||||||
|
question->QuestionText = questionText;
|
||||||
} if (isOrderQuestion) {
|
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();
|
auto *question = new MultiElementQuestion();
|
||||||
question->ID = id;
|
question->ID = id;
|
||||||
question->QuestionText = questionText;
|
question->QuestionText = questionText;
|
||||||
|
question->Section = section;
|
||||||
for (const auto& elem : questionElements) {
|
for (const auto& elem : questionElements) {
|
||||||
Choice choice;
|
Choice choice;
|
||||||
choice.Answer = elem.content;
|
choice.Answer = elem.content;
|
||||||
choice.IsCorrect = !elem.isDash;
|
choice.IsCorrect = !elem.isDash;
|
||||||
question->Choices.push_back(choice);
|
question->Choices.push_back(choice);
|
||||||
}
|
}
|
||||||
question->Section = section;
|
|
||||||
questions.push_back(question);
|
questions.push_back(question);
|
||||||
|
if (isPlusQuestion) {
|
||||||
|
question->type = MultiElementType::MultiChoice;
|
||||||
|
} else if (isOrderQuestion) {
|
||||||
|
question->type = MultiElementType::Order;
|
||||||
|
} else {
|
||||||
|
question->type = MultiElementType::Regular;
|
||||||
|
}
|
||||||
if (debug) {
|
if (debug) {
|
||||||
std::cout << question->ToString() << "\n";
|
std::cout << question->ToString() << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user