feat(cpp-object-oriented-modular-refactoring): step 6 - io-application-google-style

This commit is contained in:
KOKO\Mimi
2026-08-16 06:59:50 +09:00
parent bb178c9d3c
commit 83fd1d1c7e
31 changed files with 10499 additions and 11445 deletions
+4 -4
View File
@@ -5,8 +5,8 @@
#include "fesa/assembly/load_assembler.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/sparse_assembler.h"
#include "fesa/io/abaqus/domain_mapper.hpp"
#include "fesa/io/abaqus/input_reader.hpp"
#include "fesa/io/abaqus/domain_mapper.h"
#include "fesa/io/abaqus/input_reader.h"
#include "fesa/results/result_recovery.h"
#include "fesa/results/results_writer.h"
#include "fesa/solvers/linear/linear_solver.h"
@@ -65,11 +65,11 @@ Status LinearStaticAnalysis::Initialize(const AnalysisRequest& request) {
diagnostics_.clear();
request_ = request;
const auto parsed = AbaqusInputReader{}.read(request_.input_path);
const auto parsed = AbaqusInputReader{}.Read(request_.input_path);
if (!parsed.HasValue()) {
return parsed.GetStatus();
}
auto domain = AbaqusDomainMapper{}.map(parsed.Value());
auto domain = AbaqusDomainMapper{}.Map(parsed.Value());
if (!domain.HasValue()) {
return domain.GetStatus();
}
+68 -77
View File
@@ -1,16 +1,16 @@
#include "fesa/app/fesa_application.hpp"
#include "fesa/analysis/linear_static_analysis.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/core/diagnostic.h"
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include "fesa/app/fesa_application.h"
#include <filesystem>
#include <iostream>
#include <string>
#include <vector>
#include "fesa/analysis/linear_static_analysis.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/core/diagnostic.h"
#include "fesa/io/hdf5/hdf5_results_writer.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
namespace fesa {
namespace {
@@ -21,93 +21,84 @@ constexpr int kModelExitCode = 4;
constexpr int kSolverExitCode = 5;
constexpr int kOutputExitCode = 6;
bool startsWithOption(const std::string& argument) {
return !argument.empty() && argument.front() == '-';
bool StartsWithOption(const std::string& argument) {
return !argument.empty() && argument.front() == '-';
}
Diagnostic usageDiagnostic() {
return {
Severity::kError,
"cli-usage",
{{}, 0U},
"",
"",
"Usage: fesa.exe <model.inp> [--output <results.h5>]."};
Diagnostic UsageDiagnostic() {
return {Severity::kError,
"cli-usage",
{{}, 0U},
"",
"",
"Usage: fesa.exe <model.inp> [--output <results.h5>]."};
}
const char* severityName(const Severity severity) {
return severity == Severity::kWarning ? "warning" : "error";
const char* SeverityName(const Severity severity) {
return severity == Severity::kWarning ? "warning" : "error";
}
void writeDiagnostics(std::vector<Diagnostic> diagnostics) {
SortDiagnostics(diagnostics);
for (const auto& diagnostic : diagnostics) {
// Stable field labels and tab separators keep empty source fields
// explicit without depending on locale-specific formatting.
std::cerr
<< "severity=" << severityName(diagnostic.severity)
<< '\t' << "code=" << diagnostic.code
<< '\t' << "file="
<< diagnostic.location.file.generic_u8string()
<< '\t' << "line=" << diagnostic.location.line
<< '\t' << "keyword=" << diagnostic.keyword
<< '\t' << "entity_identity=" << diagnostic.entity_identity
<< '\t' << "message=" << diagnostic.message
<< '\n';
}
void WriteDiagnostics(std::vector<Diagnostic> diagnostics) {
SortDiagnostics(diagnostics);
for (const auto& diagnostic : diagnostics) {
// Stable field labels and tab separators keep empty source fields
// explicit without depending on locale-specific formatting.
std::cerr << "severity=" << SeverityName(diagnostic.severity) << '\t'
<< "code=" << diagnostic.code << '\t'
<< "file=" << diagnostic.location.file.generic_u8string() << '\t'
<< "line=" << diagnostic.location.line << '\t'
<< "keyword=" << diagnostic.keyword << '\t'
<< "entity_identity=" << diagnostic.entity_identity << '\t'
<< "message=" << diagnostic.message << '\n';
}
}
int exitCodeFor(const Status& status) {
switch (status.Category().value_or(FailureCategory::kInput)) {
int ExitCodeFor(const Status& status) {
switch (status.Category().value_or(FailureCategory::kInput)) {
case FailureCategory::kInput:
return kInputExitCode;
return kInputExitCode;
case FailureCategory::kModel:
return kModelExitCode;
return kModelExitCode;
case FailureCategory::kSolver:
return kSolverExitCode;
return kSolverExitCode;
case FailureCategory::kOutput:
return kOutputExitCode;
}
return kInputExitCode;
return kOutputExitCode;
}
return kInputExitCode;
}
} // namespace
} // namespace
int FesaApplication::run(const std::vector<std::string>& arguments) {
const bool defaultOutputForm =
arguments.size() == 1U &&
!arguments[0U].empty() &&
!startsWithOption(arguments[0U]);
const bool explicitOutputForm =
arguments.size() == 3U &&
!arguments[0U].empty() &&
!startsWithOption(arguments[0U]) &&
arguments[1U] == "--output" &&
!arguments[2U].empty() &&
!startsWithOption(arguments[2U]);
if (!defaultOutputForm && !explicitOutputForm) {
writeDiagnostics({usageDiagnostic()});
return kUsageExitCode;
}
int FesaApplication::Run(const std::vector<std::string>& arguments) {
const bool default_output_form = arguments.size() == 1U &&
!arguments[0U].empty() &&
!StartsWithOption(arguments[0U]);
const bool explicit_output_form =
arguments.size() == 3U && !arguments[0U].empty() &&
!StartsWithOption(arguments[0U]) && arguments[1U] == "--output" &&
!arguments[2U].empty() && !StartsWithOption(arguments[2U]);
if (!default_output_form && !explicit_output_form) {
WriteDiagnostics({UsageDiagnostic()});
return kUsageExitCode;
}
AnalysisRequest request;
request.input_path = arguments[0U];
request.output_path = explicitOutputForm
? std::filesystem::path{arguments[2U]}
: std::filesystem::current_path() / "results.h5";
AnalysisRequest request;
request.input_path = arguments[0U];
request.output_path = explicit_output_form
? std::filesystem::path{arguments[2U]}
: std::filesystem::current_path() / "results.h5";
TbbParallelFor parallelFor;
MklPardisoSolver linearSolver;
Hdf5ResultsWriter resultsWriter;
LinearStaticAnalysis analysis{
parallelFor, linearSolver, resultsWriter};
const Status status = analysis.Run(request);
if (status.IsOk()) {
return kSuccessExitCode;
}
TbbParallelFor parallel_for;
MklPardisoSolver linear_solver;
Hdf5ResultsWriter results_writer;
LinearStaticAnalysis analysis{parallel_for, linear_solver, results_writer};
const Status status = analysis.Run(request);
if (status.IsOk()) {
return kSuccessExitCode;
}
writeDiagnostics(status.Diagnostics());
return exitCodeFor(status);
WriteDiagnostics(status.Diagnostics());
return ExitCodeFor(status);
}
} // namespace fesa
} // namespace fesa
+11 -11
View File
@@ -1,17 +1,17 @@
#include "fesa/app/fesa_application.hpp"
#include <cstddef>
#include <string>
#include <vector>
#include "fesa/app/fesa_application.h"
int main(const int argc, char* argv[]) {
std::vector<std::string> arguments;
if (argc > 1) {
arguments.reserve(static_cast<std::size_t>(argc - 1));
}
// The application boundary receives only operands and options, not argv[0].
for (int index = 1; index < argc; ++index) {
arguments.emplace_back(argv[index]);
}
return fesa::FesaApplication{}.run(arguments);
std::vector<std::string> arguments;
if (argc > 1) {
arguments.reserve(static_cast<std::size_t>(argc - 1));
}
// The application boundary receives only operands and options, not argv[0].
for (int index = 1; index < argc; ++index) {
arguments.emplace_back(argv[index]);
}
return fesa::FesaApplication{}.Run(arguments);
}
File diff suppressed because it is too large Load Diff
+160 -186
View File
@@ -1,4 +1,4 @@
#include "fesa/io/abaqus/input_reader.hpp"
#include "fesa/io/abaqus/input_reader.h"
#include <algorithm>
#include <cstdint>
@@ -15,202 +15,176 @@
namespace fesa {
namespace {
std::filesystem::path normalizedPath(const std::filesystem::path& path) {
std::error_code error;
const auto absolute = std::filesystem::absolute(path, error);
return (error ? path : absolute).lexically_normal();
std::filesystem::path NormalizedPath(const std::filesystem::path& path) {
std::error_code error;
const auto absolute = std::filesystem::absolute(path, error);
return (error ? path : absolute).lexically_normal();
}
bool isAsciiWhitespace(char value) noexcept {
return value == ' ' || value == '\t' || value == '\r' ||
value == '\n' || value == '\f' || value == '\v';
bool IsAsciiWhitespace(char value) noexcept {
return value == ' ' || value == '\t' || value == '\r' || value == '\n' ||
value == '\f' || value == '\v';
}
std::string trim(std::string_view text) {
while (!text.empty() && isAsciiWhitespace(text.front())) {
text.remove_prefix(1U);
std::string Trim(std::string_view text) {
while (!text.empty() && IsAsciiWhitespace(text.front())) {
text.remove_prefix(1U);
}
while (!text.empty() && IsAsciiWhitespace(text.back())) {
text.remove_suffix(1U);
}
return std::string{text};
}
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');
}
while (!text.empty() && isAsciiWhitespace(text.back())) {
text.remove_suffix(1U);
}
return std::string{text};
return character;
});
return value;
}
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;
std::vector<std::string> SplitFields(std::string_view line) {
std::vector<std::string> fields;
std::size_t field_start = 0U;
while (true) {
const std::size_t separator = line.find(',', field_start);
if (separator == std::string_view::npos) {
fields.push_back(Trim(line.substr(field_start)));
break;
}
fields.push_back(Trim(line.substr(field_start, separator - field_start)));
field_start = separator + 1U;
}
return fields;
}
std::vector<std::string> splitFields(std::string_view line) {
std::vector<std::string> fields;
std::size_t fieldStart = 0U;
while (true) {
const std::size_t separator = line.find(',', fieldStart);
if (separator == std::string_view::npos) {
fields.push_back(trim(line.substr(fieldStart)));
break;
}
fields.push_back(trim(line.substr(fieldStart, separator - fieldStart)));
fieldStart = separator + 1U;
}
return fields;
/// @brief Computes the stable identity of the exact source bytes.
/// @note This runs before line-ending handling so parser provenance is not
/// affected by text normalization.
std::string ContentIdentity(const std::string& bytes) {
constexpr std::uint64_t kOffsetBasis = 14695981039346656037ULL;
constexpr std::uint64_t kPrime = 1099511628211ULL;
std::uint64_t hash = kOffsetBasis;
// Hash the binary input before CRLF handling so provenance follows the
// exact file bytes rather than a normalized text representation.
for (const unsigned char byte : bytes) {
hash ^= static_cast<std::uint64_t>(byte);
hash *= kPrime;
}
std::ostringstream formatted;
formatted << "fnv1a64:" << std::hex << std::setfill('0') << std::setw(16)
<< hash;
return formatted.str();
}
std::string contentIdentity(const std::string& bytes) {
constexpr std::uint64_t offsetBasis = 14695981039346656037ULL;
constexpr std::uint64_t prime = 1099511628211ULL;
std::uint64_t hash = offsetBasis;
// Hash the binary input before CRLF handling so provenance follows the
// exact file bytes rather than a normalized text representation.
for (const unsigned char byte : bytes) {
hash ^= static_cast<std::uint64_t>(byte);
hash *= prime;
}
std::ostringstream formatted;
formatted << "fnv1a64:" << std::hex << std::setfill('0')
<< std::setw(16) << hash;
return formatted.str();
}
Result<ParsedInput> failure(
const std::filesystem::path& sourcePath,
std::size_t line,
std::string code,
std::string keyword,
std::string message) {
Diagnostic diagnostic{
Severity::kError,
std::move(code),
{sourcePath, line},
std::move(keyword),
"",
std::move(message)};
return Result<ParsedInput>::Failure(Status::Failure(
FailureCategory::kInput, {std::move(diagnostic)}));
}
} // namespace
Result<ParsedInput> AbaqusInputReader::read(
const std::filesystem::path& inputPath) const {
const auto sourcePath = normalizedPath(inputPath);
std::ifstream stream{sourcePath, std::ios::binary};
if (!stream) {
return failure(
sourcePath,
0U,
"input-file-unreadable",
"",
"The Abaqus input file could not be opened for reading.");
}
const std::string bytes{
std::istreambuf_iterator<char>{stream},
std::istreambuf_iterator<char>{}};
if (stream.bad()) {
return failure(
sourcePath,
0U,
"input-file-unreadable",
"",
"The Abaqus input file could not be read completely.");
}
ParsedInput parsed{sourcePath, contentIdentity(bytes), {}};
std::size_t lineStart = 0U;
std::size_t lineNumber = 1U;
while (lineStart < bytes.size()) {
const std::size_t newline = bytes.find('\n', lineStart);
const std::size_t lineEnd =
newline == std::string::npos ? bytes.size() : newline;
std::string originalLine = bytes.substr(lineStart, lineEnd - lineStart);
if (!originalLine.empty() && originalLine.back() == '\r') {
originalLine.pop_back();
}
const std::string trimmedLine = trim(originalLine);
if (!trimmedLine.empty() && trimmedLine.rfind("**", 0U) != 0U) {
if (trimmedLine.front() == '*') {
const auto fields = splitFields(trimmedLine);
const std::string keywordText =
fields.empty() ? std::string{} : trim(
std::string_view{fields[0]}.substr(1U));
if (keywordText.empty()) {
return failure(
sourcePath,
lineNumber,
"malformed-keyword",
trimmedLine,
"A keyword line requires a non-empty keyword name.");
}
KeywordBlock block{
uppercaseAscii(keywordText),
originalLine,
{},
{},
{sourcePath, lineNumber}};
for (std::size_t index = 1U; index < fields.size(); ++index) {
const std::string& field = fields[index];
if (field.empty()) {
return failure(
sourcePath,
lineNumber,
"malformed-keyword",
block.canonicalName,
"A keyword parameter name cannot be empty.");
}
const std::size_t equals = field.find('=');
const std::string parameterName = trim(std::string_view{field}.substr(
0U, equals));
if (parameterName.empty()) {
return failure(
sourcePath,
lineNumber,
"malformed-keyword",
block.canonicalName,
"A keyword parameter name cannot be empty.");
}
KeywordParameter parameter{
uppercaseAscii(parameterName), std::nullopt};
if (equals != std::string::npos) {
parameter.value = trim(
std::string_view{field}.substr(equals + 1U));
}
block.parameters.push_back(std::move(parameter));
}
parsed.blocks.push_back(std::move(block));
} else {
if (parsed.blocks.empty()) {
return failure(
sourcePath,
lineNumber,
"orphan-data-line",
Result<ParsedInput> Failure(const std::filesystem::path& source_path,
std::size_t line, std::string code,
std::string keyword, std::string message) {
Diagnostic diagnostic{Severity::kError,
std::move(code),
{source_path, line},
std::move(keyword),
"",
"A data line must follow a keyword line.");
}
parsed.blocks.back().data.push_back(
{splitFields(originalLine), {sourcePath, lineNumber}});
}
}
if (newline == std::string::npos) {
break;
}
lineStart = newline + 1U;
++lineNumber;
}
return Result<ParsedInput>::Success(std::move(parsed));
std::move(message)};
return Result<ParsedInput>::Failure(
Status::Failure(FailureCategory::kInput, {std::move(diagnostic)}));
}
} // namespace fesa
} // namespace
Result<ParsedInput> AbaqusInputReader::Read(
const std::filesystem::path& input_path) const {
const auto source_path = NormalizedPath(input_path);
std::ifstream stream{source_path, std::ios::binary};
if (!stream) {
return Failure(source_path, 0U, "input-file-unreadable", "",
"The Abaqus input file could not be opened for reading.");
}
const std::string bytes{std::istreambuf_iterator<char>{stream},
std::istreambuf_iterator<char>{}};
if (stream.bad()) {
return Failure(source_path, 0U, "input-file-unreadable", "",
"The Abaqus input file could not be read completely.");
}
ParsedInput parsed{source_path, ContentIdentity(bytes), {}};
std::size_t line_start = 0U;
std::size_t line_number = 1U;
while (line_start < bytes.size()) {
const std::size_t newline = bytes.find('\n', line_start);
const std::size_t line_end =
newline == std::string::npos ? bytes.size() : newline;
std::string original_line = bytes.substr(line_start, line_end - line_start);
if (!original_line.empty() && original_line.back() == '\r') {
original_line.pop_back();
}
const std::string trimmed_line = Trim(original_line);
if (!trimmed_line.empty() && trimmed_line.rfind("**", 0U) != 0U) {
if (trimmed_line.front() == '*') {
const auto fields = SplitFields(trimmed_line);
const std::string keyword_text =
fields.empty() ? std::string{}
: Trim(std::string_view{fields[0]}.substr(1U));
if (keyword_text.empty()) {
return Failure(source_path, line_number, "malformed-keyword",
trimmed_line,
"A keyword line requires a non-empty keyword name.");
}
KeywordBlock block{UppercaseAscii(keyword_text),
original_line,
{},
{},
{source_path, line_number}};
for (std::size_t index = 1U; index < fields.size(); ++index) {
const std::string& field = fields[index];
if (field.empty()) {
return Failure(source_path, line_number, "malformed-keyword",
block.canonical_name,
"A keyword parameter name cannot be empty.");
}
const std::size_t equals = field.find('=');
const std::string parameter_name =
Trim(std::string_view{field}.substr(0U, equals));
if (parameter_name.empty()) {
return Failure(source_path, line_number, "malformed-keyword",
block.canonical_name,
"A keyword parameter name cannot be empty.");
}
KeywordParameter parameter{UppercaseAscii(parameter_name),
std::nullopt};
if (equals != std::string::npos) {
parameter.value = Trim(std::string_view{field}.substr(equals + 1U));
}
block.parameters.push_back(std::move(parameter));
}
parsed.blocks.push_back(std::move(block));
} else {
if (parsed.blocks.empty()) {
return Failure(source_path, line_number, "orphan-data-line", "",
"A data line must follow a keyword line.");
}
parsed.blocks.back().data.push_back(
{SplitFields(original_line), {source_path, line_number}});
}
}
if (newline == std::string::npos) {
break;
}
line_start = newline + 1U;
++line_number;
}
return Result<ParsedInput>::Success(std::move(parsed));
}
} // namespace fesa
File diff suppressed because it is too large Load Diff