feat(shell-surface-pressure-load): step 2 - reject-invalid-pressure-input

This commit is contained in:
KOKO\Mimi
2026-09-01 13:11:27 +09:00
parent d5fc316ef2
commit c3b671d267
2 changed files with 412 additions and 16 deletions
+128 -16
View File
@@ -2,6 +2,7 @@
#include <algorithm>
#include <array>
#include <charconv>
#include <cerrno>
#include <cmath>
#include <cstdlib>
@@ -313,7 +314,9 @@ class DomainMappingContext::Impl {
}
bool ValidateParameters(const KeywordBlock& block,
const std::vector<std::string_view>& allowed) {
const std::vector<std::string_view>& allowed,
std::string_view unsupported_code =
"invalid-keyword-parameter") {
std::set<std::string> seen;
for (const auto& candidate : block.parameters) {
if (!seen.insert(candidate.name).second) {
@@ -323,10 +326,10 @@ class DomainMappingContext::Impl {
}
if (std::find(allowed.begin(), allowed.end(), candidate.name) ==
allowed.end()) {
return InputFailure(
"invalid-keyword-parameter", block.location, block.canonical_name,
candidate.name,
"The keyword parameter is outside the approved subset.");
return InputFailure(std::string{unsupported_code}, block.location,
block.canonical_name, candidate.name,
"The keyword parameter is outside the approved "
"subset.");
}
}
return true;
@@ -437,7 +440,17 @@ class DomainMappingContext::Impl {
return InputFailure("invalid-numeric-value", location, keyword, text,
"Pressure values require C-locale decimal syntax.");
}
return ParseDouble(text, value, location, keyword);
const char* const begin = text.data();
const char* const end = begin + text.size();
const char* const numeric_begin = *begin == '+' ? begin + 1U : begin;
const auto parsed = std::from_chars(numeric_begin, end, value,
std::chars_format::general);
if (parsed.ec != std::errc{} || parsed.ptr != end ||
!std::isfinite(value)) {
return InputFailure("invalid-numeric-value", location, keyword, text,
"Pressure values must be finite C-locale values.");
}
return true;
}
bool ParseNonlinearStaticDouble(const std::string& text, double& value,
@@ -532,6 +545,22 @@ class DomainMappingContext::Impl {
});
}
bool IsPressureNameToken(const std::string_view text) const noexcept {
if (text.empty() || text.size() > 80U) {
return false;
}
const auto is_initial = [](const char value) {
return (value >= 'A' && value <= 'Z') ||
(value >= 'a' && value <= 'z') || value == '_';
};
const auto is_subsequent = [is_initial](const char value) {
return is_initial(value) || (value >= '0' && value <= '9') ||
value == '-' || value == '_';
};
return is_initial(text.front()) &&
std::all_of(text.begin() + 1, text.end(), is_subsequent);
}
void ParseBlocks() {
definition_.source_path = input_.source_path;
definition_.source_content_identity = input_.source_content_identity;
@@ -800,6 +829,10 @@ class DomainMappingContext::Impl {
return;
}
AddIgnoredWarning(block);
} else if (block.canonical_name == "SURFACE") {
InputFailure("unsupported-surface-scope", block.location,
block.canonical_name, "",
"Pressure surfaces are supported only at assembly scope.");
} else {
pending_section_.reset();
beam_section_context_active_ = false;
@@ -1319,6 +1352,12 @@ class DomainMappingContext::Impl {
"An assembly cannot be nested in an instance.");
return;
}
if (block.canonical_name == "SURFACE") {
InputFailure("unsupported-surface-scope", block.location,
block.canonical_name, instances_.back().name,
"Pressure surfaces are unsupported inside an instance.");
return;
}
InputFailure("unsupported-instance-mesh-semantics", block.location,
block.canonical_name, instances_.back().name,
"Instance-local mesh definitions are unsupported.");
@@ -1334,10 +1373,19 @@ class DomainMappingContext::Impl {
return;
}
const auto* name = RequiredParameterValue(block, name_parameter);
const auto* instance = RequiredParameterValue(block, "INSTANCE");
if (name == nullptr || instance == nullptr) {
if (name == nullptr) {
return;
}
const auto* instance_parameter = Parameter(block, "INSTANCE");
if (instance_parameter == nullptr || !instance_parameter->value ||
instance_parameter->value->empty()) {
InputFailure(node_set ? "invalid-keyword-parameter"
: "implicit-instance-elset",
block.location, block.canonical_name, "INSTANCE",
"Assembly element sets require an explicit INSTANCE.");
return;
}
const std::string& instance = *instance_parameter->value;
const auto* generate_parameter = Parameter(block, "GENERATE");
if (generate_parameter != nullptr && generate_parameter->value) {
InputFailure("invalid-keyword-parameter", block.location,
@@ -1359,7 +1407,30 @@ class DomainMappingContext::Impl {
"element-set namespace.");
return;
}
RawAssemblySet set{node_set, *name, *instance, {}, block.location};
if (!node_set && generate_parameter == nullptr) {
for (const auto& row : block.data) {
for (const auto& field : WithoutTrailingEmpty(row.fields)) {
if (field.empty()) {
continue;
}
const bool numeric = !field.empty() &&
std::all_of(field.begin(), field.end(),
[](const char value) {
return value >= '0' &&
value <= '9';
});
if (!numeric) {
const std::string code = AsciiCaseInsensitiveEquals(field, *name)
? "cyclic-elset"
: "nested-elset";
InputFailure(code, row.location, block.canonical_name, field,
"Assembly ELSET members must be direct labels.");
return;
}
}
}
}
RawAssemblySet set{node_set, *name, instance, {}, block.location};
if (!ParseSetMembers(block, generate_parameter != nullptr, set.members)) {
return;
}
@@ -1367,7 +1438,8 @@ class DomainMappingContext::Impl {
}
void ParseElementSurface(const KeywordBlock& block) {
if (!ValidateParameters(block, {"TYPE", "NAME"})) {
if (!ValidateParameters(block, {"TYPE", "NAME"},
"unsupported-parameter")) {
return;
}
const auto* type = RequiredParameterValue(block, "TYPE");
@@ -1375,6 +1447,12 @@ class DomainMappingContext::Impl {
if (type == nullptr || name == nullptr) {
return;
}
if (!IsPressureNameToken(*name)) {
InputFailure("invalid-surface-name", block.location,
block.canonical_name, *name,
"Surface names must use the approved ASCII name syntax.");
return;
}
if (!AsciiCaseInsensitiveEquals(*type, "ELEMENT")) {
InputFailure("unsupported-surface-type", block.location,
block.canonical_name, *type,
@@ -1387,7 +1465,7 @@ class DomainMappingContext::Impl {
return;
}
if (block.data.empty()) {
InputFailure("invalid-data-arity", block.location, block.canonical_name,
InputFailure("empty-surface", block.location, block.canonical_name,
*name, "An element surface requires at least one member.");
return;
}
@@ -1563,7 +1641,8 @@ class DomainMappingContext::Impl {
}
void ParseDsload(const KeywordBlock& block) {
if (!ValidateParameters(block, {}) || block.data.empty()) {
if (!ValidateParameters(block, {}, "unsupported-dsload-parameter") ||
block.data.empty()) {
if (!failure_) {
InputFailure("invalid-data-arity", block.location, block.canonical_name,
"", "DSLOAD requires one or more rows.");
@@ -1578,6 +1657,13 @@ class DomainMappingContext::Impl {
"", "DSLOAD rows require surface, P, magnitude.");
return;
}
if (!IsPressureNameToken(row.fields[0])) {
InputFailure("invalid-surface-name", row.location,
block.canonical_name, row.fields[0],
"DSLOAD surface names must use the approved ASCII name "
"syntax.");
return;
}
if (!AsciiCaseInsensitiveEquals(row.fields[1], "P")) {
InputFailure("unsupported-load-type", row.location,
block.canonical_name, row.fields[1],
@@ -2469,6 +2555,12 @@ class DomainMappingContext::Impl {
"A direct surface member must be instance.positive-element-label.");
}
const std::string instance_name = member.target.substr(0U, dot);
if (!IsPressureNameToken(instance_name)) {
return InputFailure(
"unsupported-surface-target", member.location, "SURFACE",
member.target,
"A direct surface member must use an identity instance name.");
}
std::int64_t source_label = 0;
if (!ParseInteger(member.target.substr(dot + 1U), source_label,
member.location, "SURFACE", true)) {
@@ -2479,6 +2571,12 @@ class DomainMappingContext::Impl {
return AsciiCaseInsensitiveEquals(value.name, instance_name);
});
if (raw_instance == instances_.end()) {
if (FindPart(instance_name) != nullptr) {
return InputFailure(
"unsupported-surface-target", member.location, "SURFACE",
member.target,
"A direct surface member cannot use a part name as an instance.");
}
return InputFailure("unresolved-reference", member.location, "SURFACE",
member.target,
"A direct surface member instance must resolve.");
@@ -2507,15 +2605,29 @@ class DomainMappingContext::Impl {
const RawSurfaceMember& member,
std::vector<std::pair<ElementSurfaceFacetDefinition, std::size_t>>*
candidates) {
if (!IsPressureNameToken(member.target)) {
return InputFailure("unsupported-surface-target", member.location,
"SURFACE", member.target,
"A surface ELSET must use the approved name syntax.");
}
const auto raw_set = std::find_if(
assembly_sets_.begin(), assembly_sets_.end(), [&member](const RawAssemblySet& value) {
return !value.is_node_set &&
AsciiCaseInsensitiveEquals(value.name, member.target);
});
if (raw_set == assembly_sets_.end()) {
return InputFailure("unresolved-reference", member.location, "SURFACE",
member.target,
"A surface ELSET must be assembly-scoped and explicit-instance.");
const bool part_scoped = std::any_of(
parts_.begin(), parts_.end(), [this, &member](const RawPart& part) {
return FindSet(part.element_sets, member.target) != nullptr;
});
if (part_scoped) {
return InputFailure(
"cross-scope-target", member.location, "SURFACE", member.target,
"A surface ELSET must be declared at assembly scope.");
}
return InputFailure(
"missing-reference", member.location, "SURFACE", member.target,
"A surface ELSET must be assembly-scoped and explicit-instance.");
}
const auto raw_instance = std::find_if(
instances_.begin(), instances_.end(), [&raw_set](const RawInstance& value) {
@@ -2713,7 +2825,7 @@ class DomainMappingContext::Impl {
load.surface_name);
});
if (surface == definition_.element_surfaces.end()) {
InputFailure("unresolved-reference", load.data_location, "DSLOAD",
InputFailure("missing-reference", load.data_location, "DSLOAD",
load.surface_name,
"A DSLOAD surface must resolve after surface validation.");
return;