feat(shell-surface-pressure-load): step 1 - map-pressure-input-contract
This commit is contained in:
@@ -26,6 +26,10 @@ bool IsAsciiWhitespace(char value) noexcept {
|
||||
value == '\f' || value == '\v';
|
||||
}
|
||||
|
||||
bool IsAllowedInputCharacter(unsigned char value) noexcept {
|
||||
return value == '\t' || (value >= 0x20U && value <= 0x7eU);
|
||||
}
|
||||
|
||||
std::string Trim(std::string_view text) {
|
||||
while (!text.empty() && IsAsciiWhitespace(text.front())) {
|
||||
text.remove_prefix(1U);
|
||||
@@ -61,6 +65,44 @@ std::vector<std::string> SplitFields(std::string_view line) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
bool IsKeywordContinuation(std::string_view trimmed_line) noexcept {
|
||||
return !trimmed_line.empty() && trimmed_line.back() == ',';
|
||||
}
|
||||
|
||||
bool AppendKeywordParameters(KeywordBlock& block,
|
||||
const std::vector<std::string>& fields,
|
||||
std::size_t first_field,
|
||||
bool has_continuation) {
|
||||
if (has_continuation && fields.empty()) {
|
||||
return false;
|
||||
}
|
||||
const std::size_t field_count =
|
||||
has_continuation ? fields.size() - 1U : fields.size();
|
||||
if (field_count < first_field) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t index = first_field; index < field_count; ++index) {
|
||||
const std::string& field = fields[index];
|
||||
if (field.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @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.
|
||||
@@ -112,7 +154,13 @@ Result<ParsedInput> AbaqusInputReader::Read(
|
||||
"The Abaqus input file could not be read completely.");
|
||||
}
|
||||
|
||||
ParsedInput parsed{source_path, ContentIdentity(bytes), {}};
|
||||
struct PhysicalLine {
|
||||
std::string original_line;
|
||||
std::string trimmed_line;
|
||||
std::size_t line_number;
|
||||
};
|
||||
|
||||
std::vector<PhysicalLine> physical_lines;
|
||||
std::size_t line_start = 0U;
|
||||
std::size_t line_number = 1U;
|
||||
while (line_start < bytes.size()) {
|
||||
@@ -123,60 +171,20 @@ Result<ParsedInput> AbaqusInputReader::Read(
|
||||
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 (original_line.size() > 256U) {
|
||||
return Failure(source_path, line_number, "input-line-too-long", "",
|
||||
"Each physical input line is limited to 256 ASCII bytes.");
|
||||
}
|
||||
for (const unsigned char character : original_line) {
|
||||
if (!IsAllowedInputCharacter(character)) {
|
||||
return Failure(source_path, line_number, "invalid-input-character", "",
|
||||
"Input lines must use printable 7-bit ASCII or tabs.");
|
||||
}
|
||||
}
|
||||
|
||||
physical_lines.push_back(
|
||||
{original_line, Trim(original_line), line_number});
|
||||
|
||||
if (newline == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
@@ -184,6 +192,75 @@ Result<ParsedInput> AbaqusInputReader::Read(
|
||||
++line_number;
|
||||
}
|
||||
|
||||
ParsedInput parsed{source_path, ContentIdentity(bytes), {}};
|
||||
for (std::size_t line_index = 0U; line_index < physical_lines.size();
|
||||
++line_index) {
|
||||
const PhysicalLine& line = physical_lines[line_index];
|
||||
const std::string& trimmed_line = line.trimmed_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.line_number, "malformed-keyword",
|
||||
trimmed_line,
|
||||
"A keyword line requires a non-empty keyword name.");
|
||||
}
|
||||
|
||||
KeywordBlock block{UppercaseAscii(keyword_text),
|
||||
line.original_line,
|
||||
{line.original_line},
|
||||
{},
|
||||
{},
|
||||
{source_path, line.line_number}};
|
||||
bool continuation = IsKeywordContinuation(trimmed_line);
|
||||
if (!AppendKeywordParameters(block, fields, 1U, continuation)) {
|
||||
return Failure(source_path, line.line_number, "malformed-keyword",
|
||||
block.canonical_name,
|
||||
"A keyword parameter name cannot be empty.");
|
||||
}
|
||||
|
||||
while (continuation) {
|
||||
++line_index;
|
||||
if (line_index >= physical_lines.size()) {
|
||||
return Failure(source_path, line.line_number, "malformed-keyword",
|
||||
block.canonical_name,
|
||||
"A keyword continuation requires a following line.");
|
||||
}
|
||||
const PhysicalLine& continuation_line = physical_lines[line_index];
|
||||
if (continuation_line.trimmed_line.empty() ||
|
||||
continuation_line.trimmed_line.rfind("**", 0U) == 0U ||
|
||||
continuation_line.trimmed_line.front() == '*') {
|
||||
return Failure(source_path, continuation_line.line_number,
|
||||
"malformed-keyword", block.canonical_name,
|
||||
"A keyword continuation cannot be interrupted.");
|
||||
}
|
||||
const auto continuation_fields =
|
||||
SplitFields(continuation_line.trimmed_line);
|
||||
continuation =
|
||||
IsKeywordContinuation(continuation_line.trimmed_line);
|
||||
if (!AppendKeywordParameters(block, continuation_fields, 0U,
|
||||
continuation)) {
|
||||
return Failure(source_path, continuation_line.line_number,
|
||||
"malformed-keyword", block.canonical_name,
|
||||
"A keyword parameter name cannot be empty.");
|
||||
}
|
||||
block.physical_lines.push_back(continuation_line.original_line);
|
||||
}
|
||||
parsed.blocks.push_back(std::move(block));
|
||||
} else {
|
||||
if (parsed.blocks.empty()) {
|
||||
return Failure(source_path, line.line_number, "orphan-data-line", "",
|
||||
"A data line must follow a keyword line.");
|
||||
}
|
||||
parsed.blocks.back().data.push_back(
|
||||
{SplitFields(line.original_line), {source_path, line.line_number}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result<ParsedInput>::Success(std::move(parsed));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user