0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
sequence_diagram_generator.cc
Go to the documentation of this file.
1/**
2 * @file src/sequence_diagram/generators/json/sequence_diagram_generator.cc
3 *
4 * Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
20
22
23std::string render_name(std::string name)
24{
25 util::replace_all(name, "##", "::");
26
27 return name;
28}
29
30} // namespace clanguml::sequence_diagram::generators::json
31
33
34void to_json(nlohmann::json &j, const participant &c)
35{
36 to_json(j, dynamic_cast<const participant::element &>(c));
37 j["type"] = c.type_name();
38
39 if (c.type_name() == "method") {
40 j["name"] = dynamic_cast<const method &>(c).method_name();
41 }
42
43 j["full_name"] = generators::json::render_name(c.full_name(false));
44
45 if (c.type_name() == "function" || c.type_name() == "function_template") {
46 const auto &f = dynamic_cast<const function &>(c);
47 if (f.is_cuda_kernel())
48 j["is_cuda_kernel"] = true;
49 if (f.is_cuda_device())
50 j["is_cuda_device"] = true;
51 }
52}
53
54void to_json(nlohmann::json &j, const activity &c)
55{
56 j["participant_id"] = std::to_string(c.from().value());
57}
58
59} // namespace clanguml::sequence_diagram::model
60
62
67using namespace clanguml::util;
68
69//
70// generator
71//
72
76{
77}
78
79void generator::generate_call(const message &m, nlohmann::json &parent) const
80{
81 const auto &from = model().get_participant<model::participant>(m.from());
82 const auto &to = model().get_participant<model::participant>(m.to());
83
84 if (!from || !to) {
85 LOG_DBG("Skipping empty call from '{}' to '{}'", m.from(), m.to());
86 return;
87 }
88
91
92 std::string message;
93
96
97 if (to.value().type_name() == "method") {
98 message = dynamic_cast<const model::function &>(to.value())
99 .message_name(render_mode);
100 }
101 else if (config().combine_free_functions_into_file_participants()) {
102 if (to.value().type_name() == "function") {
103 message = dynamic_cast<const model::function &>(to.value())
104 .message_name(render_mode);
105 }
106 else if (to.value().type_name() == "function_template") {
107 message = dynamic_cast<const model::function_template &>(to.value())
108 .message_name(render_mode);
109 }
110 }
111
112 message = config().simplify_template_type(message);
113
114 nlohmann::json msg;
115
116 msg["name"] = message;
117 msg["type"] = "message";
118 msg["from"]["activity_id"] = std::to_string(from.value().id().value());
119 msg["to"]["activity_id"] = std::to_string(to.value().id().value());
120 if (const auto &cmt = m.comment(); cmt.has_value())
121 msg["comment"] = cmt.value().at("comment");
122
123 if (from.value().type_name() == "method") {
124 const auto &class_participant =
125 model().get_participant<model::method>(from.value().id()).value();
126
127 msg["from"]["participant_id"] =
128 std::to_string(class_participant.class_id().value());
129 }
130 else if (from.value().type_name() == "function" ||
131 from.value().type_name() == "function_template") {
132 if (config().combine_free_functions_into_file_participants()) {
133 const auto &file_participant =
134 model()
135 .get_participant<model::function>(from.value().id())
136 .value();
137 msg["from"]["participant_id"] = std::to_string(
138 common::to_id(file_participant.file_relative()).value());
139 }
140 else {
141 msg["from"]["participant_id"] =
142 std::to_string(from.value().id().value());
143 }
144 }
145 else if (from.value().type_name() == "lambda") {
146 msg["from"]["participant_id"] =
147 std::to_string(from.value().id().value());
148 }
149
150 if (to.value().type_name() == "method") {
151 const auto &class_participant =
152 model().get_participant<model::method>(to.value().id()).value();
153
154 msg["to"]["participant_id"] =
155 std::to_string(class_participant.class_id().value());
156 }
157 else if (to.value().type_name() == "function" ||
158 to.value().type_name() == "function_template") {
159 if (config().combine_free_functions_into_file_participants()) {
160 const auto &file_participant =
161 model()
162 .get_participant<model::function>(to.value().id())
163 .value();
164 msg["to"]["participant_id"] = std::to_string(
165 common::to_id(file_participant.file_relative()).value());
166 }
167 else {
168 msg["to"]["participant_id"] =
169 std::to_string(to.value().id().value());
170 }
171 }
172 else if (to.value().type_name() == "lambda") {
173 msg["to"]["participant_id"] = std::to_string(to.value().id().value());
174 }
175
176 msg["source_location"] =
177 dynamic_cast<const clanguml::common::model::source_location &>(m);
178
179 msg["scope"] = to_string(m.message_scope());
180 msg["return_type"] = config().simplify_template_type(m.return_type());
181
182 parent["messages"].push_back(std::move(msg));
183
184 LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message,
185 from.value().full_name(false), m.from(), to.value().full_name(false),
186 m.to());
187}
188
190 const activity &a, std::vector<eid_t> &visited) const
191{
192 // Generate calls from this activity to other activities
193 for (const auto &m : a.messages()) {
194 switch (m.type()) {
195 case message_t::kCall:
196 process_call_message(m, visited);
197 break;
198 case message_t::kIf:
200 break;
201 case message_t::kElseIf:
202 case message_t::kElse:
204 break;
205 case message_t::kIfEnd:
207 break;
208 case message_t::kWhile:
210 break;
211 case message_t::kWhileEnd:
213 break;
214 case message_t::kFor:
216 break;
217 case message_t::kForEnd:
219 break;
220 case message_t::kDo:
222 break;
223 case message_t::kDoEnd:
225 break;
226 case message_t::kTry:
228 break;
229 case message_t::kCatch:
231 break;
232 case message_t::kTryEnd:
234 break;
235 case message_t::kSwitch:
237 break;
238 case message_t::kCase:
240 break;
241 case message_t::kSwitchEnd:
243 break;
244 case message_t::kConditional:
246 break;
247 case message_t::kConditionalElse:
249 break;
250 case message_t::kConditionalEnd:
252 break;
253 case message_t::kNone:
254 case message_t::kReturn:; // noop
255 }
256 }
257}
258
260{
261 assert(!block_statements_stack_.empty());
262
263 return block_statements_stack_.back().get();
264}
265
267 const model::message &m, std::vector<eid_t> &visited) const
268{
269 visited.push_back(m.from());
270
273 visited.pop_back();
274 return;
275 }
276
278 }
279
280 LOG_DBG("Generating message {} --> {}", m.from(), m.to());
281
283
284 if (model().sequences().find(m.to()) != model().sequences().end()) {
285 if (std::find(visited.begin(), visited.end(), m.to()) ==
286 visited.end()) { // break infinite recursion on recursive calls
287
288 LOG_DBG("Creating activity {} --> {} - missing sequence {}",
289 m.from(), m.to(), m.to());
290
291 generate_activity(model().get_activity(m.to()), visited);
292 }
293 }
294 else
295 LOG_DBG("Skipping activity {} --> {} - missing sequence {}", m.from(),
296 m.to(), m.to());
297
298 visited.pop_back();
299}
300
302{
303 nlohmann::json while_block;
304 while_block["type"] = "loop";
305 while_block["name"] = "while";
306 while_block["activity_id"] = std::to_string(m.from().value());
307 if (auto text = m.condition_text(); text.has_value())
308 while_block["condition_text"] = *text;
309
310 current_block_statement()["messages"].push_back(std::move(while_block));
311
312 block_statements_stack_.push_back(
313 std::ref(current_block_statement()["messages"].back()));
314}
315
317{
318 // Remove the while statement block from the stack
319 block_statements_stack_.pop_back();
320}
321
323{
324 nlohmann::json for_block;
325 for_block["type"] = "loop";
326 for_block["name"] = "for";
327 for_block["activity_id"] = std::to_string(m.from().value());
328 if (auto text = m.condition_text(); text.has_value())
329 for_block["condition_text"] = *text;
330
331 current_block_statement()["messages"].push_back(std::move(for_block));
332
333 block_statements_stack_.push_back(
334 std::ref(current_block_statement()["messages"].back()));
335}
336
338{
339 // Remove the while statement block from the stack
340 block_statements_stack_.pop_back();
341}
342
344{
345 nlohmann::json do_block;
346 do_block["type"] = "loop";
347 do_block["name"] = "do";
348 do_block["activity_id"] = std::to_string(m.from().value());
349 if (auto text = m.condition_text(); text.has_value())
350 do_block["condition_text"] = *text;
351
352 current_block_statement()["messages"].push_back(std::move(do_block));
353
354 block_statements_stack_.push_back(
355 std::ref(current_block_statement()["messages"].back()));
356}
357
359{
360 // Remove the do statement block from the stack
361 block_statements_stack_.pop_back();
362}
363
365{
366 nlohmann::json try_block;
367 try_block["type"] = "break";
368 try_block["name"] = "try";
369 try_block["activity_id"] = std::to_string(m.from().value());
370
371 current_block_statement()["messages"].push_back(std::move(try_block));
372
373 block_statements_stack_.push_back(
374 std::ref(current_block_statement()["messages"].back()));
375
376 nlohmann::json branch;
377 branch["type"] = "main";
378 current_block_statement()["branches"].push_back(std::move(branch));
379
380 block_statements_stack_.push_back(
381 std::ref(current_block_statement()["branches"].back()));
382}
383
385{
386 // remove previous block from the stack
387 block_statements_stack_.pop_back();
388
389 nlohmann::json branch;
390 branch["type"] = "catch";
391 current_block_statement()["branches"].push_back(std::move(branch));
392
393 block_statements_stack_.push_back(
394 std::ref(current_block_statement()["branches"].back()));
395}
396
398{
399 // Remove last if block from the stack
400 block_statements_stack_.pop_back();
401
402 // Remove the try statement block from the stack
403 block_statements_stack_.pop_back();
404}
405
407{
408 nlohmann::json if_block;
409 if_block["type"] = "alt";
410 if_block["name"] = "switch";
411 if_block["activity_id"] = std::to_string(m.from().value());
412
413 current_block_statement()["messages"].push_back(std::move(if_block));
414
415 block_statements_stack_.push_back(
416 std::ref(current_block_statement()["messages"].back()));
417}
418
420{
421 if (current_block_statement()["type"] == "case")
422 block_statements_stack_.pop_back();
423
424 nlohmann::json case_block;
425 case_block["type"] = "case";
426 case_block["name"] = m.message_name();
427 current_block_statement()["branches"].push_back(std::move(case_block));
428
429 block_statements_stack_.push_back(
430 std::ref(current_block_statement()["branches"].back()));
431}
432
434{ // Remove last case block from the stack
435 block_statements_stack_.pop_back();
436
437 // Remove the switch statement block from the stack
438 block_statements_stack_.pop_back();
439}
440
442{
443 nlohmann::json if_block;
444 if_block["type"] = "alt";
445 if_block["name"] = "conditional";
446 if_block["activity_id"] = std::to_string(m.from().value());
447 if (auto text = m.condition_text(); text.has_value())
448 if_block["condition_text"] = *text;
449
450 current_block_statement()["messages"].push_back(std::move(if_block));
451
452 block_statements_stack_.push_back(
453 std::ref(current_block_statement()["messages"].back()));
454
455 nlohmann::json branch;
456 branch["type"] = "consequent";
457 current_block_statement()["branches"].push_back(std::move(branch));
458
459 block_statements_stack_.push_back(
460 std::ref(current_block_statement()["branches"].back()));
461}
462
464{
465 // remove previous branch from the stack
466 block_statements_stack_.pop_back();
467
468 nlohmann::json branch;
469 branch["type"] = "alternative";
470 if (auto text = m.condition_text(); text.has_value())
471 branch["condition_text"] = *text;
472 current_block_statement()["branches"].push_back(std::move(branch));
473
474 block_statements_stack_.push_back(
475 std::ref(current_block_statement()["branches"].back()));
476}
477
479{
480 // Remove last if branch from the stack
481 block_statements_stack_.pop_back();
482
483 // Remove the if statement block from the stack
484 block_statements_stack_.pop_back();
485}
486
488{
489 // Remove last if branch from the stack
490 block_statements_stack_.pop_back();
491
492 // Remove the if statement block from the stack
493 block_statements_stack_.pop_back();
494}
495
497{
498 // remove previous branch from the stack
499 block_statements_stack_.pop_back();
500
501 nlohmann::json branch;
502 branch["type"] = "alternative";
503 current_block_statement()["branches"].push_back(std::move(branch));
504
505 block_statements_stack_.push_back(
506 std::ref(current_block_statement()["branches"].back()));
507}
508
510{
511 nlohmann::json if_block;
512 if_block["type"] = "alt";
513 if_block["name"] = "if";
514 if_block["activity_id"] = std::to_string(m.from().value());
515 if (auto text = m.condition_text(); text.has_value())
516 if_block["condition_text"] = *text;
517
518 current_block_statement()["messages"].push_back(std::move(if_block));
519
520 block_statements_stack_.push_back(
521 std::ref(current_block_statement()["messages"].back()));
522
523 nlohmann::json branch;
524 branch["type"] = "consequent";
525 current_block_statement()["branches"].push_back(std::move(branch));
526
527 block_statements_stack_.push_back(
528 std::ref(current_block_statement()["branches"].back()));
529}
530
532 nlohmann::json &parent, const std::string &name) const
533{
534 auto p = model().get(name);
535
536 if (!p.has_value()) {
537 LOG_WARN("Cannot find participant {} from `participants_order` option",
538 name);
539 return;
540 }
541
542 generate_participant(parent, p.value().id(), true);
543}
544
546 nlohmann::json & /*parent*/, eid_t id, bool force) const
547{
548 std::optional<eid_t> participant_id{};
549
550 if (!force) {
551 for (const auto pid : model().active_participants()) {
552 if (pid == id) {
553 participant_id = pid;
554 break;
555 }
556 }
557 }
558 else
559 participant_id = id;
560
561 if (!participant_id.has_value())
562 return participant_id;
563
564 if (is_participant_generated(*participant_id))
565 return participant_id;
566
567 const auto &participant =
568 model().get_participant<model::participant>(*participant_id).value();
569
570 const auto participant_type = participant.type_name();
571
572 if (participant_type == "method") {
573 auto class_participant_id =
574 model()
575 .get_participant<model::method>(*participant_id)
576 .value()
577 .class_id();
578
579 LOG_DBG("Generating JSON method participant: {}",
580 model()
581 .get_participant<model::method>(*participant_id)
582 .value()
583 .full_name(false));
584
585 if (!is_participant_generated(class_participant_id)) {
586 const auto &class_participant =
587 model()
588 .get_participant<model::participant>(class_participant_id)
589 .value();
590
591 generated_participants_.emplace(*participant_id);
592 generated_participants_.emplace(class_participant_id);
593
594 json_["participants"].push_back(class_participant);
595 json_["participants"].back()["activities"].push_back(participant);
596
597 // Perform config dependent postprocessing on generated class
598 const auto class_participant_full_name =
599 class_participant.full_name(false);
600
601 json_["participants"].back().at("display_name") =
602 make_display_name(class_participant_full_name);
603
604 return class_participant_id;
605 }
606
607 if (!is_participant_generated(*participant_id)) {
608 for (auto &p : json_["participants"]) {
609 if (p.at("id") ==
610 std::to_string(class_participant_id.value())) {
611 generated_participants_.emplace(*participant_id);
612 p["activities"].push_back(participant);
613 return class_participant_id;
614 }
615 }
616 }
617 }
618 else if ((participant_type == "function" ||
619 participant_type == "function_template") &&
620 config().combine_free_functions_into_file_participants()) {
621 // Create a single participant for all functions declared in a
622 // single file
623 // participant_id will become activity_id within a file participant
624 const auto &function_participant =
625 model().get_participant<model::function>(*participant_id).value();
626
627 const auto file_participant_id =
628 common::to_id(function_participant.file_relative());
629
630 if (!is_participant_generated(file_participant_id)) {
631 nlohmann::json p = function_participant;
632
633 const auto file_path =
634 config().make_path_relative(function_participant.file());
635
636 p["display_name"] = util::path_to_url(file_path.string());
637 p["name"] = file_path.filename();
638
639 if (is_participant_generated(file_participant_id))
640 return participant_id;
641
642 p["id"] = std::to_string(file_participant_id.value());
643 p["type"] = "file";
644 p.erase("source_location");
645
646 generated_participants_.emplace(participant_id.value());
647
648 p["activities"].push_back(participant);
649 json_["participants"].push_back(p);
650
651 generated_participants_.emplace(file_participant_id);
652
653 return file_participant_id;
654 }
655
656 if (!is_participant_generated(*participant_id)) {
657 for (auto &p : json_["participants"]) {
658 if (p.at("id") == std::to_string(file_participant_id.value())) {
659 generated_participants_.emplace(*participant_id);
660 p["activities"].push_back(participant);
661 }
662 }
663 }
664
665 return file_participant_id;
666 }
667 else {
668 json_["participants"].push_back(participant);
669 }
670
671 generated_participants_.emplace(*participant_id);
672
673 return participant_id;
674}
675
677{
678 return std::find(generated_participants_.begin(),
680 id) != generated_participants_.end();
681}
682
683void generator::generate_diagram(nlohmann::json &parent) const
684{
685 model().print();
686
687 if (config().using_namespace)
688 parent["using_namespace"] = config().using_namespace().to_string();
689
690 if (config().participants_order.has_value) {
691 for (const auto &p : config().participants_order()) {
692 LOG_DBG("Pregenerating participant {}", p);
694 }
695 }
696
697 for (const auto &ft : config().from_to()) {
698 // First, find the sequence of activities from 'from' location
699 // to 'to' location
700 assert(ft.size() == 2);
701
702 const auto &from_location = ft.front();
703 const auto &to_location = ft.back();
704
705 auto from_activity_id = model().get_from_activity_id(from_location);
706 auto to_activity_id = model().get_to_activity_id(to_location);
707
708 if (!from_activity_id || !to_activity_id)
709 continue;
710
711 auto message_chains_unique = model().get_all_from_to_message_chains(
712 *from_activity_id, *to_activity_id);
713
714 nlohmann::json sequence;
715 sequence["from_to"]["from"]["location"] = from_location.location;
716 sequence["from_to"]["from"]["id"] =
717 std::to_string(from_activity_id.value().value());
718 sequence["from_to"]["to"]["location"] = to_location.location;
719 sequence["from_to"]["to"]["id"] =
720 std::to_string(to_activity_id.value().value());
721
722 block_statements_stack_.push_back(std::ref(sequence));
723
724 sequence["message_chains"] = nlohmann::json::array();
725
726 for (const auto &mc : message_chains_unique) {
727 nlohmann::json message_chain;
728
729 block_statements_stack_.push_back(std::ref(message_chain));
730
731 for (const auto &m : mc) {
733 }
734
735 block_statements_stack_.pop_back();
736
737 sequence["message_chains"].push_back(std::move(message_chain));
738 }
739
740 block_statements_stack_.pop_back();
741
742 parent["sequences"].push_back(std::move(sequence));
743 }
744
745 for (const auto &to_location : config().to()) {
746 auto to_activity_id = model().get_to_activity_id(to_location);
747
748 if (!to_activity_id.has_value())
749 continue;
750
751 auto message_chains_unique = model().get_all_from_to_message_chains(
752 eid_t{}, to_activity_id.value());
753
754 nlohmann::json sequence;
755 sequence["to"]["location"] = to_location.location;
756 sequence["to"]["id"] = std::to_string(to_activity_id.value().value());
757
758 block_statements_stack_.push_back(std::ref(sequence));
759
760 sequence["message_chains"] = nlohmann::json::array();
761
762 for (const auto &mc : message_chains_unique) {
763 nlohmann::json message_chain;
764
765 block_statements_stack_.push_back(std::ref(message_chain));
766
767 for (const auto &m : mc) {
769 }
770
771 block_statements_stack_.pop_back();
772
773 sequence["message_chains"].push_back(std::move(message_chain));
774 }
775
776 block_statements_stack_.pop_back();
777
778 parent["sequences"].push_back(std::move(sequence));
779 }
780
781 for (const auto &sf : config().from()) {
782 if (sf.location_type == location_t::function) {
783 eid_t start_from{};
784 std::string start_from_str;
785 for (const auto &[k, v] : model().sequences()) {
786 const auto &caller = *model().participants().at(v.from());
787 std::string vfrom = caller.full_name(false);
788 if (vfrom == sf.location) {
789 LOG_DBG("Found sequence diagram start point: {}", k);
790 start_from = k;
791 break;
792 }
793 }
794
795 if (start_from == 0) {
796 LOG_WARN("Failed to find participant with {} for start_from "
797 "condition",
798 sf.location);
799 continue;
800 }
801
802 // Use this to break out of recurrent loops
803 std::vector<eid_t> visited_participants;
804
805 const auto &from =
806 model().get_participant<model::function>(start_from);
807
808 if (!from.has_value()) {
809 LOG_WARN("Failed to find participant {} for start_from "
810 "condition",
811 sf.location);
812 continue;
813 }
814
815 generate_participant(json_, start_from);
816
817 [[maybe_unused]] model::function::message_render_mode render_mode =
819
820 nlohmann::json sequence;
821 sequence["start_from"]["location"] = sf.location;
822 sequence["start_from"]["id"] = std::to_string(start_from.value());
823
824 block_statements_stack_.push_back(std::ref(sequence));
825
827 model().get_activity(start_from), visited_participants);
828
829 block_statements_stack_.pop_back();
830
831 if (from.value().type_name() == "method" ||
832 config().combine_free_functions_into_file_participants()) {
833
834 sequence["return_type"] =
835 make_display_name(from.value().return_type());
836 }
837
838 parent["sequences"].push_back(std::move(sequence));
839 }
840 else {
841 // TODO: Add support for other sequence start location types
842 continue;
843 }
844 }
845
846 // Perform config dependent postprocessing on generated participants
847 for (auto &p : json_["participants"]) {
848 if (p.contains("display_name")) {
849 p["display_name"] = make_display_name(p["display_name"]);
850 }
851 }
852
853 parent["participants"] = json_["participants"];
854}
855
856std::string generator::make_display_name(const std::string &full_name) const
857{
858 auto result = config().simplify_template_type(full_name);
859 result = config().using_namespace().relative(result);
861 result = render_name(result);
862
863 return result;
864}
865
866} // namespace clanguml::sequence_diagram::generators::json