This commit is contained in:
deeaitch committed 2026-10-14 10:41:17 -04:00
1 parent d87c100398
commit 4ae4e527b8
5 files changed
+138 -10

No files matched your search

+5 -2
View File
@@ -737,8 +737,11 @@ sawtooth traversal. This uses the stored payload length without introducing
an additional CAN or CAN FD payload limit. an additional CAN or CAN FD payload limit.
Factor, offset, minimum, and maximum accept signed decimal numbers, fractions, Factor, offset, minimum, and maximum accept signed decimal numbers, fractions,
and scientific notation. The entire numeric token must be valid; hexadecimal and scientific notation. Conversion is independent of the application's C and
numbers, NaN, infinity, conversion overflow, and conversion underflow are C++ locales: the decimal separator is always a dot (`.`), and comma decimals
and digit grouping are rejected. The entire numeric token must be valid; hexadecimal
numbers, NaN, infinity, conversion overflow, and conversion underflow (including
subnormal values) are
rejected. Minimum must not exceed maximum. Zero and negative factors are allowed. rejected. Minimum must not exceed maximum. Zero and negative factors are allowed.
Basic multiplexing markers `M` and `mN` are supported as described below. Basic multiplexing markers `M` and `mN` are supported as described below.
+10
View File
@@ -10,6 +10,16 @@ existing Git tag; subsequent changes are listed as unreleased.
#### Fixed #### Fixed
- Issue #19: parse signal factor, offset, minimum, and maximum independently of
C/C++ application locales with `std::from_chars`, retaining signed decimals,
scientific notation, complete-token validation, and range/finite checks.
- Added custom C++ and named C comma-decimal locale regressions for valid physical
values and invalid syntax, grouping, overflow/underflow, and reversed ranges.
Documented portable test coverage and Linux locale generation. Verified with
`qmake6`, `make -j4`, and `LOCPATH=/tmp/dbc-issue19-locales make check`: all six
suites passed without skips. The named C locale regression fails with the old
`std::stod` converter.
- Issue #18: frame/signal comments share frame-definition CAN ID validation and - Issue #18: frame/signal comments share frame-definition CAN ID validation and
resolve after definitions are read. Support tabs, escaped quotes/backslashes, resolve after definitions are read. Support tabs, escaped quotes/backslashes,
and multiline text while preserving whitespace and starting source locations. and multiline text while preserving whitespace and starting source locations.
+12 -8
View File
@@ -35,14 +35,18 @@ namespace {
R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)"); R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
if (!std::regex_match(text, number)) if (!std::regex_match(text, number))
throw std::runtime_error(std::string("Invalid ") + field + ": " + text); throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
std::size_t consumed = 0U; // Floating-point from_chars is locale-independent and does not accept
double value; // a leading '+', which the DBC numeric grammar permits.
try { const char *begin = text.data();
value = std::stod(text, &consumed); const char *end = begin + text.size();
} catch (const std::exception &) { if (*begin == '+')
throw std::runtime_error(std::string("Invalid ") + field + ": " + text); ++begin;
} double value = 0.0;
if (consumed != text.size() || !std::isfinite(value)) const std::from_chars_result parsed =
std::from_chars(begin, end, value, std::chars_format::general);
// Keep the existing rejection of conversion underflow into subnormal values.
if (parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value) ||
std::fpclassify(value) == FP_SUBNORMAL)
throw std::runtime_error(std::string("Invalid ") + field + ": " + text); throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
return value; return value;
} }
+16
View File
@@ -86,6 +86,22 @@ extended frames may share a numeric ID, and separate frames may reuse signal nam
Signal tests cover complete syntax and numeric validation, scientific notation, Signal tests cover complete syntax and numeric validation, scientific notation,
finite physical parameters and ordered ranges, spaces and tabs, receiver lists, finite physical parameters and ordered ranges, spaces and tabs, receiver lists,
unsupported multiplexing, and Intel/Motorola declared-payload boundaries. unsupported multiplexing, and Intel/Motorola declared-payload boundaries.
Physical-number tests apply a custom comma-decimal C++ locale and an available
named comma-decimal C numeric locale. They check all four physical fields,
leading signs, fractions, scientific notation, finite boundaries, zero, malformed
numbers, comma/grouped numbers, overflow/underflow, and reversed ranges. Locale
changes are restored after each test. The named C locale case skips if none of
the supported locale names are installed; the custom C++ locale case always runs.
On Linux with locale sources and `localedef`, run both cases without installing a
system locale:
```sh
mkdir -p /tmp/dbc-test-locales
localedef --no-archive -i de_DE -f UTF-8 /tmp/dbc-test-locales/de_DE.UTF-8
LOCPATH=/tmp/dbc-test-locales make check
```
Run `make check` from the build directory as usual.
Value-description tests cover full `VAL_` syntax, whitespace, escaped quotes, Value-description tests cover full `VAL_` syntax, whitespace, escaped quotes,
empty text, exact integer boundaries, duplicate keys within and across statements, empty text, exact integer boundaries, duplicate keys within and across statements,
deferred and normalized frame references, missing targets, error locations, deferred and normalized frame references, missing targets, error locations,
+95
View File
@@ -3,7 +3,9 @@
#include "dbc_parser.h" #include "dbc_parser.h"
#include "dbc_statement_handler.h" #include "dbc_statement_handler.h"
#include "dbc_tree_builder.h" #include "dbc_tree_builder.h"
#include <clocale>
#include <limits> #include <limits>
#include <locale>
#include <QtTest> #include <QtTest>
#include <chrono> #include <chrono>
@@ -28,6 +30,27 @@ struct Fixture {
} }
}; };
class CommaDecimalPunctuation : public std::numpunct<char> {
protected:
char do_decimal_point() const override { return ','; }
char do_thousands_sep() const override { return '.'; }
std::string do_grouping() const override { return "\3"; }
};
class ApplicationLocaleGuard {
public:
ApplicationLocaleGuard()
: previousCppLocale(std::locale()), previousCLocale(std::setlocale(LC_ALL, nullptr)) {}
~ApplicationLocaleGuard() {
std::locale::global(previousCppLocale);
std::setlocale(LC_ALL, previousCLocale.c_str());
}
private:
std::locale previousCppLocale;
std::string previousCLocale;
};
class CountingHandler : public DbcStatementHandler { class CountingHandler : public DbcStatementHandler {
public: public:
inline static int instances = 0; inline static int instances = 0;
@@ -50,6 +73,7 @@ std::string SignalLine(const std::string &start, const std::string &length,
class DbcParserTest : public QObject { class DbcParserTest : public QObject {
Q_OBJECT Q_OBJECT
private: private:
void CheckPhysicalNumbers();
void RejectSignal(const Fixture &fixture, const std::string &line, unsigned payload = 8U) { void RejectSignal(const Fixture &fixture, const std::string &line, unsigned payload = 8U) {
fixture.Write("BO_ 1 Frame: " + std::to_string(payload) + " ECU\n\n" + line + "\n"); fixture.Write("BO_ 1 Frame: " + std::to_string(payload) + " ECU\n\n" + line + "\n");
try { try {
@@ -81,6 +105,8 @@ private Q_SLOTS:
void integration(); void integration();
void invalidSignals(); void invalidSignals();
void validSignals(); void validSignals();
void physicalNumbersCppLocale();
void physicalNumbersCLocale();
void dispatch(); void dispatch();
void independentSessions(); void independentSessions();
void valueDescriptions(); void valueDescriptions();
@@ -824,6 +850,75 @@ void DbcParserTest::validSignals() {
} catch (const std::exception &error) { CHECK(false, error.what()); } } catch (const std::exception &error) { CHECK(false, error.what()); }
} }
void DbcParserTest::CheckPhysicalNumbers() {
Fixture fixture;
struct PhysicalCase {
const char *factor;
const char *offset;
const char *minimum;
const char *maximum;
double expectedFactor;
double expectedOffset;
double expectedMinimum;
double expectedMaximum;
};
const PhysicalCase valid[] = {
{"+1.25", "-0.5", "-2.75", "255.5", 1.25, -0.5, -2.75, 255.5},
{"+.5e+1", "-2.5E-1", "-1.", "+2e2", 5.0, -0.25, -1.0, 200.0},
{"0e-999", "+0.", "-.0", ".0e+999", 0.0, 0.0, 0.0, 0.0},
{"1.7976931348623157e308", "2.2250738585072014e-308", "-1e308", "+1e308",
std::numeric_limits<double>::max(), std::numeric_limits<double>::min(), -1e308, 1e308}};
for (const PhysicalCase &test: valid) {
fixture.Write("BO_ 1 Frame: 1 ECU\n" +
SignalLine("0", "8", "1+", test.factor, test.offset, test.minimum, test.maximum) + "\n");
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
QVERIFY(!result.HasErrors());
QVERIFY(result.diagnostics.empty());
QCOMPARE(result.database.frames.size(), std::size_t(1));
const SignalInfo &signal = result.database.frames[0].signals[0];
QCOMPARE(signal.factor, test.expectedFactor);
QCOMPARE(signal.offset, test.expectedOffset);
QCOMPARE(signal.minimum, test.expectedMinimum);
QCOMPARE(signal.maximum, test.expectedMaximum);
}
const char *invalid[] = {"1,5", "1.234,5", "1,234.5", "1junk", "nan", "inf", "-inf",
"1e999", "1e-999", "1e-320", "-1e-320", "0x1p2", "1e", "1.2.3"};
for (const char *value: invalid) {
RejectSignal(fixture, SignalLine("0", "8", "1+", value, "0", "0", "255"));
RejectSignal(fixture, SignalLine("0", "8", "1+", "1", value, "0", "255"));
RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", value, "255"));
RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value));
}
RejectSignal(fixture, SignalLine("0", "8", "1+", "1.25", "0", "2.5", "1.5"));
}
void DbcParserTest::physicalNumbersCppLocale() {
const ApplicationLocaleGuard guard;
std::locale::global(std::locale(std::locale::classic(), new CommaDecimalPunctuation));
QCOMPARE(std::use_facet<std::numpunct<char>>(std::locale()).decimal_point(), ',');
CheckPhysicalNumbers();
QCOMPARE(std::use_facet<std::numpunct<char>>(std::locale()).decimal_point(), ',');
}
void DbcParserTest::physicalNumbersCLocale() {
const ApplicationLocaleGuard guard;
bool found = false;
const char *names[] = {"de_DE.UTF-8", "de_DE.utf8", "fr_FR.UTF-8", "fr_FR.utf8",
"German_Germany.1252", "French_France.1252"};
for (const char *name: names) {
if (std::setlocale(LC_NUMERIC, name) && std::string(std::localeconv()->decimal_point) == ",") {
found = true;
break;
}
}
if (!found)
QSKIP("No named comma-decimal C locale is installed; generate de_DE.UTF-8 and set LOCPATH.");
QCOMPARE(std::string(std::localeconv()->decimal_point), std::string(","));
CheckPhysicalNumbers();
QCOMPARE(std::string(std::localeconv()->decimal_point), std::string(","));
}
void DbcParserTest::dispatch() { void DbcParserTest::dispatch() {
CountingHandler::instances = 0; CountingHandler::instances = 0;
CountingHandler::calls = 0; CountingHandler::calls = 0;