Added if statement sequence diagram support
This commit is contained in:
@@ -39,7 +39,7 @@ enum class relationship_t {
|
|||||||
kDependency
|
kDependency
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class message_t { kCall, kReturn };
|
enum class message_t { kCall, kReturn, kIf, kElse, kElseIf, kIfEnd };
|
||||||
|
|
||||||
std::string to_string(relationship_t r);
|
std::string to_string(relationship_t r);
|
||||||
|
|
||||||
|
|||||||
@@ -113,12 +113,13 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
|
|||||||
std::vector<common::model::diagram_element::id_t> &visited) const
|
std::vector<common::model::diagram_element::id_t> &visited) const
|
||||||
{
|
{
|
||||||
for (const auto &m : a.messages) {
|
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);
|
const auto &to = m_model.get_participant<model::participant>(m.to);
|
||||||
if (!to)
|
if (!to)
|
||||||
continue;
|
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);
|
||||||
@@ -129,21 +130,35 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
|
|||||||
|
|
||||||
if (m_model.sequences.find(m.to) != m_model.sequences.end()) {
|
if (m_model.sequences.find(m.to) != m_model.sequences.end()) {
|
||||||
if (std::find(visited.begin(), visited.end(), m.to) ==
|
if (std::find(visited.begin(), visited.end(), m.to) ==
|
||||||
visited.end()) { // break infinite recursion on recursive calls
|
visited
|
||||||
|
.end()) { // break infinite recursion on recursive calls
|
||||||
LOG_DBG("Creating activity {} --> {} - missing sequence {}",
|
LOG_DBG("Creating activity {} --> {} - missing sequence {}",
|
||||||
m.from, m.to, m.to);
|
m.from, m.to, m.to);
|
||||||
generate_activity(m_model.sequences[m.to], ostr, visited);
|
generate_activity(m_model.sequences[m.to], ostr, visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LOG_DBG("Skipping activity {} --> {} - missing sequence {}", m.from,
|
LOG_DBG("Skipping activity {} --> {} - missing sequence {}",
|
||||||
m.to, m.to);
|
m.from, m.to, m.to);
|
||||||
|
|
||||||
generate_return(m, ostr);
|
generate_return(m, ostr);
|
||||||
|
|
||||||
visited.pop_back();
|
|
||||||
|
|
||||||
ostr << "deactivate " << to_alias << std::endl;
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,43 @@ struct call_expression_context {
|
|||||||
|
|
||||||
void leave_lambda_expression();
|
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::CXXRecordDecl *current_class_decl_;
|
||||||
clang::ClassTemplateDecl *current_class_template_decl_;
|
clang::ClassTemplateDecl *current_class_template_decl_;
|
||||||
clang::ClassTemplateSpecializationDecl
|
clang::ClassTemplateSpecializationDecl
|
||||||
@@ -83,6 +120,8 @@ struct call_expression_context {
|
|||||||
private:
|
private:
|
||||||
std::int64_t current_caller_id_;
|
std::int64_t current_caller_id_;
|
||||||
std::stack<std::int64_t> current_lambda_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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -539,6 +539,138 @@ bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
|
|||||||
return true;
|
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 ¤t_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)
|
bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
||||||
{
|
{
|
||||||
using clanguml::common::model::message_t;
|
using clanguml::common::model::message_t;
|
||||||
@@ -635,7 +767,6 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!process_function_call_expression(m, expr)) {
|
if (!process_function_call_expression(m, expr)) {
|
||||||
// expr->dump();
|
|
||||||
LOG_DBG("Skipping call to unsupported type of call expression "
|
LOG_DBG("Skipping call to unsupported type of call expression "
|
||||||
"at: {}",
|
"at: {}",
|
||||||
expr->getBeginLoc().printToString(source_manager()));
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
@@ -825,6 +956,9 @@ bool translation_unit_visitor::process_function_call_expression(
|
|||||||
|
|
||||||
auto callee_name = callee_function->getQualifiedNameAsString() + "()";
|
auto callee_name = callee_function->getQualifiedNameAsString() + "()";
|
||||||
|
|
||||||
|
if (!diagram().should_include(callee_name))
|
||||||
|
return false;
|
||||||
|
|
||||||
std::unique_ptr<model::function_template> f_ptr;
|
std::unique_ptr<model::function_template> f_ptr;
|
||||||
|
|
||||||
if (!get_unique_id(callee_function->getID()).has_value()) {
|
if (!get_unique_id(callee_function->getID()).has_value()) {
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ public:
|
|||||||
bool VisitFunctionTemplateDecl(
|
bool VisitFunctionTemplateDecl(
|
||||||
clang::FunctionTemplateDecl *function_declaration);
|
clang::FunctionTemplateDecl *function_declaration);
|
||||||
|
|
||||||
|
bool TraverseCompoundStmt(clang::CompoundStmt *stmt);
|
||||||
|
|
||||||
|
bool TraverseIfStmt(clang::IfStmt *stmt);
|
||||||
|
|
||||||
clanguml::sequence_diagram::model::diagram &diagram();
|
clanguml::sequence_diagram::model::diagram &diagram();
|
||||||
|
|
||||||
const clanguml::sequence_diagram::model::diagram &diagram() const;
|
const clanguml::sequence_diagram::model::diagram &diagram() const;
|
||||||
|
|||||||
14
tests/t20020/.clang-uml
Normal file
14
tests/t20020/.clang-uml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
compilation_database_dir: ..
|
||||||
|
output_directory: puml
|
||||||
|
diagrams:
|
||||||
|
t20020_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- ../../tests/t20020/t20020.cc
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20020
|
||||||
|
using_namespace:
|
||||||
|
- clanguml::t20020
|
||||||
|
start_from:
|
||||||
|
- function: "clanguml::t20020::tmain()"
|
||||||
56
tests/t20020/t20020.cc
Normal file
56
tests/t20020/t20020.cc
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace clanguml {
|
||||||
|
namespace t20020 {
|
||||||
|
struct A {
|
||||||
|
int a1() { return 0; }
|
||||||
|
int a2() { return 1; }
|
||||||
|
int a3() { return 2; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct B {
|
||||||
|
void log() { }
|
||||||
|
|
||||||
|
int b1() { return 3; }
|
||||||
|
int b2() { return 4; }
|
||||||
|
};
|
||||||
|
|
||||||
|
int tmain()
|
||||||
|
{
|
||||||
|
A a;
|
||||||
|
B b;
|
||||||
|
|
||||||
|
int result{0};
|
||||||
|
|
||||||
|
if (reinterpret_cast<uint64_t>(&a) % 100 == 0ULL) {
|
||||||
|
result = a.a1();
|
||||||
|
}
|
||||||
|
else if (reinterpret_cast<uint64_t>(&a) % 64 == 0ULL) {
|
||||||
|
if (a.a2() > 2)
|
||||||
|
result = b.b1();
|
||||||
|
else
|
||||||
|
result = b.b2();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = a.a3();
|
||||||
|
}
|
||||||
|
|
||||||
|
b.log();
|
||||||
|
|
||||||
|
// This if/else should not be included in the diagram at all
|
||||||
|
// as the calls to std will be excluded by the diagram filters
|
||||||
|
if (result != 2) {
|
||||||
|
result = std::exp(result);
|
||||||
|
}
|
||||||
|
else if(result == 3) {
|
||||||
|
result = 4;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = std::exp(result + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
tests/t20020/test_case.h
Normal file
49
tests/t20020/test_case.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20020/test_case.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
TEST_CASE("t20020", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20020");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20020_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20020_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20020_sequence");
|
||||||
|
|
||||||
|
auto puml = generate_sequence_puml(diagram, *model);
|
||||||
|
AliasMatcher _A(puml);
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||||
|
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||||
|
|
||||||
|
|
||||||
|
// Check if all calls exist
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a1()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a2()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a3()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b1()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b2()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "log()"));
|
||||||
|
|
||||||
|
save_puml(
|
||||||
|
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||||
|
}
|
||||||
@@ -266,6 +266,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20017/test_case.h"
|
#include "t20017/test_case.h"
|
||||||
#include "t20018/test_case.h"
|
#include "t20018/test_case.h"
|
||||||
#include "t20019/test_case.h"
|
#include "t20019/test_case.h"
|
||||||
|
#include "t20020/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
@@ -205,6 +205,9 @@ test_cases:
|
|||||||
- name: t20019
|
- name: t20019
|
||||||
title: Curiously Recurring Template Pattern sequence diagram test case
|
title: Curiously Recurring Template Pattern sequence diagram test case
|
||||||
description:
|
description:
|
||||||
|
- name: t20020
|
||||||
|
title: If statement sequence diagram test case
|
||||||
|
description:
|
||||||
Package diagrams:
|
Package diagrams:
|
||||||
- name: t30001
|
- name: t30001
|
||||||
title: Basic package diagram test case
|
title: Basic package diagram test case
|
||||||
|
|||||||
Reference in New Issue
Block a user