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