Added if statement sequence diagram support

This commit is contained in:
Bartek Kryza
2022-12-11 00:12:31 +01:00
parent dae8513529
commit 13dae33d99
10 changed files with 340 additions and 25 deletions

View File

@@ -39,7 +39,7 @@ enum class relationship_t {
kDependency
};
enum class message_t { kCall, kReturn };
enum class message_t { kCall, kReturn, kIf, kElse, kElseIf, kIfEnd };
std::string to_string(relationship_t r);

View File

@@ -113,37 +113,52 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
std::vector<common::model::diagram_element::id_t> &visited) const
{
for (const auto &m : a.messages) {
visited.push_back(m.from);
if (m.type == message_t::kCall) {
const auto &to = m_model.get_participant<model::participant>(m.to);
if (!to)
continue;
const auto &to = m_model.get_participant<model::participant>(m.to);
if (!to)
continue;
visited.push_back(m.from);
LOG_DBG("Generating message {} --> {}", m.from, m.to);
LOG_DBG("Generating message {} --> {}", m.from, m.to);
generate_call(m, ostr);
generate_call(m, ostr);
std::string to_alias = generate_alias(to.value());
std::string to_alias = generate_alias(to.value());
ostr << "activate " << to_alias << std::endl;
ostr << "activate " << to_alias << std::endl;
if (m_model.sequences.find(m.to) != m_model.sequences.end()) {
if (std::find(visited.begin(), visited.end(), m.to) ==
visited.end()) { // break infinite recursion on recursive calls
LOG_DBG("Creating activity {} --> {} - missing sequence {}",
m.from, m.to, m.to);
generate_activity(m_model.sequences[m.to], ostr, visited);
if (m_model.sequences.find(m.to) != m_model.sequences.end()) {
if (std::find(visited.begin(), visited.end(), m.to) ==
visited
.end()) { // break infinite recursion on recursive calls
LOG_DBG("Creating activity {} --> {} - missing sequence {}",
m.from, m.to, m.to);
generate_activity(m_model.sequences[m.to], ostr, visited);
}
}
else
LOG_DBG("Skipping activity {} --> {} - missing sequence {}",
m.from, m.to, m.to);
generate_return(m, ostr);
ostr << "deactivate " << to_alias << std::endl;
visited.pop_back();
}
else if (m.type == message_t::kIf) {
ostr << "alt\n";
}
else if (m.type == message_t::kElseIf) {
ostr << "else\n";
}
else if (m.type == message_t::kElse) {
ostr << "else\n";
}
else if (m.type == message_t::kIfEnd) {
ostr << "end\n";
}
else
LOG_DBG("Skipping activity {} --> {} - missing sequence {}", m.from,
m.to, m.to);
generate_return(m, ostr);
visited.pop_back();
ostr << "deactivate " << to_alias << std::endl;
}
}

View File

@@ -70,6 +70,43 @@ struct call_expression_context {
void leave_lambda_expression();
clang::IfStmt *current_ifstmt() const
{
if (if_stmt_stack_.empty())
return nullptr;
return if_stmt_stack_.top();
}
void enter_ifstmt(clang::IfStmt *stmt) { return if_stmt_stack_.push(stmt); }
void leave_ifstmt()
{
if (!if_stmt_stack_.empty()) {
if_stmt_stack_.pop();
std::stack<clang::IfStmt *>{}.swap(elseif_stmt_stack_);
}
}
void enter_elseifstmt(clang::IfStmt *stmt)
{
return elseif_stmt_stack_.push(stmt);
}
void leave_elseifstmt()
{
if (elseif_stmt_stack_.empty())
return elseif_stmt_stack_.pop();
}
clang::IfStmt *current_elseifstmt() const
{
if (elseif_stmt_stack_.empty())
return nullptr;
return elseif_stmt_stack_.top();
}
clang::CXXRecordDecl *current_class_decl_;
clang::ClassTemplateDecl *current_class_template_decl_;
clang::ClassTemplateSpecializationDecl
@@ -83,6 +120,8 @@ struct call_expression_context {
private:
std::int64_t current_caller_id_;
std::stack<std::int64_t> current_lambda_caller_id_;
std::stack<clang::IfStmt *> if_stmt_stack_;
std::stack<clang::IfStmt *> elseif_stmt_stack_;
};
}

View File

@@ -539,6 +539,138 @@ bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
return true;
}
bool translation_unit_visitor::TraverseCompoundStmt(clang::CompoundStmt *stmt)
{
using clanguml::common::model::message_t;
using clanguml::common::model::namespace_;
using clanguml::sequence_diagram::model::activity;
using clanguml::sequence_diagram::model::message;
const auto *current_ifstmt = context().current_ifstmt();
const auto *current_elseifstmt = context().current_elseifstmt();
//
// Add final else block (not else if)
//
if (current_elseifstmt != nullptr) {
if (current_elseifstmt->getElse() == stmt) {
const auto current_caller_id = context().caller_id();
if (current_caller_id) {
message m;
m.from = current_caller_id;
m.type = message_t::kElse;
diagram().sequences[current_caller_id].messages.emplace_back(
std::move(m));
}
}
}
else if(current_ifstmt != nullptr) {
if (current_ifstmt->getElse() == stmt) {
const auto current_caller_id = context().caller_id();
if (current_caller_id) {
message m;
m.from = current_caller_id;
m.type = message_t::kElse;
diagram().sequences[current_caller_id].messages.emplace_back(
std::move(m));
}
}
}
RecursiveASTVisitor<translation_unit_visitor>::TraverseCompoundStmt(stmt);
return true;
}
bool translation_unit_visitor::TraverseIfStmt(clang::IfStmt *stmt)
{
using clanguml::common::model::message_t;
using clanguml::common::model::namespace_;
using clanguml::sequence_diagram::model::activity;
using clanguml::sequence_diagram::model::message;
const auto *current_ifstmt = context().current_ifstmt();
context().enter_ifstmt(stmt);
const auto current_caller_id = context().caller_id();
bool elseif_block{false};
if (current_caller_id) {
if (diagram().sequences.find(current_caller_id) ==
diagram().sequences.end()) {
activity a;
a.from = current_caller_id;
diagram().sequences.insert({current_caller_id, std::move(a)});
}
message m;
m.from = current_caller_id;
// Check if this is a beginning of a new if statement, or an
// else if condition of the current if statement
if (current_ifstmt != nullptr) {
for (const auto *child_stmt : current_ifstmt->children()) {
if (child_stmt == stmt) {
elseif_block = true;
break;
}
}
}
if (elseif_block) {
m.type = message_t::kElseIf;
context().enter_elseifstmt(stmt);
}
else {
m.type = message_t::kIf;
}
diagram().sequences[current_caller_id].messages.emplace_back(
std::move(m));
}
RecursiveASTVisitor<translation_unit_visitor>::TraverseIfStmt(stmt);
context().leave_ifstmt();
if (current_caller_id && !elseif_block) {
message m;
m.from = current_caller_id;
m.type = message_t::kIfEnd;
if (diagram().sequences.find(current_caller_id) !=
diagram().sequences.end()) {
auto &current_messages =
diagram().sequences[current_caller_id].messages;
// Remove the if/else messages if there were no calls
// added to the diagram between them
auto last_if_it =
std::find_if(current_messages.rbegin(), current_messages.rend(),
[](const message &m) { return m.type == message_t::kIf; });
bool last_if_block_is_empty = std::none_of(
current_messages.rbegin(), last_if_it,
[](const message &m) { return m.type == message_t::kCall; });
if (!last_if_block_is_empty) {
current_messages.emplace_back(std::move(m));
}
else {
current_messages.erase(
(last_if_it + 1).base(), current_messages.end());
}
}
}
return true;
}
bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
{
using clanguml::common::model::message_t;
@@ -635,7 +767,6 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
}
else {
if (!process_function_call_expression(m, expr)) {
// expr->dump();
LOG_DBG("Skipping call to unsupported type of call expression "
"at: {}",
expr->getBeginLoc().printToString(source_manager()));
@@ -825,6 +956,9 @@ bool translation_unit_visitor::process_function_call_expression(
auto callee_name = callee_function->getQualifiedNameAsString() + "()";
if (!diagram().should_include(callee_name))
return false;
std::unique_ptr<model::function_template> f_ptr;
if (!get_unique_id(callee_function->getID()).has_value()) {

View File

@@ -64,6 +64,10 @@ public:
bool VisitFunctionTemplateDecl(
clang::FunctionTemplateDecl *function_declaration);
bool TraverseCompoundStmt(clang::CompoundStmt *stmt);
bool TraverseIfStmt(clang::IfStmt *stmt);
clanguml::sequence_diagram::model::diagram &diagram();
const clanguml::sequence_diagram::model::diagram &diagram() const;