Added support for try/catch statements in sequence diagrams
This commit is contained in:
@@ -94,6 +94,12 @@ std::string to_string(message_t r)
|
||||
return "for";
|
||||
case message_t::kForEnd:
|
||||
return "end for";
|
||||
case message_t::kTry:
|
||||
return "try";
|
||||
case message_t::kCatch:
|
||||
return "catch";
|
||||
case message_t::kTryEnd:
|
||||
return "end try";
|
||||
default:
|
||||
assert(false);
|
||||
return "";
|
||||
|
||||
@@ -53,6 +53,9 @@ enum class message_t {
|
||||
kDoEnd,
|
||||
kFor,
|
||||
kForEnd,
|
||||
kTry,
|
||||
kCatch,
|
||||
kTryEnd,
|
||||
kNone
|
||||
};
|
||||
|
||||
|
||||
@@ -190,6 +190,15 @@ void generator::generate_activity(const activity &a, std::ostream &ostr,
|
||||
else if (m.type() == message_t::kDoEnd) {
|
||||
ostr << "end\n";
|
||||
}
|
||||
else if (m.type() == message_t::kTry) {
|
||||
ostr << "group try\n";
|
||||
}
|
||||
else if (m.type() == message_t::kCatch) {
|
||||
ostr << "else " << m.message_name() << '\n';
|
||||
}
|
||||
else if (m.type() == message_t::kTryEnd) {
|
||||
ostr << "end\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,6 +224,55 @@ void diagram::end_if_stmt(
|
||||
}
|
||||
}
|
||||
|
||||
void diagram::add_try_stmt(
|
||||
const common::model::diagram_element::id_t current_caller_id)
|
||||
{
|
||||
using clanguml::common::model::message_t;
|
||||
|
||||
if (sequences_.find(current_caller_id) == sequences_.end()) {
|
||||
activity a{current_caller_id};
|
||||
sequences_.insert({current_caller_id, std::move(a)});
|
||||
}
|
||||
|
||||
get_activity(current_caller_id)
|
||||
.add_message({message_t::kTry, current_caller_id});
|
||||
}
|
||||
|
||||
void diagram::end_try_stmt(
|
||||
const common::model::diagram_element::id_t current_caller_id)
|
||||
{
|
||||
using clanguml::common::model::message_t;
|
||||
|
||||
message m{message_t::kTryEnd, current_caller_id};
|
||||
|
||||
if (sequences_.find(current_caller_id) != sequences_.end()) {
|
||||
auto ¤t_messages = get_activity(current_caller_id).messages();
|
||||
|
||||
if (current_messages.back().type() == message_t::kTry) {
|
||||
current_messages.pop_back();
|
||||
}
|
||||
else {
|
||||
current_messages.emplace_back(std::move(m));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void diagram::add_catch_stmt(
|
||||
const int64_t current_caller_id, std::string caught_type)
|
||||
{
|
||||
using clanguml::common::model::message_t;
|
||||
|
||||
if (sequences_.find(current_caller_id) == sequences_.end()) {
|
||||
activity a{current_caller_id};
|
||||
sequences_.insert({current_caller_id, std::move(a)});
|
||||
}
|
||||
|
||||
message m{message_t::kCatch, current_caller_id};
|
||||
m.set_message_name(std::move(caught_type));
|
||||
|
||||
get_activity(current_caller_id).add_message(std::move(m));
|
||||
}
|
||||
|
||||
bool diagram::started() const { return started_; }
|
||||
|
||||
void diagram::started(bool s) { started_ = s; }
|
||||
@@ -255,13 +304,13 @@ diagram::participants() const
|
||||
std::set<common::model::diagram_element::id_t> &diagram::active_participants()
|
||||
{
|
||||
return active_participants_;
|
||||
};
|
||||
}
|
||||
|
||||
const std::set<common::model::diagram_element::id_t> &
|
||||
diagram::active_participants() const
|
||||
{
|
||||
return active_participants_;
|
||||
};
|
||||
}
|
||||
|
||||
void diagram::print() const
|
||||
{
|
||||
@@ -305,7 +354,6 @@ void diagram::print() const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace clanguml::common::model {
|
||||
|
||||
@@ -85,6 +85,9 @@ public:
|
||||
void end_if_stmt(common::model::diagram_element::id_t current_caller_id,
|
||||
common::model::message_t type);
|
||||
|
||||
void add_try_stmt(common::model::diagram_element::id_t current_caller_id);
|
||||
void end_try_stmt(common::model::diagram_element::id_t current_caller_id);
|
||||
|
||||
void add_loop_stmt(common::model::diagram_element::id_t current_caller_id,
|
||||
common::model::message_t type);
|
||||
void end_loop_stmt(common::model::diagram_element::id_t current_caller_id,
|
||||
@@ -119,6 +122,10 @@ public:
|
||||
const std::set<common::model::diagram_element::id_t> &
|
||||
active_participants() const;
|
||||
|
||||
void add_catch_stmt(
|
||||
const common::model::diagram_element::id_t current_caller_id,
|
||||
std::string caught_type);
|
||||
|
||||
private:
|
||||
bool started_{false};
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ clang::IfStmt *call_expression_context::current_ifstmt() const
|
||||
|
||||
void call_expression_context::enter_ifstmt(clang::IfStmt *stmt)
|
||||
{
|
||||
return if_stmt_stack_.push(stmt);
|
||||
if_stmt_stack_.push(stmt);
|
||||
}
|
||||
|
||||
void call_expression_context::leave_ifstmt()
|
||||
@@ -209,13 +209,13 @@ void call_expression_context::leave_ifstmt()
|
||||
|
||||
void call_expression_context::enter_elseifstmt(clang::IfStmt *stmt)
|
||||
{
|
||||
return elseif_stmt_stack_.push(stmt);
|
||||
elseif_stmt_stack_.push(stmt);
|
||||
}
|
||||
|
||||
void call_expression_context::leave_elseifstmt()
|
||||
{
|
||||
if (elseif_stmt_stack_.empty())
|
||||
return elseif_stmt_stack_.pop();
|
||||
elseif_stmt_stack_.pop();
|
||||
}
|
||||
|
||||
clang::IfStmt *call_expression_context::current_elseifstmt() const
|
||||
@@ -236,7 +236,7 @@ clang::Stmt *call_expression_context::current_loopstmt() const
|
||||
|
||||
void call_expression_context::enter_loopstmt(clang::Stmt *stmt)
|
||||
{
|
||||
return loop_stmt_stack_.push(stmt);
|
||||
loop_stmt_stack_.push(stmt);
|
||||
}
|
||||
|
||||
void call_expression_context::leave_loopstmt()
|
||||
@@ -245,6 +245,25 @@ void call_expression_context::leave_loopstmt()
|
||||
return loop_stmt_stack_.pop();
|
||||
}
|
||||
|
||||
clang::Stmt *call_expression_context::current_trystmt() const
|
||||
{
|
||||
if (try_stmt_stack_.empty())
|
||||
return nullptr;
|
||||
|
||||
return try_stmt_stack_.top();
|
||||
}
|
||||
|
||||
void call_expression_context::enter_trystmt(clang::Stmt *stmt)
|
||||
{
|
||||
try_stmt_stack_.push(stmt);
|
||||
}
|
||||
|
||||
void call_expression_context::leave_trystmt()
|
||||
{
|
||||
if (try_stmt_stack_.empty())
|
||||
try_stmt_stack_.pop();
|
||||
}
|
||||
|
||||
bool call_expression_context::is_expr_in_current_control_statement_condition(
|
||||
const clang::Stmt *stmt) const
|
||||
{
|
||||
|
||||
@@ -74,13 +74,16 @@ struct call_expression_context {
|
||||
|
||||
void enter_elseifstmt(clang::IfStmt *stmt);
|
||||
void leave_elseifstmt();
|
||||
|
||||
clang::IfStmt *current_elseifstmt() const;
|
||||
clang::Stmt *current_loopstmt() const;
|
||||
|
||||
clang::Stmt *current_loopstmt() const;
|
||||
void enter_loopstmt(clang::Stmt *stmt);
|
||||
void leave_loopstmt();
|
||||
|
||||
clang::Stmt *current_trystmt() const;
|
||||
void enter_trystmt(clang::Stmt *stmt);
|
||||
void leave_trystmt();
|
||||
|
||||
bool is_expr_in_current_control_statement_condition(
|
||||
const clang::Stmt *stmt) const;
|
||||
|
||||
@@ -101,6 +104,7 @@ private:
|
||||
std::stack<clang::IfStmt *> elseif_stmt_stack_;
|
||||
|
||||
std::stack<clang::Stmt *> loop_stmt_stack_;
|
||||
std::stack<clang::Stmt *> try_stmt_stack_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -705,6 +705,53 @@ bool translation_unit_visitor::TraverseForStmt(clang::ForStmt *stmt)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool translation_unit_visitor::TraverseCXXTryStmt(clang::CXXTryStmt *stmt)
|
||||
{
|
||||
using clanguml::common::model::message_t;
|
||||
using clanguml::sequence_diagram::model::activity;
|
||||
using clanguml::sequence_diagram::model::message;
|
||||
|
||||
const auto current_caller_id = context().caller_id();
|
||||
|
||||
if (current_caller_id) {
|
||||
context().enter_trystmt(stmt);
|
||||
diagram().add_try_stmt(current_caller_id);
|
||||
}
|
||||
|
||||
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXTryStmt(stmt);
|
||||
|
||||
if (current_caller_id) {
|
||||
context().leave_trystmt();
|
||||
diagram().end_try_stmt(current_caller_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool translation_unit_visitor::TraverseCXXCatchStmt(clang::CXXCatchStmt *stmt)
|
||||
{
|
||||
using clanguml::common::model::message_t;
|
||||
using clanguml::sequence_diagram::model::activity;
|
||||
using clanguml::sequence_diagram::model::message;
|
||||
|
||||
const auto current_caller_id = context().caller_id();
|
||||
|
||||
if (current_caller_id && context().current_trystmt()) {
|
||||
std::string caught_type;
|
||||
if (stmt->getCaughtType().isNull())
|
||||
caught_type = "...";
|
||||
else
|
||||
caught_type = common::to_string(
|
||||
stmt->getCaughtType(), *context().get_ast_context());
|
||||
|
||||
diagram().add_catch_stmt(current_caller_id, std::move(caught_type));
|
||||
}
|
||||
|
||||
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXCatchStmt(stmt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool translation_unit_visitor::TraverseCXXForRangeStmt(
|
||||
clang::CXXForRangeStmt *stmt)
|
||||
{
|
||||
|
||||
@@ -76,6 +76,10 @@ public:
|
||||
|
||||
bool TraverseCXXForRangeStmt(clang::CXXForRangeStmt *stmt);
|
||||
|
||||
bool TraverseCXXTryStmt(clang::CXXTryStmt *stmt);
|
||||
|
||||
bool TraverseCXXCatchStmt(clang::CXXCatchStmt *stmt);
|
||||
|
||||
clanguml::sequence_diagram::model::diagram &diagram();
|
||||
|
||||
const clanguml::sequence_diagram::model::diagram &diagram() const;
|
||||
|
||||
14
tests/t20023/.clang-uml
Normal file
14
tests/t20023/.clang-uml
Normal file
@@ -0,0 +1,14 @@
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t20023_sequence:
|
||||
type: sequence
|
||||
glob:
|
||||
- ../../tests/t20023/t20023.cc
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t20023
|
||||
using_namespace:
|
||||
- clanguml::t20023
|
||||
start_from:
|
||||
- function: "clanguml::t20023::tmain()"
|
||||
40
tests/t20023/t20023.cc
Normal file
40
tests/t20023/t20023.cc
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <stdexcept>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t20023 {
|
||||
|
||||
struct A {
|
||||
int a1() { return 1; }
|
||||
int a2() { return 2; }
|
||||
int a3() { return 3; }
|
||||
int a4() { return 3; }
|
||||
|
||||
int a()
|
||||
{
|
||||
try {
|
||||
return a1();
|
||||
}
|
||||
catch (std::runtime_error &) {
|
||||
return a2();
|
||||
}
|
||||
catch (std::logic_error &) {
|
||||
return a3();
|
||||
}
|
||||
catch (...) {
|
||||
return a4();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int tmain()
|
||||
{
|
||||
A a;
|
||||
|
||||
int result{};
|
||||
|
||||
result = a.a();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
tests/t20023/test_case.h
Normal file
46
tests/t20023/test_case.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* tests/t20023/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("t20023", "[test-case][sequence]")
|
||||
{
|
||||
auto [config, db] = load_config("t20023");
|
||||
|
||||
auto diagram = config.diagrams["t20023_sequence"];
|
||||
|
||||
REQUIRE(diagram->name == "t20023_sequence");
|
||||
|
||||
auto model = generate_sequence_diagram(*db, diagram);
|
||||
|
||||
REQUIRE(model->name() == "t20023_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"), "a()"));
|
||||
REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a1()"));
|
||||
REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a2()"));
|
||||
REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a3()"));
|
||||
REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a4()"));
|
||||
|
||||
save_puml(
|
||||
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||
}
|
||||
@@ -269,6 +269,7 @@ using namespace clanguml::test::matchers;
|
||||
#include "t20020/test_case.h"
|
||||
#include "t20021/test_case.h"
|
||||
#include "t20022/test_case.h"
|
||||
#include "t20023/test_case.h"
|
||||
|
||||
///
|
||||
/// Package diagram tests
|
||||
|
||||
@@ -214,6 +214,9 @@ test_cases:
|
||||
- name: t20022
|
||||
title: Forward class declaration sequence diagram test case
|
||||
description:
|
||||
- name: t20023
|
||||
title: Try/catch statement sequence diagram test case
|
||||
description:
|
||||
Package diagrams:
|
||||
- name: t30001
|
||||
title: Basic package diagram test case
|
||||
|
||||
Reference in New Issue
Block a user