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
+75 -106
View File
@@ -14,26 +14,14 @@
#include <utility>
#include <vector>
#include "fesa/core/ascii.h"
#include "fesa/math/vector3.h"
#include "fesa/model/shell_geometry.h"
#include "fesa/model/source_target_resolver.h"
namespace fesa {
namespace {
std::string UppercaseAscii(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](char character) {
if (character >= 'a' && character <= 'z') {
return static_cast<char>(character - 'a' + 'A');
}
return character;
});
return value;
}
bool EqualName(const std::string& left, const std::string& right) {
return UppercaseAscii(left) == UppercaseAscii(right);
}
std::vector<std::string> WithoutTrailingEmpty(std::vector<std::string> fields) {
while (!fields.empty() && fields.back().empty()) {
fields.pop_back();
@@ -254,20 +242,6 @@ class MappingContext {
return true;
}
bool TryPositiveInteger(const std::string& text, std::int64_t& value) const {
if (text.empty()) {
return false;
}
char* end = nullptr;
errno = 0;
const long long parsed = std::strtoll(text.c_str(), &end, 10);
if (errno == ERANGE || end != text.c_str() + text.size() || parsed <= 0) {
return false;
}
value = static_cast<std::int64_t>(parsed);
return true;
}
bool ParseDouble(const std::string& text, double& value,
const SourceLocation& location, const std::string& keyword) {
if (text.empty()) {
@@ -311,16 +285,17 @@ class MappingContext {
bool ContainsName(const std::vector<RawPart>& values,
const std::string& name) const {
return std::any_of(
values.begin(), values.end(),
[&name](const RawPart& value) { return EqualName(value.name, name); });
return std::any_of(values.begin(), values.end(),
[&name](const RawPart& value) {
return AsciiCaseInsensitiveEquals(value.name, name);
});
}
bool ContainsName(const std::vector<RawInstance>& values,
const std::string& name) const {
return std::any_of(values.begin(), values.end(),
[&name](const RawInstance& value) {
return EqualName(value.name, name);
return AsciiCaseInsensitiveEquals(value.name, name);
});
}
@@ -328,14 +303,14 @@ class MappingContext {
const std::string& name) const {
return std::any_of(values.begin(), values.end(),
[&name](const RawMaterial& value) {
return EqualName(value.name, name);
return AsciiCaseInsensitiveEquals(value.name, name);
});
}
bool ContainsSetName(const std::vector<RawSet>& sets,
const std::string& name) const {
const auto matches = [&name](const RawSet& set) {
return EqualName(set.name, name);
return AsciiCaseInsensitiveEquals(set.name, name);
};
return std::any_of(sets.begin(), sets.end(), matches);
}
@@ -344,7 +319,7 @@ class MappingContext {
return std::any_of(assembly_sets_.begin(), assembly_sets_.end(),
[node_set, &name](const RawAssemblySet& set) {
return set.is_node_set == node_set &&
EqualName(set.name, name);
AsciiCaseInsensitiveEquals(set.name, name);
});
}
@@ -674,15 +649,15 @@ class MappingContext {
RawElement::Type element_type{};
ElementFamily element_family{};
std::size_t expected_field_count = 0U;
if (EqualName(*type, "B33")) {
if (AsciiCaseInsensitiveEquals(*type, "B33")) {
element_type = RawElement::Type::kB33;
element_family = ElementFamily::kBeam;
expected_field_count = 3U;
} else if (EqualName(*type, "S4")) {
} else if (AsciiCaseInsensitiveEquals(*type, "S4")) {
element_type = RawElement::Type::kS4;
element_family = ElementFamily::kShell;
expected_field_count = 5U;
} else if (EqualName(*type, "S4R")) {
} else if (AsciiCaseInsensitiveEquals(*type, "S4R")) {
element_type = RawElement::Type::kS4r;
element_family = ElementFamily::kShell;
expected_field_count = 5U;
@@ -803,9 +778,7 @@ class MappingContext {
return;
}
if (block.data[0].fields.size() == 2U) {
std::int64_t ignored_integration_points = 0;
if (!TryPositiveInteger(block.data[0].fields[1],
ignored_integration_points)) {
if (!ParsePositiveSourceLabel(block.data[0].fields[1]).HasValue()) {
InputFailure("unsupported-shell-section-option", block.data[0].location,
block.canonical_name, block.data[0].fields[1],
"The optional shell integration-point field must be a "
@@ -932,7 +905,7 @@ class MappingContext {
if (element_set == nullptr || material == nullptr || section == nullptr) {
return;
}
if (!EqualName(*section, "GENERAL")) {
if (!AsciiCaseInsensitiveEquals(*section, "GENERAL")) {
InputFailure("unsupported-section-formulation", block.location,
block.canonical_name, *section,
"Only SECTION=GENERAL belongs to the approved subset.");
@@ -940,7 +913,8 @@ class MappingContext {
}
if (std::any_of(part.sections.begin(), part.sections.end(),
[element_set](const RawSection& existing) {
return EqualName(existing.element_set_name, *element_set);
return AsciiCaseInsensitiveEquals(
existing.element_set_name, *element_set);
})) {
InputFailure("duplicate-entity", block.location, block.canonical_name,
*element_set,
@@ -1332,7 +1306,7 @@ class MappingContext {
"NLGEOM requires NO in the approved subset.");
return;
}
if (!EqualName(*nlgeom->value, "NO")) {
if (!AsciiCaseInsensitiveEquals(*nlgeom->value, "NO")) {
if (model_element_family_ == ElementFamily::kShell) {
InputFailure("unsupported-nonlinear-geometry", block.location,
block.canonical_name, *nlgeom->value,
@@ -1510,22 +1484,24 @@ class MappingContext {
const RawPart* FindPart(const std::string& name) const {
const auto found = std::find_if(
parts_.begin(), parts_.end(),
[&name](const RawPart& part) { return EqualName(part.name, name); });
parts_.begin(), parts_.end(), [&name](const RawPart& part) {
return AsciiCaseInsensitiveEquals(part.name, name);
});
return found == parts_.end() ? nullptr : &*found;
}
const RawInstance* FindInstance(const std::string& name) const {
const auto found = std::find_if(instances_.begin(), instances_.end(),
[&name](const RawInstance& instance) {
return EqualName(instance.name, name);
});
const auto found =
std::find_if(instances_.begin(), instances_.end(),
[&name](const RawInstance& instance) {
return AsciiCaseInsensitiveEquals(instance.name, name);
});
return found == instances_.end() ? nullptr : &*found;
}
std::optional<EntityIndex> FindMaterialIndex(const std::string& name) const {
for (std::size_t index = 0U; index < materials_.size(); ++index) {
if (EqualName(materials_[index].name, name)) {
if (AsciiCaseInsensitiveEquals(materials_[index].name, name)) {
return static_cast<EntityIndex>(index);
}
}
@@ -1534,9 +1510,10 @@ class MappingContext {
const RawSet* FindSet(const std::vector<RawSet>& sets,
const std::string& name) const {
const auto found = std::find_if(
sets.begin(), sets.end(),
[&name](const RawSet& set) { return EqualName(set.name, name); });
const auto found =
std::find_if(sets.begin(), sets.end(), [&name](const RawSet& set) {
return AsciiCaseInsensitiveEquals(set.name, name);
});
return found == sets.end() ? nullptr : &*found;
}
@@ -2000,7 +1977,8 @@ class MappingContext {
const auto definition_instance = std::find_if(
definition_.instances.begin(), definition_.instances.end(),
[&raw_instance](const InstanceDefinition& instance) {
return EqualName(instance.name, raw_instance->name);
return AsciiCaseInsensitiveEquals(instance.name,
raw_instance->name);
});
if (definition_instance == definition_.instances.end()) {
InputFailure("unresolved-reference", raw_set.location,
@@ -2010,11 +1988,11 @@ class MappingContext {
}
if (raw_set.is_node_set) {
definition_.node_sets.erase(
std::remove_if(definition_.node_sets.begin(),
definition_.node_sets.end(),
[&raw_set](const NodeSet& set) {
return EqualName(set.name, raw_set.name);
}),
std::remove_if(
definition_.node_sets.begin(), definition_.node_sets.end(),
[&raw_set](const NodeSet& set) {
return AsciiCaseInsensitiveEquals(set.name, raw_set.name);
}),
definition_.node_sets.end());
NodeSet set{
raw_set.name, definition_instance->name, {}, raw_set.location};
@@ -2040,7 +2018,8 @@ class MappingContext {
std::remove_if(definition_.element_sets.begin(),
definition_.element_sets.end(),
[&raw_set](const ElementSet& set) {
return EqualName(set.name, raw_set.name);
return AsciiCaseInsensitiveEquals(set.name,
raw_set.name);
}),
definition_.element_sets.end());
ElementSet set{
@@ -2066,68 +2045,57 @@ class MappingContext {
}
}
std::optional<std::vector<EntityIndex>> ResolveNodeTarget(
const std::string& target, const SourceLocation& location,
const std::string& keyword) {
std::vector<const NodeSet*> matching_sets;
/// @brief Builds one stable node-target index from the completed candidate.
SourceTargetIndex BuildSourceTargetIndex() const {
std::vector<SourceTargetIndexEntry> entries;
std::size_t declaration_order = 0U;
for (std::size_t node = 0U; node < definition_.nodes.size(); ++node) {
const auto& source_id = definition_.nodes[node].source_id;
entries.push_back({SourceEntityKind::kNode, source_id.instance_name, "",
source_id, static_cast<EntityIndex>(node),
declaration_order++});
}
for (const auto& set : definition_.node_sets) {
if (EqualName(set.name, target)) {
matching_sets.push_back(&set);
for (const EntityIndex node : set.node_indices) {
const auto& source_id = definition_.nodes[node].source_id;
entries.push_back({SourceEntityKind::kNode,
set.instance_name.value_or(source_id.instance_name),
set.name, source_id, node, declaration_order++});
}
}
return SourceTargetIndex{std::move(entries)};
}
std::vector<EntityIndex> matching_nodes;
std::int64_t label = 0;
if (TryPositiveInteger(target, label)) {
for (std::size_t index = 0U; index < definition_.nodes.size(); ++index) {
if (definition_.nodes[index].source_id.source_label == label) {
matching_nodes.push_back(static_cast<EntityIndex>(index));
}
}
}
// A token is resolved only after both approved interpretations have
// been considered; declaration order never gives a node set priority
// over an equally valid direct source-label target.
if (matching_sets.size() > 1U) {
std::optional<std::vector<EntityIndex>> ResolveNodeTarget(
const SourceTargetResolver& resolver, const std::string& target,
const SourceLocation& location, const std::string& keyword) {
auto resolved = resolver.Resolve({SourceEntityKind::kNode, "", target});
if (!resolved.HasValue()) {
InputFailure(
"unresolved-reference", location, keyword, target,
"The node-set target is ambiguous across identity instances.");
"The boundary or load target must resolve unambiguously to one "
"node or one node set.");
return std::nullopt;
}
if (matching_nodes.size() > 1U) {
InputFailure("unresolved-reference", location, keyword, target,
"The direct node-label target is ambiguous across identity "
"instances.");
return std::nullopt;
std::vector<EntityIndex> indices;
indices.reserve(resolved.Value().size());
for (const auto& entry : resolved.Value()) {
indices.push_back(entry.entity_index);
}
if (matching_sets.size() == 1U && matching_nodes.size() == 1U) {
InputFailure("unresolved-reference", location, keyword, target,
"The target is ambiguous between a node-set name and a "
"direct node label.");
return std::nullopt;
}
if (matching_sets.size() == 1U) {
return matching_sets.front()->node_indices;
}
if (matching_nodes.size() == 1U) {
return matching_nodes;
}
InputFailure(
"unresolved-reference", location, keyword, target,
"The boundary or load target must resolve to one node or node set.");
return std::nullopt;
return indices;
}
void FinalizeStep() {
std::vector<BoundaryCondition> boundaries = model_boundaries_;
boundaries.insert(boundaries.end(), step_.boundaries.begin(),
step_.boundaries.end());
const SourceTargetIndex target_index = BuildSourceTargetIndex();
const SourceTargetResolver target_resolver{target_index};
std::map<std::pair<EntityIndex, int>, double> prescribed_values;
for (const auto& boundary : boundaries) {
auto target =
ResolveNodeTarget(boundary.target, boundary.location, "BOUNDARY");
auto target = ResolveNodeTarget(target_resolver, boundary.target,
boundary.location, "BOUNDARY");
if (!target) {
return;
}
@@ -2148,7 +2116,8 @@ class MappingContext {
}
}
for (const auto& load : step_.loads) {
if (!ResolveNodeTarget(load.target, load.location, "CLOAD")) {
if (!ResolveNodeTarget(target_resolver, load.target, load.location,
"CLOAD")) {
return;
}
}