diff --git a/src/common/model/enums.cc b/src/common/model/enums.cc index cb1685a2..b984f26a 100644 --- a/src/common/model/enums.cc +++ b/src/common/model/enums.cc @@ -106,6 +106,10 @@ std::string to_string(message_t r) return "case"; case message_t::kSwitchEnd: return "end switch"; + case message_t::kConditional: + return "conditional"; + case message_t::kConditionalEnd: + return "end conditional"; default: assert(false); return ""; diff --git a/src/common/model/enums.h b/src/common/model/enums.h index 463ec56d..e73d92e6 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -59,6 +59,8 @@ enum class message_t { kSwitch, kCase, kSwitchEnd, + kConditional, + kConditionalEnd, kNone }; diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index ba6938f2..a88ee3b1 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -208,6 +208,15 @@ void generator::generate_activity(const activity &a, std::ostream &ostr, else if (m.type() == message_t::kSwitchEnd) { ostr << "end\n"; } + else if (m.type() == message_t::kConditional) { + ostr << "alt\n"; + } + else if (m.type() == message_t::kElse) { + ostr << "else\n"; + } + else if (m.type() == message_t::kConditionalEnd) { + ostr << "end\n"; + } } } diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 28067a7b..de7b7980 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -348,6 +348,52 @@ void diagram::add_default_stmt( } } +void diagram::add_conditional_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::kConditional, current_caller_id}); +} + +void diagram::add_conditional_elsestmt( + const common::model::diagram_element::id_t current_caller_id) +{ + using clanguml::common::model::message_t; + + get_activity(current_caller_id) + .add_message({message_t::kElse, current_caller_id}); +} + +void diagram::end_conditional_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + using clanguml::common::model::message_t; + + message m{message_t::kConditionalEnd, current_caller_id}; + + if (sequences_.find(current_caller_id) != sequences_.end()) { + auto ¤t_messages = get_activity(current_caller_id).messages(); + + if (current_messages.at(current_messages.size() - 1).type() == + message_t::kElse && + current_messages.at(current_messages.size() - 2).type() == + message_t::kConditional) { + current_messages.pop_back(); + 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 70c065f4..439bea41 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -112,6 +112,13 @@ public: void add_default_stmt( common::model::diagram_element::id_t current_caller_id); + void add_conditional_stmt( + common::model::diagram_element::id_t current_caller_id); + void add_conditional_elsestmt( + common::model::diagram_element::id_t current_caller_id); + void end_conditional_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 67f73a65..10990308 100644 --- a/src/sequence_diagram/visitor/call_expression_context.cc +++ b/src/sequence_diagram/visitor/call_expression_context.cc @@ -283,6 +283,27 @@ void call_expression_context::leave_switchstmt() switch_stmt_stack_.pop(); } +clang::ConditionalOperator * +call_expression_context::current_conditionaloperator() const +{ + if (conditional_operator_stack_.empty()) + return nullptr; + + return conditional_operator_stack_.top(); +} + +void call_expression_context::enter_conditionaloperator( + clang::ConditionalOperator *stmt) +{ + conditional_operator_stack_.push(stmt); +} + +void call_expression_context::leave_conditionaloperator() +{ + if (conditional_operator_stack_.empty()) + conditional_operator_stack_.pop(); +} + bool call_expression_context::is_expr_in_current_control_statement_condition( const clang::Stmt *stmt) const { @@ -334,6 +355,13 @@ bool call_expression_context::is_expr_in_current_control_statement_condition( return true; } } + + if (current_conditionaloperator()) { + if (common::is_subexpr_of( + current_conditionaloperator()->getCond(), stmt)) { + return true; + } + } } return false; diff --git a/src/sequence_diagram/visitor/call_expression_context.h b/src/sequence_diagram/visitor/call_expression_context.h index 913b0e7a..a1c247a2 100644 --- a/src/sequence_diagram/visitor/call_expression_context.h +++ b/src/sequence_diagram/visitor/call_expression_context.h @@ -88,6 +88,10 @@ struct call_expression_context { void enter_switchstmt(clang::SwitchStmt *stmt); void leave_switchstmt(); + clang::ConditionalOperator *current_conditionaloperator() const; + void enter_conditionaloperator(clang::ConditionalOperator *stmt); + void leave_conditionaloperator(); + bool is_expr_in_current_control_statement_condition( const clang::Stmt *stmt) const; @@ -110,6 +114,7 @@ private: std::stack loop_stmt_stack_; std::stack try_stmt_stack_; std::stack switch_stmt_stack_; + std::stack conditional_operator_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 3081677a..af73c10e 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -807,6 +807,37 @@ bool translation_unit_visitor::TraverseDefaultStmt(clang::DefaultStmt *stmt) return true; } +bool translation_unit_visitor::TraverseConditionalOperator( + clang::ConditionalOperator *stmt) +{ + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + context().enter_conditionaloperator(stmt); + diagram().add_conditional_stmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseStmt( + stmt->getCond()); + + RecursiveASTVisitor::TraverseStmt( + stmt->getTrueExpr()); + + if (current_caller_id) { + diagram().add_conditional_elsestmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseStmt( + stmt->getFalseExpr()); + + if (current_caller_id) { + context().leave_conditionaloperator(); + diagram().end_conditional_stmt(current_caller_id); + } + + 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 f068cd76..8748a670 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -86,6 +86,8 @@ public: bool TraverseDefaultStmt(clang::DefaultStmt *stmt); + bool TraverseConditionalOperator(clang::ConditionalOperator *stmt); + clanguml::sequence_diagram::model::diagram &diagram(); const clanguml::sequence_diagram::model::diagram &diagram() const; diff --git a/tests/t20028/.clang-uml b/tests/t20028/.clang-uml new file mode 100644 index 00000000..c20559fc --- /dev/null +++ b/tests/t20028/.clang-uml @@ -0,0 +1,17 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20028_sequence: + type: sequence + glob: + - ../../tests/t20028/t20028.cc + include: + namespaces: + - clanguml::t20028 + exclude: + namespaces: + - clanguml::t20028::detail + using_namespace: + - clanguml::t20028 + start_from: + - function: "clanguml::t20028::tmain()" \ No newline at end of file diff --git a/tests/t20028/t20028.cc b/tests/t20028/t20028.cc new file mode 100644 index 00000000..a6f9ded4 --- /dev/null +++ b/tests/t20028/t20028.cc @@ -0,0 +1,31 @@ +namespace clanguml { +namespace t20028 { + +struct A { + int a() { return 0; } + int b() { return 1; } + int c() { return 2; } + int d() { return 3; } +}; + +namespace detail { +struct B { + int e() { return 4; } +}; +} // namespace detail + +int tmain() +{ + A a; + detail::B b; + + int result{}; + + result = b.e() ? b.e() : 12; + + result += a.a() ? a.b() + a.c() : a.d(); + + return result; +} +} +} \ No newline at end of file diff --git a/tests/t20028/test_case.h b/tests/t20028/test_case.h new file mode 100644 index 00000000..ecee16ef --- /dev/null +++ b/tests/t20028/test_case.h @@ -0,0 +1,46 @@ +/** + * tests/t20028/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("t20028", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20028"); + + auto diagram = config.diagrams["t20028_sequence"]; + + REQUIRE(diagram->name == "t20028_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20028_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("tmain()"), _A("A"), "b()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "c()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "d()")); + REQUIRE_THAT(puml, !HasCall(_A("tmain()"), _A("B"), "e()")); + + 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 b4fca775..1b15e9b7 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -274,6 +274,7 @@ using namespace clanguml::test::matchers; #include "t20025/test_case.h" #include "t20026/test_case.h" #include "t20027/test_case.h" +#include "t20028/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 0c59ebca..09d41fea 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -229,6 +229,9 @@ test_cases: - name: t20027 title: Filter call expressions based on access test case description: + - name: t20028 + title: Conditional (ternary) '?:' operator test case + description: Package diagrams: - name: t30001 title: Basic package diagram test case