fix: enforce phase1 parser subset
This commit is contained in:
+128
-17
@@ -308,6 +308,9 @@ inline KeywordLine parseKeywordLine(const std::string& line) {
|
||||
keyword.name = lower(trim(pieces.front()));
|
||||
for (std::size_t i = 1; i < pieces.size(); ++i) {
|
||||
const std::string piece = trim(pieces[i]);
|
||||
if (piece.empty()) {
|
||||
continue;
|
||||
}
|
||||
const auto eq = piece.find('=');
|
||||
if (eq == std::string::npos) {
|
||||
keyword.flags.insert(lower(piece));
|
||||
@@ -337,9 +340,38 @@ class AbaqusInputParser {
|
||||
std::string current_material_key;
|
||||
KeywordLine current_shell_section;
|
||||
LocalIndex line_number = 0;
|
||||
LocalIndex current_keyword_line = 0;
|
||||
|
||||
auto add_error = [&](const std::string& code, const std::string& message) {
|
||||
result.diagnostics.push_back({Severity::Error, code, message, {file_name, line_number, current.name}});
|
||||
const LocalIndex source_line = current_keyword_line == 0 ? line_number : current_keyword_line;
|
||||
result.diagnostics.push_back({Severity::Error, code, message, {file_name, source_line, current.name}});
|
||||
};
|
||||
auto is_allowed = [](const std::string& value, std::initializer_list<const char*> allowed_values) {
|
||||
return std::any_of(allowed_values.begin(), allowed_values.end(), [&](const char* allowed) {
|
||||
return value == allowed;
|
||||
});
|
||||
};
|
||||
auto reject_unsupported_controls = [&](std::initializer_list<const char*> allowed_parameters,
|
||||
std::initializer_list<const char*> allowed_flags) {
|
||||
for (const auto& [parameter, value] : current.parameters) {
|
||||
(void)value;
|
||||
if (!is_allowed(parameter, allowed_parameters)) {
|
||||
const LocalIndex source_line = current_keyword_line == 0 ? line_number : current_keyword_line;
|
||||
result.diagnostics.push_back({Severity::Error,
|
||||
"FESA-PARSE-UNSUPPORTED-PARAMETER",
|
||||
"Unsupported *" + current.name + " parameter: " + parameter,
|
||||
{file_name, source_line, current.name}});
|
||||
}
|
||||
}
|
||||
for (const std::string& flag : current.flags) {
|
||||
if (!is_allowed(flag, allowed_flags)) {
|
||||
const LocalIndex source_line = current_keyword_line == 0 ? line_number : current_keyword_line;
|
||||
result.diagnostics.push_back({Severity::Error,
|
||||
"FESA-PARSE-UNSUPPORTED-PARAMETER",
|
||||
"Unsupported *" + current.name + " flag: " + flag,
|
||||
{file_name, source_line, current.name}});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
@@ -349,16 +381,52 @@ class AbaqusInputParser {
|
||||
continue;
|
||||
}
|
||||
if (!line.empty() && line.front() == '*') {
|
||||
current = parseKeywordLine(line);
|
||||
if (current.name == "node" || current.name == "element" || current.name == "nset" ||
|
||||
current.name == "elset" || current.name == "elastic" || current.name == "shell section" ||
|
||||
current.name == "boundary" || current.name == "cload" || current.name == "static") {
|
||||
if (current.name == "shell section") {
|
||||
current_shell_section = current;
|
||||
current_keyword_line = line_number;
|
||||
std::string keyword_line = line;
|
||||
while (!keyword_line.empty() && keyword_line.back() == ',') {
|
||||
std::string continuation;
|
||||
if (!std::getline(stream, continuation)) {
|
||||
break;
|
||||
}
|
||||
++line_number;
|
||||
continuation = trim(continuation);
|
||||
if (continuation.empty() || continuation.rfind("**", 0) == 0) {
|
||||
continue;
|
||||
}
|
||||
keyword_line += continuation;
|
||||
}
|
||||
current = parseKeywordLine(keyword_line);
|
||||
if (current.name == "node") {
|
||||
reject_unsupported_controls({}, {});
|
||||
continue;
|
||||
}
|
||||
if (current.name == "element") {
|
||||
reject_unsupported_controls({"type", "elset"}, {});
|
||||
continue;
|
||||
}
|
||||
if (current.name == "nset") {
|
||||
reject_unsupported_controls({"nset"}, {"generate"});
|
||||
continue;
|
||||
}
|
||||
if (current.name == "elset") {
|
||||
reject_unsupported_controls({"elset"}, {"generate"});
|
||||
continue;
|
||||
}
|
||||
if (current.name == "elastic") {
|
||||
reject_unsupported_controls({}, {});
|
||||
continue;
|
||||
}
|
||||
if (current.name == "shell section") {
|
||||
reject_unsupported_controls({"elset", "material"}, {});
|
||||
current_shell_section = current;
|
||||
continue;
|
||||
}
|
||||
if (current.name == "boundary" || current.name == "cload" || current.name == "static") {
|
||||
reject_unsupported_controls({}, {});
|
||||
continue;
|
||||
}
|
||||
if (current.name == "material") {
|
||||
reject_unsupported_controls({"name"}, {});
|
||||
auto name_it = current.parameters.find("name");
|
||||
if (name_it == current.parameters.end() || trim(name_it->second).empty()) {
|
||||
add_error("FESA-PARSE-MATERIAL-NAME", "*Material requires NAME");
|
||||
@@ -376,6 +444,7 @@ class AbaqusInputParser {
|
||||
continue;
|
||||
}
|
||||
if (current.name == "step") {
|
||||
reject_unsupported_controls({"name", "nlgeom"}, {});
|
||||
auto nlgeom = current.parameters.find("nlgeom");
|
||||
if (nlgeom != current.parameters.end() && lower(trim(nlgeom->second)) == "yes") {
|
||||
add_error("FESA-PARSE-UNSUPPORTED-NLGEOM", "NLGEOM=YES is not supported in Phase 1");
|
||||
@@ -389,6 +458,7 @@ class AbaqusInputParser {
|
||||
continue;
|
||||
}
|
||||
if (current.name == "end step") {
|
||||
reject_unsupported_controls({}, {});
|
||||
continue;
|
||||
}
|
||||
add_error("FESA-PARSE-UNSUPPORTED-KEYWORD", "Unsupported keyword: *" + current.name);
|
||||
@@ -433,8 +503,16 @@ class AbaqusInputParser {
|
||||
}
|
||||
|
||||
private:
|
||||
static std::size_t effectiveFieldCount(const std::vector<std::string>& fields) {
|
||||
std::size_t count = fields.size();
|
||||
while (count > 0 && trim(fields[count - 1]).empty()) {
|
||||
--count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void parseNode(const std::vector<std::string>& fields, ParseResult& result, const std::string& file_name, LocalIndex line) {
|
||||
if (fields.size() < 4) {
|
||||
if (effectiveFieldCount(fields) != 4) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-NODE", "*Node data requires id,x,y,z", {file_name, line, "node"}});
|
||||
return;
|
||||
}
|
||||
@@ -464,7 +542,7 @@ class AbaqusInputParser {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-UNSUPPORTED-ELEMENT", "Unsupported element type: " + type_it->second, {file_name, line, "element"}});
|
||||
return;
|
||||
}
|
||||
if (fields.size() < 5) {
|
||||
if (effectiveFieldCount(fields) != 5) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-ELEMENT", "S4 element requires id,n1,n2,n3,n4", {file_name, line, "element"}});
|
||||
return;
|
||||
}
|
||||
@@ -524,7 +602,8 @@ class AbaqusInputParser {
|
||||
static void parseSetData(const std::vector<std::string>& fields, bool generate, std::vector<GlobalId>& output,
|
||||
std::vector<Diagnostic>& diagnostics, const std::string& file_name, LocalIndex line, const std::string& keyword) {
|
||||
if (generate) {
|
||||
if (fields.size() < 3) {
|
||||
const std::size_t field_count = effectiveFieldCount(fields);
|
||||
if (field_count != 3) {
|
||||
diagnostics.push_back({Severity::Error, "FESA-PARSE-GENERATE", "Generated set requires first,last,increment", {file_name, line, keyword}});
|
||||
return;
|
||||
}
|
||||
@@ -540,7 +619,9 @@ class AbaqusInputParser {
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const std::string& field : fields) {
|
||||
const std::size_t field_count = effectiveFieldCount(fields);
|
||||
for (std::size_t i = 0; i < field_count; ++i) {
|
||||
const std::string& field = fields[i];
|
||||
if (trim(field).empty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -558,10 +639,16 @@ class AbaqusInputParser {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-ELASTIC-MATERIAL", "*Elastic must follow *Material", {file_name, line, "elastic"}});
|
||||
return;
|
||||
}
|
||||
if (fields.size() < 2) {
|
||||
const std::size_t field_count = effectiveFieldCount(fields);
|
||||
if (field_count < 2) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-ELASTIC", "*Elastic requires E,nu", {file_name, line, "elastic"}});
|
||||
return;
|
||||
}
|
||||
if (field_count > 2) {
|
||||
result.diagnostics.push_back(
|
||||
{Severity::Error, "FESA-PARSE-ELASTIC-UNSUPPORTED", "Only isotropic E,nu elastic data is supported", {file_name, line, "elastic"}});
|
||||
return;
|
||||
}
|
||||
auto e = parseReal(fields[0]);
|
||||
auto nu = parseReal(fields[1]);
|
||||
if (!e || !nu || *e <= 0.0 || *nu <= -1.0 || *nu >= 0.5) {
|
||||
@@ -579,10 +666,18 @@ class AbaqusInputParser {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-SHELL-SECTION-PARAM", "*Shell Section requires ELSET and MATERIAL", {file_name, line, "shell section"}});
|
||||
return;
|
||||
}
|
||||
if (fields.empty()) {
|
||||
const std::size_t field_count = effectiveFieldCount(fields);
|
||||
if (field_count == 0) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-SHELL-SECTION", "*Shell Section requires thickness", {file_name, line, "shell section"}});
|
||||
return;
|
||||
}
|
||||
if (field_count > 1) {
|
||||
result.diagnostics.push_back({Severity::Error,
|
||||
"FESA-PARSE-SHELL-SECTION-UNSUPPORTED",
|
||||
"Only homogeneous shell thickness data is supported",
|
||||
{file_name, line, "shell section"}});
|
||||
return;
|
||||
}
|
||||
auto thickness = parseReal(fields[0]);
|
||||
if (!thickness || *thickness <= 0.0) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-SHELL-THICKNESS", "Shell thickness must be positive", {file_name, line, "shell section"}});
|
||||
@@ -592,13 +687,21 @@ class AbaqusInputParser {
|
||||
}
|
||||
|
||||
static void parseBoundary(const std::vector<std::string>& fields, ParseResult& result, const std::string& file_name, LocalIndex line) {
|
||||
if (fields.size() < 2) {
|
||||
const std::size_t field_count = effectiveFieldCount(fields);
|
||||
if (field_count < 2) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-BOUNDARY", "*Boundary requires target,first_dof", {file_name, line, "boundary"}});
|
||||
return;
|
||||
}
|
||||
if (field_count > 4) {
|
||||
result.diagnostics.push_back({Severity::Error,
|
||||
"FESA-PARSE-BOUNDARY-UNSUPPORTED",
|
||||
"Only direct zero-valued boundary data is supported",
|
||||
{file_name, line, "boundary"}});
|
||||
return;
|
||||
}
|
||||
auto first = parseInt64(fields[1]);
|
||||
auto last = fields.size() >= 3 && !fields[2].empty() ? parseInt64(fields[2]) : first;
|
||||
auto magnitude = fields.size() >= 4 && !fields[3].empty() ? parseReal(fields[3]) : std::optional<Real>(0.0);
|
||||
auto last = field_count >= 3 && !fields[2].empty() ? parseInt64(fields[2]) : first;
|
||||
auto magnitude = field_count >= 4 && !fields[3].empty() ? parseReal(fields[3]) : std::optional<Real>(0.0);
|
||||
if (!first || !last || !magnitude || !dofFromAbaqus(static_cast<int>(*first)) || !dofFromAbaqus(static_cast<int>(*last)) || *first > *last) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-BOUNDARY-DOF", "Invalid boundary DOF range", {file_name, line, "boundary"}});
|
||||
return;
|
||||
@@ -611,10 +714,18 @@ class AbaqusInputParser {
|
||||
}
|
||||
|
||||
static void parseLoad(const std::vector<std::string>& fields, ParseResult& result, const std::string& file_name, LocalIndex line) {
|
||||
if (fields.size() < 3) {
|
||||
const std::size_t field_count = effectiveFieldCount(fields);
|
||||
if (field_count < 3) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-PARSE-CLOAD", "*Cload requires target,dof,magnitude", {file_name, line, "cload"}});
|
||||
return;
|
||||
}
|
||||
if (field_count > 3) {
|
||||
result.diagnostics.push_back({Severity::Error,
|
||||
"FESA-PARSE-CLOAD-UNSUPPORTED",
|
||||
"Only direct concentrated load data is supported",
|
||||
{file_name, line, "cload"}});
|
||||
return;
|
||||
}
|
||||
auto dof = parseInt64(fields[1]);
|
||||
auto magnitude = parseReal(fields[2]);
|
||||
if (!dof || !magnitude || !dofFromAbaqus(static_cast<int>(*dof))) {
|
||||
|
||||
Reference in New Issue
Block a user