Skip to content

Commit

Permalink
Added support for regular expressions in sequence diagram boundary co…
Browse files Browse the repository at this point in the history
…nditions (#366)
  • Loading branch information
bkryza committed Jan 12, 2025
1 parent 07da43f commit 4769b84
Show file tree
Hide file tree
Showing 24 changed files with 831 additions and 391 deletions.
2 changes: 1 addition & 1 deletion src/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ std::string to_string(location_t cp);

struct source_location {
location_t location_type{location_t::function};
std::string location;
common::string_or_regex location;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/config/schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ const std::string schema_str = R"(
anyof: !optional filter_t
allof: !optional filter_t
function_location_t:
function: string
function: regex_or_string_t
marker_location_t:
marker: string
source_location_t:
Expand Down
2 changes: 1 addition & 1 deletion src/config/yaml_decoders.cc
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ template <> struct convert<std::vector<source_location>> {
else if (n["function"]) {
source_location loc;
loc.location_type = location_t::function;
loc.location = n["function"].as<std::string>();
loc.location = n["function"].as<decltype(loc.location)>();
rhs.emplace_back(std::move(loc));
}
else {
Expand Down
278 changes: 177 additions & 101 deletions src/sequence_diagram/generators/json/sequence_diagram_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -763,166 +763,242 @@ void generator::generate_diagram(nlohmann::json &parent) const
}
}

for (const auto &ft : config().from_to()) {
// First, find the sequence of activities from 'from' location
// to 'to' location
assert(ft.size() == 2);
generate_from_to_sequences(parent);

const auto &from_location = ft.front();
const auto &to_location = ft.back();
generate_to_sequences(parent);

generate_from_sequences(parent);

auto from_activity_id = model().get_from_activity_id(from_location);
auto to_activity_id = model().get_to_activity_id(to_location);
// Perform config dependent postprocessing on generated participants
for (auto &p : json_["participants"]) {
if (p.contains("display_name")) {
p["display_name"] = make_display_name(p["display_name"]);
}
}

if (!from_activity_id || !to_activity_id)
parent["participants"] = json_["participants"];
}

void generator::generate_from_sequences(nlohmann::json &parent) const
{
std::vector<eid_t> start_from = find_from_activities();

// Use this to break out of recurrent loops
std::vector<eid_t> visited_participants;
for (const auto from_id : start_from) {

const auto &from = model().get_participant<model::function>(from_id);

if (!from.has_value()) {
LOG_WARN("Failed to find participant {} for 'from' "
"condition");
continue;
}

auto message_chains_unique = model().get_all_from_to_message_chains(
*from_activity_id, *to_activity_id);
generate_participant(json_, from_id);

[[maybe_unused]] model::function::message_render_mode render_mode =
model::function::message_render_mode::full;

nlohmann::json sequence;
sequence["from_to"]["from"]["location"] = from_location.location;
sequence["from_to"]["from"]["id"] =
std::to_string(from_activity_id.value().value());
sequence["from_to"]["to"]["location"] = to_location.location;
sequence["from_to"]["to"]["id"] =
std::to_string(to_activity_id.value().value());
sequence["from"]["location"] = from.value().full_name(false);
sequence["from"]["id"] = std::to_string(from_id.value());

block_statements_stack_.push_back(std::ref(sequence));

sequence["message_chains"] = nlohmann::json::array();
generate_activity(model().get_activity(from_id), visited_participants);

for (const auto &mc : message_chains_unique) {
nlohmann::json message_chain;
block_statements_stack_.pop_back();

block_statements_stack_.push_back(std::ref(message_chain));
if (from.value().type_name() == "method" ||
config().combine_free_functions_into_file_participants()) {

for (const auto &m : mc) {
generate_call(m, current_block_statement());
}
sequence["return_type"] =
make_display_name(from.value().return_type());
}

block_statements_stack_.pop_back();
parent["sequences"].push_back(std::move(sequence));
}
}

std::vector<model::message_chain_t> generator::find_to_message_chains() const
{
std::vector<model::message_chain_t> result;

sequence["message_chains"].push_back(std::move(message_chain));
for (const auto &to_location : config().to()) {
auto to_activity_ids = model().get_to_activity_ids(to_location);

if (to_activity_ids.empty()) {
LOG_WARN("Failed to find participant matching '{}' for "
"'to' condition: ",
to_location.location.to_string());
}

block_statements_stack_.pop_back();
for (const auto &to_activity_id : to_activity_ids) {
std::vector<model::message_chain_t> message_chains_unique =
model().get_all_from_to_message_chains(eid_t{}, to_activity_id);

parent["sequences"].push_back(std::move(sequence));
result.insert(result.end(), message_chains_unique.begin(),
message_chains_unique.end());
}
}

for (const auto &to_location : config().to()) {
auto to_activity_id = model().get_to_activity_id(to_location);
return result;
}

void generator::generate_to_sequences(nlohmann::json &parent) const
{
std::vector<model::message_chain_t> message_chains =
find_to_message_chains();

for (const auto &mc : message_chains) {
const auto from_activity_id = mc.front().from();
const auto to_activity_id = mc.back().to();

if (!to_activity_id.has_value())
if (model().participants().count(from_activity_id) == 0)
continue;

auto message_chains_unique = model().get_all_from_to_message_chains(
eid_t{}, to_activity_id.value());
const auto &to =
model().get_participant<model::function>(to_activity_id);

nlohmann::json sequence;
sequence["to"]["location"] = to_location.location;
sequence["to"]["id"] = std::to_string(to_activity_id.value().value());
sequence["to"]["location"] = to.value().full_name(false);
sequence["to"]["id"] = std::to_string(to_activity_id.value());
sequence["message_chains"] = nlohmann::json::array();

block_statements_stack_.push_back(std::ref(sequence));

sequence["message_chains"] = nlohmann::json::array();
nlohmann::json message_chain;

for (const auto &mc : message_chains_unique) {
nlohmann::json message_chain;
block_statements_stack_.push_back(std::ref(message_chain));

block_statements_stack_.push_back(std::ref(message_chain));

for (const auto &m : mc) {
generate_call(m, current_block_statement());
}
for (const auto &m : mc) {
generate_call(m, current_block_statement());
}

block_statements_stack_.pop_back();
block_statements_stack_.pop_back();

sequence["message_chains"].push_back(std::move(message_chain));
}
sequence["message_chains"].push_back(std::move(message_chain));

block_statements_stack_.pop_back();

parent["sequences"].push_back(std::move(sequence));
}
}

for (const auto &sf : config().from()) {
if (sf.location_type == location_t::function) {
eid_t start_from{};
std::string start_from_str;
for (const auto &[k, v] : model().sequences()) {
if (model().participants().count(v.from()) == 0)
continue;
void generator::generate_from_to_sequences(nlohmann::json &parent) const
{
for (const auto &ft : config().from_to()) {
// First, find the sequence of activities from 'from' location
// to 'to' location
assert(ft.size() == 2);

const auto &caller = *model().participants().at(v.from());
std::string vfrom = caller.full_name(false);
if (vfrom == sf.location) {
LOG_DBG("Found sequence diagram start point: {}", k);
start_from = k;
break;
}
}
const auto &from_location = ft.front();
const auto &to_location = ft.back();

if (start_from == 0) {
LOG_WARN("Failed to find participant with {} for start_from "
"condition",
sf.location);
continue;
}
auto from_activity_ids = model().get_from_activity_ids(from_location);
auto to_activity_ids = model().get_to_activity_ids(to_location);

// Use this to break out of recurrent loops
std::vector<eid_t> visited_participants;
if (from_activity_ids.empty()) {
throw error::invalid_sequence_from_condition(model().type(),
model().name(),
fmt::format("Failed to find participant matching '{}' for "
"'from' condition: ",
from_location.location.to_string()));
}

const auto &from =
model().get_participant<model::function>(start_from);
if (from_activity_ids.empty() || to_activity_ids.empty()) {
throw error::invalid_sequence_to_condition(model().type(),
model().name(),
fmt::format("Failed to find participant matching '{}' for "
"'to' condition: ",
to_location.location.to_string()));
}

if (!from.has_value()) {
LOG_WARN("Failed to find participant {} for start_from "
"condition",
sf.location);
for (const auto from_activity_id : from_activity_ids) {
if (model().participants().count(from_activity_id) == 0)
continue;
}

generate_participant(json_, start_from);
const auto &from =
model().get_participant<model::function>(from_activity_id);

[[maybe_unused]] model::function::message_render_mode render_mode =
model::function::message_render_mode::full;
for (const auto to_activity_id : to_activity_ids) {
if (model().participants().count(to_activity_id) == 0)
continue;

nlohmann::json sequence;
sequence["start_from"]["location"] = sf.location;
sequence["start_from"]["id"] = std::to_string(start_from.value());
const auto &to =
model().get_participant<model::function>(to_activity_id);

block_statements_stack_.push_back(std::ref(sequence));
auto message_chains_unique =
model().get_all_from_to_message_chains(
from_activity_id, to_activity_id);

generate_activity(
model().get_activity(start_from), visited_participants);
nlohmann::json sequence;
sequence["from_to"]["from"]["location"] =
from.value().full_name(false);
sequence["from_to"]["from"]["id"] =
std::to_string(from_activity_id.value());
sequence["from_to"]["to"]["location"] =
to.value().full_name(false);
sequence["from_to"]["to"]["id"] =
std::to_string(to_activity_id.value());

block_statements_stack_.pop_back();
block_statements_stack_.push_back(std::ref(sequence));

if (from.value().type_name() == "method" ||
config().combine_free_functions_into_file_participants()) {
sequence["message_chains"] = nlohmann::json::array();

sequence["return_type"] =
make_display_name(from.value().return_type());
}
for (const auto &mc : message_chains_unique) {
nlohmann::json message_chain;

parent["sequences"].push_back(std::move(sequence));
}
else {
// TODO: Add support for other sequence start location types
continue;
block_statements_stack_.push_back(std::ref(message_chain));

for (const auto &m : mc) {
generate_call(m, current_block_statement());
}

block_statements_stack_.pop_back();

sequence["message_chains"].push_back(
std::move(message_chain));
}

block_statements_stack_.pop_back();

parent["sequences"].push_back(std::move(sequence));
}
}
}
}

// Perform config dependent postprocessing on generated participants
for (auto &p : json_["participants"]) {
if (p.contains("display_name")) {
p["display_name"] = make_display_name(p["display_name"]);
std::vector<eid_t> generator::find_from_activities() const
{
std::vector<eid_t> start_from;
for (const auto &sf : config().from()) {
if (sf.location_type == location_t::function) {
bool found{false};
for (const auto &[k, v] : model().sequences()) {
if (model().participants().count(v.from()) == 0)
continue;

const auto &caller = *model().participants().at(v.from());
std::string vfrom = caller.full_name(false);
if (sf.location == vfrom) {
LOG_DBG("Found sequence diagram start point: {}", k);
start_from.push_back(k);
found = true;
}
}

if (!found)
throw error::invalid_sequence_from_condition(model().type(),
model().name(),
fmt::format("Failed to find participant matching '{}' for "
"'from' condition: ",
sf.location.to_string()));
}
}

parent["participants"] = json_["participants"];
return start_from;
}

std::string generator::make_display_name(const std::string &full_name) const
Expand Down
10 changes: 10 additions & 0 deletions src/sequence_diagram/generators/json/sequence_diagram_generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,16 @@ class generator : public common_generator<diagram_config, diagram_model> {
*/
void process_end_while_message() const;

void generate_from_to_sequences(nlohmann::json &parent) const;

void generate_to_sequences(nlohmann::json &parent) const;

void generate_from_sequences(nlohmann::json &parent) const;

std::vector<eid_t> find_from_activities() const;

std::vector<model::message_chain_t> find_to_message_chains() const;

mutable std::set<eid_t> generated_participants_;

// Needed to add "participants" array in a temporary object accessible from
Expand Down
Loading

0 comments on commit 4769b84

Please sign in to comment.