From 7ce8a6ae3a38d717d0c233d3eb53ca30fbdb0910 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Sun, 2 Aug 2026 21:56:50 -0700 Subject: [PATCH 1/2] fix: recognize tab as a geometry-card field separator The integer and real field scanners in parse_geometry_card_line() only terminated a field on space, comma, or NUL. A tab immediately after a field was consumed as part of the field instead of ending it, so decks using tabs to separate geometry-card values (e.g. 86-2-ex9.nec) aborted with "NON-NUMERICAL CHARACTER '' IN INTEGER FIELD" at the tab position. - add geometry_field_separator(), classifying isspace(), comma, and NUL as one shared separator rule - replace both duplicated scan-loop conditions with the new helper so the integer and real paths cannot diverge on what ends a field Signed-off-by: Eric Wheeler --- src/c_geometry.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/c_geometry.cpp b/src/c_geometry.cpp index f334ec6..9f3899b 100644 --- a/src/c_geometry.cpp +++ b/src/c_geometry.cpp @@ -25,7 +25,19 @@ #include #include -c_geometry::c_geometry() +/** + * geometry_field_separator - Determine whether a character separates fields + * @character: Character read from a geometry card + * + * Return: true for NEC commas, whitespace, and the line terminator. + */ +static bool geometry_field_separator(char character) +{ + return (std::isspace(static_cast(character)) != 0) || + (character == ',') || (character == '\0'); +} + +c_geometry::c_geometry() : patch_x1(0,0,0), patch_x2(0,0,0), patch_x3(0,0,0), patch_x4(0,0,0) { n_segments = 0; np = 0; // n_segments is the number of segments @@ -2383,7 +2395,7 @@ void c_geometry::parse_geometry_card_line(const char* line_buf, char *gm, integer_params[i] = atoi( &line_buf[line_idx] ); line_idx--; - while( (line_buf[++line_idx] != ' ') && (line_buf[line_idx] != ',') && (line_buf[line_idx] != '\0') ) + while( !geometry_field_separator(line_buf[++line_idx]) ) { if ( ((line_buf[line_idx] < '0') || (line_buf[line_idx] > '9')) && (line_buf[line_idx] != '+') && (line_buf[line_idx] != '-') ) @@ -2419,7 +2431,7 @@ void c_geometry::parse_geometry_card_line(const char* line_buf, char *gm, real_params[i] = atof( &line_buf[line_idx] ); line_idx--; - while( (line_buf[++line_idx] != ' ') && (line_buf[line_idx] != ',') && (line_buf[line_idx] != '\0') ) + while( !geometry_field_separator(line_buf[++line_idx]) ) { if ( ((line_buf[line_idx] < '0') || (line_buf[line_idx] > '9')) && (line_buf[line_idx] != '.') && (line_buf[line_idx] != '+') && From 299e6395385f6ae6c0ce1c38b0a4d627cf670777 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Sun, 2 Aug 2026 21:57:41 -0700 Subject: [PATCH 2/2] test: add tab-separated geometry field regression test The geometry parser previously rejected tab characters between fields on geometry cards, aborting at the first tab following an integer field. This adds a permanent regression test exercising that failure through the public file parser. - add a GW/GE deck delimited entirely by tabs and parse it through c_geometry::parse_geometry(nec_context *, FILE *), asserting n_segments == 3 to confirm tab termination works in both the integer and real field scanners Signed-off-by: Eric Wheeler --- src/nec2cpp_tb.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/nec2cpp_tb.cpp b/src/nec2cpp_tb.cpp index bdce25c..56f428f 100644 --- a/src/nec2cpp_tb.cpp +++ b/src/nec2cpp_tb.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -336,6 +337,26 @@ TEST_CASE("load_line(istream) drains over-length CM, next line parses as CE", REQUIRE(buf[1] == 'E'); } +TEST_CASE("geometry parser accepts tab-separated fields", "[geometry][parser]") { + // Build one valid wire deck with tabs delimiting every geometry field. + const char deck[] = + "GW\t1\t3\t0\t0\t0\t0\t0\t1\t0.001\n" + "GE\t0\n"; + std::unique_ptr input( + std::tmpfile(), &std::fclose); + REQUIRE(input != nullptr); + REQUIRE(std::fwrite(deck, 1, sizeof(deck) - 1, input.get()) == + sizeof(deck) - 1); + std::rewind(input.get()); + + // Parse through the public file interface and retain the generated segments. + nec_context nec; + nec.initialize(); + c_geometry* geometry = nec.get_geometry(); + REQUIRE_NOTHROW(geometry->parse_geometry(&nec, input.get())); + REQUIRE(geometry->n_segments == 3); +} + TEST_CASE("load_line returns EOF when an over-length line is the last line", "[load_line]") { /* No trailing newline: the drain hits EOF mid-tail. The fill loop's