diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 34c511f7..45c95493 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -144,6 +144,51 @@ std::string to_string(const clang::RecordType &type, return to_string(type.desugar(), ctx, try_canonical); } +std::string to_string(const clang::Expr *expr) +{ + clang::LangOptions lang_options; + std::string result; + llvm::raw_string_ostream ostream(result); + expr->printPretty(ostream, NULL, clang::PrintingPolicy(lang_options)); + + return result; +} + +std::string to_string(const clang::Stmt *stmt) +{ + clang::LangOptions lang_options; + std::string result; + llvm::raw_string_ostream ostream(result); + stmt->printPretty(ostream, NULL, clang::PrintingPolicy(lang_options)); + + return result; +} + +std::string to_string(const clang::FunctionTemplateDecl *decl) +{ + std::vector template_parameters; + // Handle template function + for (const auto *parameter : *decl->getTemplateParameters()) { + if (clang::dyn_cast_or_null(parameter)) { + const auto *template_type_parameter = + clang::dyn_cast_or_null(parameter); + + std::string template_parameter{ + template_type_parameter->getNameAsString()}; + + if (template_type_parameter->isParameterPack()) + template_parameter += "..."; + + template_parameters.emplace_back(std::move(template_parameter)); + } + else { + // TODO + } + } + return fmt::format("{}<{}>({})", decl->getQualifiedNameAsString(), + fmt::join(template_parameters, ","), ""); +} + std::string get_source_text_raw( clang::SourceRange range, const clang::SourceManager &sm) { diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index c332dd10..09441e3c 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -62,6 +62,12 @@ std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx, std::string to_string(const clang::RecordType &type, const clang::ASTContext &ctx, bool try_canonical = true); +std::string to_string(const clang::Expr *expr); + +std::string to_string(const clang::Stmt *stmt); + +std::string to_string(const clang::FunctionTemplateDecl *decl); + std::string get_source_text_raw( clang::SourceRange range, const clang::SourceManager &sm); diff --git a/src/common/model/enums.cc b/src/common/model/enums.cc index 9aaf3b33..cb1685a2 100644 --- a/src/common/model/enums.cc +++ b/src/common/model/enums.cc @@ -100,6 +100,12 @@ std::string to_string(message_t r) return "catch"; case message_t::kTryEnd: return "end try"; + case message_t::kSwitch: + return "switch"; + case message_t::kCase: + return "case"; + case message_t::kSwitchEnd: + return "end switch"; default: assert(false); return ""; diff --git a/src/common/model/enums.h b/src/common/model/enums.h index 66c93249..463ec56d 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -56,6 +56,9 @@ enum class message_t { kTry, kCatch, kTryEnd, + kSwitch, + kCase, + kSwitchEnd, kNone }; diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index c72d99ae..15dc5e3e 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -199,6 +199,15 @@ void generator::generate_activity(const activity &a, std::ostream &ostr, else if (m.type() == message_t::kTryEnd) { ostr << "end\n"; } + else if (m.type() == message_t::kSwitch) { + ostr << "group switch\n"; + } + else if (m.type() == message_t::kCase) { + ostr << "else " << m.message_name() << '\n'; + } + else if (m.type() == message_t::kSwitchEnd) { + ostr << "end\n"; + } } } diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index e9a4bdf1..28067a7b 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -273,6 +273,81 @@ void diagram::add_catch_stmt( get_activity(current_caller_id).add_message(std::move(m)); } +void diagram::add_switch_stmt( + 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)}); + } + + message m{message_t::kSwitch, current_caller_id}; + + get_activity(current_caller_id).add_message(std::move(m)); +} + +void diagram::end_switch_stmt( + 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::kSwitch) { + current_messages.pop_back(); + } + else { + current_messages.emplace_back(std::move(m)); + } + } +} + +void diagram::add_case_stmt( + common::model::diagram_element::id_t current_caller_id, + const std::string &case_label) +{ + using clanguml::common::model::message_t; + + message m{message_t::kCase, current_caller_id}; + m.set_message_name(case_label); + + if (sequences_.find(current_caller_id) != sequences_.end()) { + auto ¤t_messages = get_activity(current_caller_id).messages(); + + if (current_messages.back().type() == message_t::kCase) { + // Do nothing - fallthroughs not supported yet... + } + else { + current_messages.emplace_back(std::move(m)); + } + } +} + +void diagram::add_default_stmt( + common::model::diagram_element::id_t current_caller_id) +{ + using clanguml::common::model::message_t; + + message m{message_t::kCase, current_caller_id}; + m.set_message_name("default"); + + if (sequences_.find(current_caller_id) != sequences_.end()) { + auto ¤t_messages = get_activity(current_caller_id).messages(); + + if (current_messages.back().type() == message_t::kCase) { + current_messages.pop_back(); + } + else { + current_messages.emplace_back(std::move(m)); + } + } +} + bool diagram::started() const { return started_; } void diagram::started(bool s) { started_ = s; } diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index b51b1128..70c065f4 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -93,14 +93,24 @@ public: void end_loop_stmt(common::model::diagram_element::id_t current_caller_id, common::model::message_t type); - void add_while_stmt(common::model::diagram_element::id_t i); - void end_while_stmt(common::model::diagram_element::id_t i); + void add_while_stmt(common::model::diagram_element::id_t current_caller_id); + void end_while_stmt(common::model::diagram_element::id_t current_caller_id); - void add_do_stmt(common::model::diagram_element::id_t i); - void end_do_stmt(common::model::diagram_element::id_t i); + void add_do_stmt(common::model::diagram_element::id_t current_caller_id); + void end_do_stmt(common::model::diagram_element::id_t current_caller_id); - void add_for_stmt(common::model::diagram_element::id_t i); - void end_for_stmt(common::model::diagram_element::id_t i); + void add_for_stmt(common::model::diagram_element::id_t current_caller_id); + void end_for_stmt(common::model::diagram_element::id_t current_caller_id); + + void add_switch_stmt( + common::model::diagram_element::id_t current_caller_id); + void end_switch_stmt( + common::model::diagram_element::id_t current_caller_id); + void add_case_stmt(common::model::diagram_element::id_t current_caller_id); + void add_case_stmt(common::model::diagram_element::id_t current_caller_id, + const std::string &case_label); + void add_default_stmt( + common::model::diagram_element::id_t current_caller_id); bool started() const; void started(bool s); diff --git a/src/sequence_diagram/visitor/call_expression_context.cc b/src/sequence_diagram/visitor/call_expression_context.cc index 97611c38..67f73a65 100644 --- a/src/sequence_diagram/visitor/call_expression_context.cc +++ b/src/sequence_diagram/visitor/call_expression_context.cc @@ -264,6 +264,25 @@ void call_expression_context::leave_trystmt() try_stmt_stack_.pop(); } +clang::SwitchStmt *call_expression_context::current_switchstmt() const +{ + if (switch_stmt_stack_.empty()) + return nullptr; + + return switch_stmt_stack_.top(); +} + +void call_expression_context::enter_switchstmt(clang::SwitchStmt *stmt) +{ + switch_stmt_stack_.push(stmt); +} + +void call_expression_context::leave_switchstmt() +{ + if (switch_stmt_stack_.empty()) + switch_stmt_stack_.pop(); +} + bool call_expression_context::is_expr_in_current_control_statement_condition( const clang::Stmt *stmt) const { diff --git a/src/sequence_diagram/visitor/call_expression_context.h b/src/sequence_diagram/visitor/call_expression_context.h index 891bcc7d..913b0e7a 100644 --- a/src/sequence_diagram/visitor/call_expression_context.h +++ b/src/sequence_diagram/visitor/call_expression_context.h @@ -84,6 +84,10 @@ struct call_expression_context { void enter_trystmt(clang::Stmt *stmt); void leave_trystmt(); + clang::SwitchStmt *current_switchstmt() const; + void enter_switchstmt(clang::SwitchStmt *stmt); + void leave_switchstmt(); + bool is_expr_in_current_control_statement_condition( const clang::Stmt *stmt) const; @@ -105,6 +109,7 @@ private: std::stack loop_stmt_stack_; std::stack try_stmt_stack_; + std::stack switch_stmt_stack_; }; } \ No newline at end of file diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 31fab119..7dc5b9e4 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -23,31 +23,6 @@ namespace clanguml::sequence_diagram::visitor { -std::string to_string(const clang::FunctionTemplateDecl *decl) -{ - std::vector template_parameters; - // Handle template function - for (const auto *parameter : *decl->getTemplateParameters()) { - if (clang::dyn_cast_or_null(parameter)) { - const auto *template_type_parameter = - clang::dyn_cast_or_null(parameter); - - std::string template_parameter{ - template_type_parameter->getNameAsString()}; - - if (template_type_parameter->isParameterPack()) - template_parameter += "..."; - - template_parameters.emplace_back(std::move(template_parameter)); - } - else { - // TODO - } - } - return fmt::format("{}<{}>({})", decl->getQualifiedNameAsString(), - fmt::join(template_parameters, ","), ""); -} - translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm, clanguml::sequence_diagram::model::diagram &diagram, const clanguml::config::sequence_diagram &config) @@ -318,8 +293,9 @@ bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m) m_ptr->is_static(m->isStatic()); for (const auto *param : m->parameters()) { - m_ptr->add_parameter(simplify_system_template( - common::to_string(param->getType(), m->getASTContext(), false))); + m_ptr->add_parameter(config().using_namespace().relative( + simplify_system_template(common::to_string( + param->getType(), m->getASTContext(), false)))); } set_source_location(*m, *m_ptr); @@ -777,6 +753,52 @@ bool translation_unit_visitor::TraverseCXXForRangeStmt( return true; } +bool translation_unit_visitor::TraverseSwitchStmt(clang::SwitchStmt *stmt) +{ + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + context().enter_switchstmt(stmt); + diagram().add_switch_stmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseSwitchStmt(stmt); + + if (current_caller_id) { + context().leave_switchstmt(); + diagram().end_switch_stmt(current_caller_id); + } + + return true; +} + +bool translation_unit_visitor::TraverseCaseStmt(clang::CaseStmt *stmt) +{ + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + diagram().add_case_stmt( + current_caller_id, common::to_string(stmt->getLHS())); + } + + RecursiveASTVisitor::TraverseCaseStmt(stmt); + + return true; +} + +bool translation_unit_visitor::TraverseDefaultStmt(clang::DefaultStmt *stmt) +{ + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + diagram().add_default_stmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseDefaultStmt(stmt); + + return true; +} + bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) { using clanguml::common::model::message_scope_t; diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 8dcbbfa2..f068cd76 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -80,6 +80,12 @@ public: bool TraverseCXXCatchStmt(clang::CXXCatchStmt *stmt); + bool TraverseSwitchStmt(clang::SwitchStmt *stmt); + + bool TraverseCaseStmt(clang::CaseStmt *stmt); + + bool TraverseDefaultStmt(clang::DefaultStmt *stmt); + clanguml::sequence_diagram::model::diagram &diagram(); const clanguml::sequence_diagram::model::diagram &diagram() const; diff --git a/tests/t20024/.clang-uml b/tests/t20024/.clang-uml new file mode 100644 index 00000000..fde4258a --- /dev/null +++ b/tests/t20024/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20024_sequence: + type: sequence + glob: + - ../../tests/t20024/t20024.cc + include: + namespaces: + - clanguml::t20024 + using_namespace: + - clanguml::t20024 + start_from: + - function: "clanguml::t20024::tmain()" \ No newline at end of file diff --git a/tests/t20024/t20024.cc b/tests/t20024/t20024.cc new file mode 100644 index 00000000..f253f225 --- /dev/null +++ b/tests/t20024/t20024.cc @@ -0,0 +1,65 @@ +namespace clanguml { +namespace t20024 { + +enum enum_a { zero = 0, one = 1, two = 2, three = 3 }; + +enum class colors { red, orange, green }; + +struct A { + int select(enum_a v) + { + switch (v) { + case zero: + return a0(); + case one: + return a1(); + case two: + return a2(); + default: + return a3(); + } + } + + int a0() { return 0; } + int a1() { return 1; } + int a2() { return 2; } + int a3() { return 3; } +}; + +struct B { + void select(colors c) + { + switch (c) { + case colors::red: + red(); + break; + case colors::orange: + orange(); + break; + case colors::green: + green(); + break; + default: + grey(); + } + } + + void red() { } + void orange() { } + void green() { } + void grey() { } +}; + +int tmain() +{ + A a; + B b; + + a.select(enum_a::two); + + b.select(colors::green); + + return 0; +} +} +} \ No newline at end of file diff --git a/tests/t20024/test_case.h b/tests/t20024/test_case.h new file mode 100644 index 00000000..b91bbf48 --- /dev/null +++ b/tests/t20024/test_case.h @@ -0,0 +1,51 @@ +/** + * tests/t20024/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * 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("t20024", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20024"); + + auto diagram = config.diagrams["t20024_sequence"]; + + REQUIRE(diagram->name == "t20024_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20024_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"), "select(enum_a)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a0()")); + 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("tmain()"), _A("B"), "select(colors)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("B"), "red()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("B"), "orange()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("B"), "green()")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 20a8fa44..ce6cf013 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -270,6 +270,7 @@ using namespace clanguml::test::matchers; #include "t20021/test_case.h" #include "t20022/test_case.h" #include "t20023/test_case.h" +#include "t20024/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 1c670f81..8b328373 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -217,6 +217,9 @@ test_cases: - name: t20023 title: Try/catch statement sequence diagram test case description: + - name: t20024 + title: Switch statement sequence diagram test case + description: Package diagrams: - name: t30001 title: Basic package diagram test case