feat(cpp-object-oriented-modular-refactoring): step 6 - io-application-google-style
This commit is contained in:
+2116
-2383
File diff suppressed because it is too large
Load Diff
+160
-186
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user