feat(cpp-object-oriented-modular-refactoring): step 11 - source-target-resolver

This commit is contained in:
KOKO\Mimi
2026-08-16 08:34:09 +09:00
parent 5430fffd62
commit a6324a9004
12 changed files with 714 additions and 273 deletions
+227
View File
@@ -0,0 +1,227 @@
#include "fesa/model/source_target_resolver.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "fesa/core/ascii.h"
#include "fesa/model/domain.h"
namespace fesa {
namespace {
struct Match {
const SourceTargetIndexEntry* entry;
bool named_target;
std::size_t input_order;
};
bool InstanceMatches(const std::string& query_instance,
const std::string& entry_instance) {
return query_instance.empty() ||
AsciiCaseInsensitiveEquals(query_instance, entry_instance);
}
bool LooksLikeInteger(const std::string& text) {
if (text.empty()) {
return false;
}
std::size_t offset = 0U;
if (text.front() == '+' || text.front() == '-') {
offset = 1U;
}
if (offset == text.size()) {
return false;
}
return std::all_of(
text.begin() + static_cast<std::ptrdiff_t>(offset), text.end(),
[](const char value) { return value >= '0' && value <= '9'; });
}
Diagnostic TargetDiagnostic(const std::string& code,
const std::string& identity,
const std::string& message) {
return {Severity::kError, code, {{}, 0U}, "SOURCE_TARGET", identity, message};
}
Result<std::vector<ResolvedSourceTarget>> TargetFailure(
const std::string& code, const std::string& identity,
const std::string& message) {
return Result<std::vector<ResolvedSourceTarget>>::Failure(Status::Failure(
FailureCategory::kInput, {TargetDiagnostic(code, identity, message)}));
}
bool SpansInstances(const std::vector<Match>& matches, const bool named) {
const SourceTargetIndexEntry* first = nullptr;
for (const auto& match : matches) {
if (match.named_target != named) {
continue;
}
if (first == nullptr) {
first = match.entry;
} else if (!AsciiCaseInsensitiveEquals(first->instance_name,
match.entry->instance_name)) {
return true;
}
}
return false;
}
} // namespace
SourceTargetIndex::SourceTargetIndex(
std::vector<SourceTargetIndexEntry> entries)
: entries_{std::move(entries)} {}
SourceTargetIndex SourceTargetIndex::FromDomain(const Domain& domain) {
std::vector<SourceTargetIndexEntry> entries;
std::size_t declaration_order = 0U;
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
const auto& node = domain.Nodes()[index];
entries.push_back({SourceEntityKind::kNode, node.source_id.instance_name,
"", node.source_id, static_cast<EntityIndex>(index),
declaration_order++});
}
for (const auto& set : domain.NodeSets()) {
for (const EntityIndex node_index : set.node_indices) {
SourceEntityId source_id{set.instance_name.value_or(""), 0, ""};
if (node_index < domain.Nodes().size()) {
source_id = domain.Nodes()[node_index].source_id;
}
entries.push_back({SourceEntityKind::kNode,
set.instance_name.value_or(source_id.instance_name),
set.name, std::move(source_id), node_index,
declaration_order++});
}
}
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
const auto& element = domain.Elements()[index];
entries.push_back({SourceEntityKind::kElement,
element.source_id.instance_name, "", element.source_id,
static_cast<EntityIndex>(index), declaration_order++});
}
for (std::size_t index = 0U; index < domain.ShellElements().size(); ++index) {
const auto& element = domain.ShellElements()[index];
entries.push_back({SourceEntityKind::kElement,
element.source_id.instance_name, "", element.source_id,
static_cast<EntityIndex>(index), declaration_order++});
}
for (const auto& set : domain.ElementSets()) {
for (const EntityIndex element_index : set.element_indices) {
SourceEntityId source_id{set.instance_name.value_or(""), 0, ""};
if (domain.ShellElements().empty() &&
element_index < domain.Elements().size()) {
source_id = domain.Elements()[element_index].source_id;
} else if (domain.Elements().empty() &&
element_index < domain.ShellElements().size()) {
source_id = domain.ShellElements()[element_index].source_id;
}
entries.push_back({SourceEntityKind::kElement,
set.instance_name.value_or(source_id.instance_name),
set.name, std::move(source_id), element_index,
declaration_order++});
}
}
return SourceTargetIndex{std::move(entries)};
}
const std::vector<SourceTargetIndexEntry>& SourceTargetIndex::Entries()
const noexcept {
return entries_;
}
SourceTargetResolver::SourceTargetResolver(
const SourceTargetIndex& index) noexcept
: index_{&index} {}
Result<std::vector<ResolvedSourceTarget>> SourceTargetResolver::Resolve(
const SourceTargetQuery& query) const {
const auto parsed_label =
ParsePositiveSourceLabel(query.target_name_or_label);
std::vector<Match> matches;
for (std::size_t input_order = 0U; input_order < index_->Entries().size();
++input_order) {
const auto& entry = index_->Entries()[input_order];
if (entry.entity_kind != query.entity_kind ||
!InstanceMatches(query.instance_name, entry.instance_name)) {
continue;
}
const bool named = !entry.target_name.empty() &&
AsciiCaseInsensitiveEquals(entry.target_name,
query.target_name_or_label);
const bool direct = entry.target_name.empty() && parsed_label.HasValue() &&
entry.source_id.source_label == parsed_label.Value();
if (named || direct) {
matches.push_back({&entry, named, input_order});
}
}
if (matches.empty()) {
if (!parsed_label.HasValue() &&
LooksLikeInteger(query.target_name_or_label)) {
return TargetFailure(
"invalid-source-target", query.target_name_or_label,
"A direct source target must be a positive base-10 label.");
}
return TargetFailure(
"unresolved-source-target", query.target_name_or_label,
"The source target does not resolve in its namespace.");
}
const bool has_named =
std::any_of(matches.begin(), matches.end(),
[](const Match& match) { return match.named_target; });
const bool has_direct =
std::any_of(matches.begin(), matches.end(),
[](const Match& match) { return !match.named_target; });
if ((has_named && has_direct) || SpansInstances(matches, true) ||
SpansInstances(matches, false)) {
return TargetFailure(
"ambiguous-source-target", query.target_name_or_label,
"The source target has more than one valid identity interpretation.");
}
std::stable_sort(
matches.begin(), matches.end(), [](const Match& lhs, const Match& rhs) {
if (lhs.entry->declaration_order != rhs.entry->declaration_order) {
return lhs.entry->declaration_order < rhs.entry->declaration_order;
}
return lhs.input_order < rhs.input_order;
});
std::vector<Diagnostic> duplicate_diagnostics;
std::vector<EntityIndex> seen_indices;
for (const auto& match : matches) {
const auto duplicate = std::find(seen_indices.begin(), seen_indices.end(),
match.entry->entity_index);
if (duplicate != seen_indices.end()) {
duplicate_diagnostics.push_back(TargetDiagnostic(
"duplicate-source-target", query.target_name_or_label,
"Duplicate resolved source target " +
match.entry->source_id.source_label_text + "."));
} else {
seen_indices.push_back(match.entry->entity_index);
}
}
if (!duplicate_diagnostics.empty()) {
return Result<std::vector<ResolvedSourceTarget>>::Failure(Status::Failure(
FailureCategory::kInput, std::move(duplicate_diagnostics)));
}
if (!has_named && seen_indices.size() > 1U) {
return TargetFailure(
"ambiguous-source-target", query.target_name_or_label,
"The direct source label resolves to multiple semantic entities.");
}
std::vector<ResolvedSourceTarget> resolved;
resolved.reserve(matches.size());
for (const auto& match : matches) {
resolved.push_back({match.entry->source_id, match.entry->entity_index});
}
return Result<std::vector<ResolvedSourceTarget>>::Success(
std::move(resolved));
}
} // namespace fesa