diff --git a/README.md b/README.md index 92c9caa..42ebfec 100644 --- a/README.md +++ b/README.md @@ -868,6 +868,9 @@ parent selector; conflicting parent references are rejected. Multiple independen root selectors are supported. Statements and selectors may precede or follow their targets. Decimal values retain the full `uint64_t` range and must fit the parent's bit width. Ranges are stored without expanding individual values. +`SG_MUL_VAL_` headers, decimal bounds, and range lists are scanned iteratively, +without regular expressions or a new statement-length limit. Long valid lists +are preserved; malformed ranges and trailing text produce source-located errors. Malformed ranges, unresolved references, signed selectors, ambiguous implicit selectors, and dependency cycles produce `InvalidMultiplexing` diagnostics. Statement errors retain the `SG_MUL_VAL_` source line; graph and overlap errors @@ -1239,7 +1242,10 @@ Each statement must occupy one physical line, contain at least one integer/text pair, and end with a semicolon. Spaces and tabs are accepted between fields; descriptions may contain whitespace, semicolons, and empty text. Inside quoted text, escape a quote or a backslash with a preceding backslash; other escapes -are rejected. +are rejected. Headers, quoted descriptions, and pairs are scanned iteratively, +without regular expressions or repeatedly removing the parsed prefix. Long +valid descriptions and lists are preserved without a new length limit; malformed +input returns the same source-located diagnostics as short statements. Keys are decimal integers in `INT64_MIN..UINT64_MAX`, with an optional minus sign; plus signs, fractions, hexadecimal numbers, exponents, and overflow are rejected. Keys never pass through `double`. @@ -1397,8 +1403,8 @@ When building a consumer separately, pass ### Local core regression checks Run `bash tests/run_core_ci.sh release` or `bash tests/run_core_ci.sh sanitize` -to build the core archive with Qt 6.8.2, run all six core QtTest suites (parser, -decoder, coverage, lifetime, validation, and pipeline/builder integration), and +to build the core archive with Qt 6.8.2, run all seven core QtTest suites (parser, +statement robustness, decoder, coverage, lifetime, validation, and pipeline/builder integration), and run both the qmake and standalone Qt-free tree tests. The sanitizer mode uses AddressSanitizer and UndefinedBehaviorSanitizer, with leak detection enabled by default and fatal sanitizer errors. @@ -1572,7 +1578,8 @@ floating-point values. Default application skips unrelated scopes, CAN ID normalization uses the validated identifier mask, and signal layout validation owns the length checks. Statement handlers continue to use `TypeFactory`. Core, adapter, example, and test C++ sources use explicit return types and contain -no lambdas. Public metadata and parser-state comments use Doxygen descriptions. +no lambdas. DBC statement parsing uses iterative scanners rather than regular +expressions. Public metadata and parser-state comments use Doxygen descriptions. See [tests/README.md](tests/README.md#extended-validation) for attribute boundary regressions, sanitizer commands, and the latest validation results. diff --git a/changelog.md b/changelog.md index 0d0a92b..c7824fc 100644 --- a/changelog.md +++ b/changelog.md @@ -34,6 +34,18 @@ existing Git tag; subsequent changes are listed as unreleased. #### Fixed +- Replace the remaining `VAL_` and `SG_MUL_VAL_` regular expressions with a + shared forward-only statement reader. Long descriptions and range lists no + longer exhaust the stack. Preserve quoted escapes, exact integer values, + deferred references, duplicate checks, and source-located diagnostics without + imposing a new statement-length limit. Avoid repeatedly erasing `VAL_` prefixes. +- Add and register a dedicated statement robustness suite for 50,000-character + fields, 10,000 value pairs/ranges, malformed long inputs, both parser APIs, + metadata preservation, value lookup, and active/inactive branch decoding. + Release and ASan/UBSan builds with the optional Qt adapter each pass 2,449 + core checks, seven model checks, and the qmake tree test. One locale case + skips; leak checking remains disabled due to ptrace. + - Reject all `SIG_VALTYPE_` declarations with a source-located `UnsupportedSignalType` error instead of silently decoding their payload as integers. Both parser APIs reject the unsupported operator, including type 0; diff --git a/core/dbc_statement_handler.cpp b/core/dbc_statement_handler.cpp index 2e87a93..7d6ff80 100644 --- a/core/dbc_statement_handler.cpp +++ b/core/dbc_statement_handler.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -143,15 +142,99 @@ namespace { return key; } - std::string UnescapeDescription(const std::string "ed) { - std::string description; - for (std::size_t i = 0; i < quoted.size(); ++i) { - if (quoted[i] == '\\') - ++i; - description += quoted[i]; + // VAL_ and SG_MUL_VAL_ share a small, forward-only statement reader. + class StatementTokens { + public: + StatementTokens(const std::string &text, const std::string &keyword) + : text(text), keyword(keyword) { + if (Word() != keyword) + Fail(); + RequireSpace(); } - return description; - } + + bool SkipSpaces() { + const std::size_t begin = position; + while (position < text.size() && (text[position] == ' ' || text[position] == '\t')) + ++position; + return position != begin; + } + + void RequireSpace() { + if (!SkipSpaces()) + Fail(); + } + + bool Consume(char character) { + if (position == text.size() || text[position] != character) + return false; + ++position; + return true; + } + + void Expect(char character) { + SkipSpaces(); + if (!Consume(character)) + Fail(); + } + + std::string Word() { + const std::size_t begin = position; + while (position < text.size() && text[position] != ' ' && text[position] != '\t' && + text[position] != ';' && text[position] != '"') + ++position; + if (position == begin) + Fail(); + return text.substr(begin, position - begin); + } + + std::uint64_t Unsigned() { + SkipSpaces(); + const std::size_t begin = position; + while (position < text.size() && text[position] >= '0' && text[position] <= '9') + ++position; + std::uint64_t value = 0U; + const std::from_chars_result parsed = + std::from_chars(text.data() + begin, text.data() + position, value); + if (position == begin || parsed.ec != std::errc() || parsed.ptr != text.data() + position) + Fail(); + return value; + } + + std::string Description() { + if (!Consume('"')) + Fail(); + std::string description; + while (position < text.size()) { + char character = text[position++]; + if (character == '"') + return description; + if (character == '\\') { + if (position == text.size()) + Fail(); + character = text[position++]; + if (character != '"' && character != '\\') + Fail(); + } + description += character; + } + Fail(); + } + + void End() { + SkipSpaces(); + if (position != text.size()) + Fail(); + } + + private: + [[noreturn]] void Fail() const { + throw std::runtime_error("Malformed " + keyword + " statement."); + } + + const std::string &text; + const std::string keyword; + std::size_t position = 0U; + }; FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended, const std::string &statement = "VAL_") { FrameInfo *frame = nullptr; @@ -438,33 +521,28 @@ bool DbcStatementDispatcher::Dispatch(const std::string &line, } void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const { - static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)"); - static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc"); - std::smatch fields; - if (!std::regex_match(line, fields, header)) - throw std::runtime_error("Malformed or unsupported VAL_ statement."); - const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits::max(), "VAL_ CAN ID"); - if ((rawId & 0x60000000U) != 0U) - throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range."); + StatementTokens tokens(line, "VAL_"); + const std::uint32_t rawId = ParseCanId(tokens.Word()); + tokens.RequireSpace(); PendingValueDescriptions pending; NormalizeCanId(rawId, pending.canId, pending.isExtended); - pending.signalName = fields[2].str(); + pending.signalName = tokens.Word(); + tokens.RequireSpace(); pending.lineNumber = context.lineNumber; - std::string rest = fields[3].str(); - while (rest != ";") { - if (!std::regex_search(rest, fields, pair)) - throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon."); - const std::string number = fields[1].str(); + while (!tokens.Consume(';')) { + const std::string number = tokens.Word(); const RawSignalValue key = ParseValueKey(number); - const std::string description = UnescapeDescription(fields[2].str()); + tokens.RequireSpace(); + const std::string description = tokens.Description(); if (!pending.descriptions.emplace(key, description).second) throw std::runtime_error("Duplicate VAL_ value key: " + number); - const std::size_t consumed = static_cast(fields.length()); - rest.erase(0, consumed); - if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t') + const bool separated = tokens.SkipSpaces(); + if (tokens.Consume(';')) + break; + if (!separated) throw std::runtime_error("Missing whitespace between VAL_ pairs."); - rest = dbc_detail::TrimText(rest); } + tokens.End(); if (pending.descriptions.empty()) throw std::runtime_error("VAL_ requires at least one value/description pair."); context.pendingValues.push_back(std::move(pending)); @@ -487,35 +565,29 @@ void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) { } void DbcMultiplexingHandler::Handle(const std::string &line, DbcParseContext &context) const { - static const std::regex syntax(R"(^SG_MUL_VAL_[ \t]+([0-9]+)[ \t]+([^ \t;]+)[ \t]+([^ \t;]+)[ \t]+(.+);[ \t]*$)"); - static const std::regex rangeSyntax(R"(^[ \t]*([0-9]+)[ \t]*-[ \t]*([0-9]+)[ \t]*$)"); - std::smatch fields; - if (!std::regex_match(line, fields, syntax)) - throw std::runtime_error("Malformed SG_MUL_VAL_ statement."); + StatementTokens tokens(line, "SG_MUL_VAL_"); PendingMultiplexing pending; - NormalizeCanId(ParseCanId(fields[1].str()), pending.canId, pending.isExtended); - pending.signalName = fields[2].str(); - pending.selectorName = fields[3].str(); + NormalizeCanId(ParseCanId(tokens.Word()), pending.canId, pending.isExtended); + tokens.RequireSpace(); + pending.signalName = tokens.Word(); + tokens.RequireSpace(); + pending.selectorName = tokens.Word(); + tokens.RequireSpace(); pending.lineNumber = context.lineNumber; - const std::string list = fields[4].str(); - std::size_t begin = 0U; for (;;) { - const std::size_t comma = list.find(',', begin); - const std::string item = list.substr(begin, comma == std::string::npos ? comma : comma - begin); - if (!std::regex_match(item, fields, rangeSyntax)) - throw std::runtime_error("Malformed multiplex range."); MultiplexRange range; - const std::string lower = fields[1].str(); - const std::string upper = fields[2].str(); - const std::from_chars_result low = std::from_chars(lower.data(), lower.data() + lower.size(), range.minimum); - const std::from_chars_result high = std::from_chars(upper.data(), upper.data() + upper.size(), range.maximum); - if (low.ec != std::errc() || high.ec != std::errc() || range.minimum > range.maximum) + range.minimum = tokens.Unsigned(); + tokens.Expect('-'); + range.maximum = tokens.Unsigned(); + if (range.minimum > range.maximum) throw std::runtime_error("Invalid multiplex range."); pending.ranges.push_back(range); - if (comma == std::string::npos) + tokens.SkipSpaces(); + if (tokens.Consume(';')) break; - begin = comma + 1U; + tokens.Expect(','); } + tokens.End(); context.pendingMultiplexing.push_back(std::move(pending)); } diff --git a/docs/html/classDbcCommentHandler.html b/docs/html/classDbcCommentHandler.html index 0bcec11..fef72f0 100644 --- a/docs/html/classDbcCommentHandler.html +++ b/docs/html/classDbcCommentHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 339 of file dbc_statement_handler.cpp.

+

Definition at line 422 of file dbc_statement_handler.cpp.

References PendingComment::canId, PendingComment::comment, PendingComment::isExtended, PendingComment::isSignal, PendingComment::lineNumber, DbcParseContext::lineNumber, DbcParseContext::pendingComments, and PendingComment::signalName.

diff --git a/docs/html/classDbcFrameHandler.html b/docs/html/classDbcFrameHandler.html index 87ed90a..2373caa 100644 --- a/docs/html/classDbcFrameHandler.html +++ b/docs/html/classDbcFrameHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 311 of file dbc_statement_handler.cpp.

+

Definition at line 394 of file dbc_statement_handler.cpp.

References FrameInfo::canId, DbcParseContext::currentFrame, DbcParseContext::database, DbcParseContext::enableJ1939, DbcDatabase::frames, FrameInfo::hasPgn, FrameInfo::isExtended, FrameInfo::pgn, and DbcParseContext::signalLines.

diff --git a/docs/html/classDbcHeaderHandler.html b/docs/html/classDbcHeaderHandler.html index ba988cb..e1e4a71 100644 --- a/docs/html/classDbcHeaderHandler.html +++ b/docs/html/classDbcHeaderHandler.html @@ -155,7 +155,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 426 of file dbc_statement_handler.cpp.

+

Definition at line 509 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcMultiplexingHandler.html b/docs/html/classDbcMultiplexingHandler.html index 91c3d6d..8910b1f 100644 --- a/docs/html/classDbcMultiplexingHandler.html +++ b/docs/html/classDbcMultiplexingHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 489 of file dbc_statement_handler.cpp.

+

Definition at line 567 of file dbc_statement_handler.cpp.

References DbcParseContext::lineNumber.

diff --git a/docs/html/classDbcParser.html b/docs/html/classDbcParser.html index 844833b..b204782 100644 --- a/docs/html/classDbcParser.html +++ b/docs/html/classDbcParser.html @@ -219,7 +219,7 @@ Public Member Functions

Definition at line 105 of file dbc_parser.cpp.

-

References DbcParseResult::database, DbcDiagnosticError::diagnostic, DbcParseResult::diagnostics, DbcStatementDispatcher::Dispatch(), and DbcParserOptions::enableJ1939.

+

References DbcParseResult::database, DbcDiagnosticError::diagnostic, DbcParseResult::diagnostics, DbcStatementDispatcher::Dispatch(), and DbcParserOptions::enableJ1939.

Referenced by main(), and ParseFile().

diff --git a/docs/html/classDbcSignalHandler.html b/docs/html/classDbcSignalHandler.html index 1f6198c..6b6a6a1 100644 --- a/docs/html/classDbcSignalHandler.html +++ b/docs/html/classDbcSignalHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 325 of file dbc_statement_handler.cpp.

+

Definition at line 408 of file dbc_statement_handler.cpp.

References DbcParseContext::currentFrame, FrameInfo::dlc, SignalInfo::isLittleEndian, SignalInfo::length, DbcParseContext::lineNumber, FrameInfo::name, SignalInfo::name, DbcParseContext::signalLines, and SignalInfo::startBit.

diff --git a/docs/html/classDbcStatementDispatcher.html b/docs/html/classDbcStatementDispatcher.html index 7191061..b87dee4 100644 --- a/docs/html/classDbcStatementDispatcher.html +++ b/docs/html/classDbcStatementDispatcher.html @@ -133,7 +133,7 @@ Public Member Functions

Register supported statement handlers through TypeFactory.

-

Definition at line 403 of file dbc_statement_handler.cpp.

+

Definition at line 486 of file dbc_statement_handler.cpp.

@@ -199,7 +199,7 @@ template<class Handler >

Returns false for an unregistered keyword; input must be trimmed.

-

Definition at line 428 of file dbc_statement_handler.cpp.

+

Definition at line 511 of file dbc_statement_handler.cpp.

Referenced by DbcParser::ParseFileWithDiagnostics().

diff --git a/docs/html/classDbcUnsupportedSignalTypeHandler.html b/docs/html/classDbcUnsupportedSignalTypeHandler.html index c44ce69..5631287 100644 --- a/docs/html/classDbcUnsupportedSignalTypeHandler.html +++ b/docs/html/classDbcUnsupportedSignalTypeHandler.html @@ -155,7 +155,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 422 of file dbc_statement_handler.cpp.

+

Definition at line 505 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcValueDescriptionHandler.html b/docs/html/classDbcValueDescriptionHandler.html index 5101ad9..bdef38d 100644 --- a/docs/html/classDbcValueDescriptionHandler.html +++ b/docs/html/classDbcValueDescriptionHandler.html @@ -158,7 +158,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 440 of file dbc_statement_handler.cpp.

+

Definition at line 523 of file dbc_statement_handler.cpp.

References PendingValueDescriptions::canId, PendingValueDescriptions::descriptions, PendingValueDescriptions::isExtended, PendingValueDescriptions::lineNumber, DbcParseContext::lineNumber, DbcParseContext::pendingValues, and PendingValueDescriptions::signalName.

diff --git a/docs/html/dbc__coverage__test_8cpp_source.html b/docs/html/dbc__coverage__test_8cpp_source.html index c6e458d..5e6eb74 100644 --- a/docs/html/dbc__coverage__test_8cpp_source.html +++ b/docs/html/dbc__coverage__test_8cpp_source.html @@ -993,7 +993,7 @@ $(document).ready(function(){initNavTree('dbc__coverage__test_8cpp_source.html',
893}
894#include "dbc_coverage_test.moc"
Defer frame and signal comments for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:87
@@ -1004,7 +1004,7 @@ $(document).ready(function(){initNavTree('dbc__coverage__test_8cpp_source.html',
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Defer raw value descriptions for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
const std::vector< DecodeFrame > & GetFrames() const
diff --git a/docs/html/dbc__parser_8cpp_source.html b/docs/html/dbc__parser_8cpp_source.html index 6d2bf39..70fe704 100644 --- a/docs/html/dbc__parser_8cpp_source.html +++ b/docs/html/dbc__parser_8cpp_source.html @@ -284,7 +284,7 @@ $(document).ready(function(){initNavTree('dbc__parser_8cpp_source.html',''); ini
DbcParseResult ParseFileWithDiagnostics(const std::string &filePath) const
Parse with warnings and a structured first error instead of throwing.
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
DBC file parser and interpretation options.
Parsed DBC content stored in a simple internal form.
Diagnostic message and physical source location.
diff --git a/docs/html/dbc__parser__test_8cpp_source.html b/docs/html/dbc__parser__test_8cpp_source.html index c145eb7..b51e584 100644 --- a/docs/html/dbc__parser__test_8cpp_source.html +++ b/docs/html/dbc__parser__test_8cpp_source.html @@ -1780,7 +1780,7 @@ $(document).ready(function(){initNavTree('dbc__parser__test_8cpp_source.html',''
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
void RegisterType(const std::string &keyword)
Register or replace a handler and create its reusable instance.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
Interface for processing a registered DBC statement.
virtual void Handle(const std::string &line, DbcParseContext &context) const =0
Process a trimmed statement using state belonging to this parse.
Builds a simple tree from parsed DBC database.
diff --git a/docs/html/dbc__statement__handler_8cpp_source.html b/docs/html/dbc__statement__handler_8cpp_source.html index 333d96b..55854a9 100644 --- a/docs/html/dbc__statement__handler_8cpp_source.html +++ b/docs/html/dbc__statement__handler_8cpp_source.html @@ -107,620 +107,692 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8cpp_source.ht
7#include <charconv>
8#include <cmath>
9#include <limits>
-
10#include <regex>
-
11#include <sstream>
-
12#include <stdexcept>
-
13
-
14namespace {
-
15 std::uint32_t ParseDecimal(const std::string &text,
-
16 std::uint32_t maximum,
-
17 const char *field) {
-
18 std::uint32_t value = 0U;
-
19 const std::from_chars_result parsed = std::from_chars(text.data(), text.data() + text.size(), value);
-
20 if (text.empty() || parsed.ec != std::errc() ||
-
21 parsed.ptr != text.data() + text.size() || value > maximum)
-
22 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
23 return value;
-
24 }
-
25
-
26 std::uint32_t ParseCanId(const std::string &text) {
-
27 const std::uint32_t rawId = ParseDecimal(
-
28 text, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
-
29 // Only bit 31 may accompany the actual 29-bit identifier.
-
30 if ((rawId & 0x60000000U) != 0U)
-
31 throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
-
32 return rawId;
-
33 }
-
34
-
35 double ParsePhysicalValue(const std::string &text, const char *field) {
-
36 if (text.empty() || (text[0] == '+' &&
-
37 (text.size() == 1U || text[1] == '+' || text[1] == '-')))
-
38 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
39 // Floating-point from_chars is locale-independent and does not accept
-
40 // a leading '+', which the DBC numeric grammar permits.
-
41 const char *begin = text.data();
-
42 const char *end = begin + text.size();
-
43 if (*begin == '+')
-
44 ++begin;
-
45 double value = 0.0;
-
46 const std::from_chars_result parsed =
-
47 std::from_chars(begin, end, value, std::chars_format::general);
-
48 // Keep the existing rejection of conversion underflow into subnormal values.
-
49 if (parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value) ||
-
50 std::fpclassify(value) == FP_SUBNORMAL)
-
51 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
52 return value;
-
53 }
-
54
-
55 // Iterative scanning keeps stack use independent of the statement length.
-
56 class SignalTokens {
-
57 public:
-
58 SignalTokens(const std::string &text, std::size_t position) : text(text), position(position) {}
-
59
-
60 void SkipSpaces() {
-
61 while (position < text.size() && (text[position] == ' ' || text[position] == '\t'))
-
62 ++position;
-
63 }
-
64
-
65 char Character() {
-
66 if (position == text.size())
-
67 throw std::runtime_error("Incomplete SG_ signal definition.");
-
68 return text[position++];
-
69 }
-
70
-
71 void Expect(char expected) {
-
72 SkipSpaces();
-
73 if (Character() != expected)
-
74 throw std::runtime_error("Malformed SG_ signal definition.");
-
75 }
-
76
-
77 std::string Until(char delimiter) {
-
78 const std::size_t end = text.find(delimiter, position);
-
79 if (end == std::string::npos)
-
80 throw std::runtime_error("Missing delimiter in SG_ signal definition.");
-
81 const std::string value = text.substr(position, end - position);
-
82 position = end + 1U;
-
83 return value;
-
84 }
-
85
-
86 std::vector<std::string> Receivers() {
-
87 if (position == text.size() || (text[position] != ' ' && text[position] != '\t'))
-
88 throw std::runtime_error("Missing receiver separator in SG_ signal definition.");
-
89 std::vector<std::string> receivers;
-
90 for (;;) {
-
91 const std::size_t end = text.find(',', position);
-
92 const std::string receiver = dbc_detail::TrimText(text.substr(position, end - position));
-
93 if (receiver.empty() || receiver.find_first_of(" \t\r\n") != std::string::npos)
-
94 throw std::runtime_error("Invalid SG_ receiver list.");
-
95 receivers.push_back(receiver);
-
96 if (end == std::string::npos)
-
97 return receivers;
-
98 position = end + 1U;
-
99 }
-
100 }
-
101
-
102 private:
-
103 const std::string &text;
-
104 std::size_t position = 0U;
-
105 };
-
106
-
107 Multiplexing ParseMultiplexingMarker(const std::string &marker) {
-
108 Multiplexing result;
-
109 if (marker == "M")
-
110 result.role = MultiplexRole::Selector;
-
111 else if (!marker.empty()) {
-
112 if (marker[0] != 'm')
-
113 throw std::runtime_error("Unsupported multiplexing marker.");
-
114 const bool nested = marker.back() == 'M';
-
115 const std::string value = marker.substr(1U, marker.size() - (nested ? 2U : 1U));
-
116 const std::from_chars_result parsed =
-
117 std::from_chars(value.data(), value.data() + value.size(), result.value);
-
118 if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
-
119 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
-
120 result.role = nested ? MultiplexRole::BranchSelector : MultiplexRole::Branch;
-
121 }
-
122
-
123 return result;
-
124 }
-
125
-
126 RawSignalValue ParseValueKey(const std::string &number) {
-
127 RawSignalValue key;
-
128 if (!number.empty() && number[0] == '-') {
-
129 std::int64_t value = 0;
-
130 const std::from_chars_result parsed =
-
131 std::from_chars(number.data(), number.data() + number.size(), value);
-
132 if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
-
133 throw std::runtime_error("Invalid VAL_ integer: " + number);
-
134 key = NormalizeValueKey(RawSignalValue(value));
-
135 } else {
-
136 std::uint64_t value = 0;
-
137 const std::from_chars_result parsed =
-
138 std::from_chars(number.data(), number.data() + number.size(), value);
-
139 if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
-
140 throw std::runtime_error("Invalid VAL_ integer: " + number);
-
141 key = value;
-
142 }
-
143 return key;
-
144 }
-
145
-
146 std::string UnescapeDescription(const std::string &quoted) {
-
147 std::string description;
-
148 for (std::size_t i = 0; i < quoted.size(); ++i) {
-
149 if (quoted[i] == '\\')
-
150 ++i;
-
151 description += quoted[i];
-
152 }
-
153 return description;
-
154 }
-
155
-
156 FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended, const std::string &statement = "VAL_") {
-
157 FrameInfo *frame = nullptr;
-
158 for (FrameInfo &candidate : database.frames) {
-
159 if (candidate.canId == canId && candidate.isExtended == isExtended) {
-
160 if (frame)
-
161 throw std::runtime_error("Ambiguous " + statement + " frame reference.");
-
162 frame = &candidate;
-
163 }
-
164 }
-
165 if (!frame)
-
166 throw std::runtime_error("Unresolved " + statement + " frame reference.");
-
167 return frame;
-
168 }
-
169
-
170 SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName, const std::string &statement = "VAL_") {
-
171 SignalInfo *signal = nullptr;
-
172 for (SignalInfo &candidate : frame.signals) {
-
173 if (candidate.name == signalName) {
-
174 if (signal)
-
175 throw std::runtime_error("Ambiguous " + statement + " signal reference: " + signalName);
-
176 signal = &candidate;
-
177 }
+
10#include <sstream>
+
11#include <stdexcept>
+
12
+
13namespace {
+
14 std::uint32_t ParseDecimal(const std::string &text,
+
15 std::uint32_t maximum,
+
16 const char *field) {
+
17 std::uint32_t value = 0U;
+
18 const std::from_chars_result parsed = std::from_chars(text.data(), text.data() + text.size(), value);
+
19 if (text.empty() || parsed.ec != std::errc() ||
+
20 parsed.ptr != text.data() + text.size() || value > maximum)
+
21 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
22 return value;
+
23 }
+
24
+
25 std::uint32_t ParseCanId(const std::string &text) {
+
26 const std::uint32_t rawId = ParseDecimal(
+
27 text, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
+
28 // Only bit 31 may accompany the actual 29-bit identifier.
+
29 if ((rawId & 0x60000000U) != 0U)
+
30 throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
+
31 return rawId;
+
32 }
+
33
+
34 double ParsePhysicalValue(const std::string &text, const char *field) {
+
35 if (text.empty() || (text[0] == '+' &&
+
36 (text.size() == 1U || text[1] == '+' || text[1] == '-')))
+
37 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
38 // Floating-point from_chars is locale-independent and does not accept
+
39 // a leading '+', which the DBC numeric grammar permits.
+
40 const char *begin = text.data();
+
41 const char *end = begin + text.size();
+
42 if (*begin == '+')
+
43 ++begin;
+
44 double value = 0.0;
+
45 const std::from_chars_result parsed =
+
46 std::from_chars(begin, end, value, std::chars_format::general);
+
47 // Keep the existing rejection of conversion underflow into subnormal values.
+
48 if (parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value) ||
+
49 std::fpclassify(value) == FP_SUBNORMAL)
+
50 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
51 return value;
+
52 }
+
53
+
54 // Iterative scanning keeps stack use independent of the statement length.
+
55 class SignalTokens {
+
56 public:
+
57 SignalTokens(const std::string &text, std::size_t position) : text(text), position(position) {}
+
58
+
59 void SkipSpaces() {
+
60 while (position < text.size() && (text[position] == ' ' || text[position] == '\t'))
+
61 ++position;
+
62 }
+
63
+
64 char Character() {
+
65 if (position == text.size())
+
66 throw std::runtime_error("Incomplete SG_ signal definition.");
+
67 return text[position++];
+
68 }
+
69
+
70 void Expect(char expected) {
+
71 SkipSpaces();
+
72 if (Character() != expected)
+
73 throw std::runtime_error("Malformed SG_ signal definition.");
+
74 }
+
75
+
76 std::string Until(char delimiter) {
+
77 const std::size_t end = text.find(delimiter, position);
+
78 if (end == std::string::npos)
+
79 throw std::runtime_error("Missing delimiter in SG_ signal definition.");
+
80 const std::string value = text.substr(position, end - position);
+
81 position = end + 1U;
+
82 return value;
+
83 }
+
84
+
85 std::vector<std::string> Receivers() {
+
86 if (position == text.size() || (text[position] != ' ' && text[position] != '\t'))
+
87 throw std::runtime_error("Missing receiver separator in SG_ signal definition.");
+
88 std::vector<std::string> receivers;
+
89 for (;;) {
+
90 const std::size_t end = text.find(',', position);
+
91 const std::string receiver = dbc_detail::TrimText(text.substr(position, end - position));
+
92 if (receiver.empty() || receiver.find_first_of(" \t\r\n") != std::string::npos)
+
93 throw std::runtime_error("Invalid SG_ receiver list.");
+
94 receivers.push_back(receiver);
+
95 if (end == std::string::npos)
+
96 return receivers;
+
97 position = end + 1U;
+
98 }
+
99 }
+
100
+
101 private:
+
102 const std::string &text;
+
103 std::size_t position = 0U;
+
104 };
+
105
+
106 Multiplexing ParseMultiplexingMarker(const std::string &marker) {
+
107 Multiplexing result;
+
108 if (marker == "M")
+
109 result.role = MultiplexRole::Selector;
+
110 else if (!marker.empty()) {
+
111 if (marker[0] != 'm')
+
112 throw std::runtime_error("Unsupported multiplexing marker.");
+
113 const bool nested = marker.back() == 'M';
+
114 const std::string value = marker.substr(1U, marker.size() - (nested ? 2U : 1U));
+
115 const std::from_chars_result parsed =
+
116 std::from_chars(value.data(), value.data() + value.size(), result.value);
+
117 if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
+
118 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
+
119 result.role = nested ? MultiplexRole::BranchSelector : MultiplexRole::Branch;
+
120 }
+
121
+
122 return result;
+
123 }
+
124
+
125 RawSignalValue ParseValueKey(const std::string &number) {
+
126 RawSignalValue key;
+
127 if (!number.empty() && number[0] == '-') {
+
128 std::int64_t value = 0;
+
129 const std::from_chars_result parsed =
+
130 std::from_chars(number.data(), number.data() + number.size(), value);
+
131 if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
+
132 throw std::runtime_error("Invalid VAL_ integer: " + number);
+
133 key = NormalizeValueKey(RawSignalValue(value));
+
134 } else {
+
135 std::uint64_t value = 0;
+
136 const std::from_chars_result parsed =
+
137 std::from_chars(number.data(), number.data() + number.size(), value);
+
138 if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
+
139 throw std::runtime_error("Invalid VAL_ integer: " + number);
+
140 key = value;
+
141 }
+
142 return key;
+
143 }
+
144
+
145 // VAL_ and SG_MUL_VAL_ share a small, forward-only statement reader.
+
146 class StatementTokens {
+
147 public:
+
148 StatementTokens(const std::string &text, const std::string &keyword)
+
149 : text(text), keyword(keyword) {
+
150 if (Word() != keyword)
+
151 Fail();
+
152 RequireSpace();
+
153 }
+
154
+
155 bool SkipSpaces() {
+
156 const std::size_t begin = position;
+
157 while (position < text.size() && (text[position] == ' ' || text[position] == '\t'))
+
158 ++position;
+
159 return position != begin;
+
160 }
+
161
+
162 void RequireSpace() {
+
163 if (!SkipSpaces())
+
164 Fail();
+
165 }
+
166
+
167 bool Consume(char character) {
+
168 if (position == text.size() || text[position] != character)
+
169 return false;
+
170 ++position;
+
171 return true;
+
172 }
+
173
+
174 void Expect(char character) {
+
175 SkipSpaces();
+
176 if (!Consume(character))
+
177 Fail();
178 }
-
179 if (!signal)
-
180 throw std::runtime_error("Unresolved " + statement + " signal reference: " + signalName);
-
181 return signal;
-
182 }
-
183
-
184 std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
-
185 if (isLittleEndian)
-
186 return bit + 1U;
-
187 // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
-
188 return bit % 8U == 0U ? bit + 15U : bit - 1U;
-
189 }
-
190
-
191 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
-
192 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
-
193} // namespace
-
194
-
195namespace {
-
196 void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
-
197 /*
-
198 * DBC commonly stores extended identifiers with bit 31 set.
-
199 * Example:
-
200 * raw id = 0x80000000 | actual_29_bit_id
-
201 */
-
202 isExtended = (rawCanId & 0x80000000U) != 0U || rawCanId > 0x7FFU;
-
203 normalizedCanId = rawCanId & 0x1FFFFFFFU;
-
204 }
-
205} // namespace
-
206
-
207FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
-
208 /*
-
209 * Example:
-
210 * BO_ 256 EngineData: 8 EEC1
-
211 */
-
212
-
213 std::istringstream stream(line);
-
214 std::string keyword;
-
215 std::string idText;
-
216 if (!(stream >> keyword >> idText) || keyword != "BO_")
-
217 throw std::runtime_error("Missing CAN ID in frame definition.");
-
218
-
219 const std::uint32_t rawCanId = ParseCanId(idText);
-
220
-
221 std::string nameText;
-
222 if (!std::getline(stream, nameText, ':') || stream.eof())
-
223 throw std::runtime_error("Missing ':' in frame definition.");
-
224 std::istringstream nameStream(nameText);
-
225 FrameInfo frame;
-
226 std::string extra;
-
227 if (!(nameStream >> frame.name) || (nameStream >> extra))
-
228 throw std::runtime_error("Missing or invalid frame name.");
-
229
-
230 std::string lengthText;
-
231 if (!(stream >> lengthText))
-
232 throw std::runtime_error("Missing payload length in frame definition.");
-
233 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
-
234 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
-
235 if (!(stream >> frame.transmitter))
-
236 throw std::runtime_error("Missing transmitter in frame definition.");
-
237 if (stream >> extra)
-
238 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
-
239
-
240 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
-
241
-
242 return frame;
-
243}
-
244
-
245SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
-
246 if (line.size() < 4U || line.compare(0U, 3U, "SG_") != 0 ||
-
247 (line[3] != ' ' && line[3] != '\t'))
-
248 throw std::runtime_error("Malformed SG_ signal definition.");
-
249 SignalTokens tokens(line, 3U);
-
250 SignalInfo signal;
-
251 std::istringstream nameFields(tokens.Until(':'));
-
252 std::string marker;
-
253 std::string extra;
-
254 nameFields >> signal.name >> marker;
-
255 if (signal.name.empty() || (nameFields >> extra))
-
256 throw std::runtime_error("Invalid signal name or multiplexing marker.");
-
257 signal.multiplexing = ParseMultiplexingMarker(marker);
-
258
-
259 signal.startBit = ParseDecimal(dbc_detail::TrimText(tokens.Until('|')),
-
260 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
-
261 signal.length = ParseDecimal(dbc_detail::TrimText(tokens.Until('@')), 64U, "signal length");
-
262 if (signal.length == 0U)
-
263 throw std::runtime_error("Signal length must be within 1..64 bits.");
-
264 tokens.SkipSpaces();
-
265 const char byteOrder = tokens.Character();
-
266 const char signedness = tokens.Character();
-
267 if ((byteOrder != '0' && byteOrder != '1') || (signedness != '+' && signedness != '-'))
-
268 throw std::runtime_error("Invalid SG_ byte order or signedness.");
-
269 signal.isLittleEndian = byteOrder == '1';
-
270 signal.isSigned = signedness == '-';
-
271
-
272 tokens.Expect('(');
-
273 signal.factor = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(',')), "signal factor");
-
274 signal.offset = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(')')), "signal offset");
-
275 tokens.Expect('[');
-
276 signal.minimum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until('|')), "signal minimum");
-
277 signal.maximum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(']')), "signal maximum");
-
278 if (signal.minimum > signal.maximum)
-
279 throw std::runtime_error("Signal minimum exceeds maximum.");
-
280 tokens.Expect('"');
-
281 signal.unit = tokens.Until('"');
-
282 if (signal.unit.find_first_of("\r\n") != std::string::npos)
-
283 throw std::runtime_error("Invalid newline in SG_ unit.");
-
284 signal.receivers = tokens.Receivers();
-
285 return signal;
-
286}
-
287
-
288
-
289namespace {
-
290 FrameInfo *FindFrameById(DbcDatabase &database,
-
291 std::uint32_t canId,
-
292 bool isExtended) {
-
293 for (FrameInfo &frame : database.frames) {
-
294 if (frame.canId == canId && frame.isExtended == isExtended)
-
295 return &frame;
-
296 }
-
297
-
298 return nullptr;
-
299 }
-
300
-
301 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
-
302 for (SignalInfo &signal : frame.signals) {
-
303 if (signal.name == signalName)
-
304 return &signal;
-
305 }
-
306
-
307 return nullptr;
-
308 }
-
309}// namespace
-
310
-
-
311void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
312 FrameInfo frame = ParseFrameLine(line);
-
313 if (context.enableJ1939 && frame.isExtended) {
-
314 frame.hasPgn = true;
-
315 frame.pgn = ExtractJ1939Pgn(frame.canId);
-
316 }
-
317 if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
-
318 throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
-
319 " frame CAN ID: " + std::to_string(frame.canId));
-
320 context.database.frames.push_back(std::move(frame));
-
321 context.currentFrame = &context.database.frames.back();
-
322 context.signalLines.emplace_back();
-
323}
-
+
179
+
180 std::string Word() {
+
181 const std::size_t begin = position;
+
182 while (position < text.size() && text[position] != ' ' && text[position] != '\t' &&
+
183 text[position] != ';' && text[position] != '"')
+
184 ++position;
+
185 if (position == begin)
+
186 Fail();
+
187 return text.substr(begin, position - begin);
+
188 }
+
189
+
190 std::uint64_t Unsigned() {
+
191 SkipSpaces();
+
192 const std::size_t begin = position;
+
193 while (position < text.size() && text[position] >= '0' && text[position] <= '9')
+
194 ++position;
+
195 std::uint64_t value = 0U;
+
196 const std::from_chars_result parsed =
+
197 std::from_chars(text.data() + begin, text.data() + position, value);
+
198 if (position == begin || parsed.ec != std::errc() || parsed.ptr != text.data() + position)
+
199 Fail();
+
200 return value;
+
201 }
+
202
+
203 std::string Description() {
+
204 if (!Consume('"'))
+
205 Fail();
+
206 std::string description;
+
207 while (position < text.size()) {
+
208 char character = text[position++];
+
209 if (character == '"')
+
210 return description;
+
211 if (character == '\\') {
+
212 if (position == text.size())
+
213 Fail();
+
214 character = text[position++];
+
215 if (character != '"' && character != '\\')
+
216 Fail();
+
217 }
+
218 description += character;
+
219 }
+
220 Fail();
+
221 }
+
222
+
223 void End() {
+
224 SkipSpaces();
+
225 if (position != text.size())
+
226 Fail();
+
227 }
+
228
+
229 private:
+
230 [[noreturn]] void Fail() const {
+
231 throw std::runtime_error("Malformed " + keyword + " statement.");
+
232 }
+
233
+
234 const std::string &text;
+
235 const std::string keyword;
+
236 std::size_t position = 0U;
+
237 };
+
238
+
239 FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended, const std::string &statement = "VAL_") {
+
240 FrameInfo *frame = nullptr;
+
241 for (FrameInfo &candidate : database.frames) {
+
242 if (candidate.canId == canId && candidate.isExtended == isExtended) {
+
243 if (frame)
+
244 throw std::runtime_error("Ambiguous " + statement + " frame reference.");
+
245 frame = &candidate;
+
246 }
+
247 }
+
248 if (!frame)
+
249 throw std::runtime_error("Unresolved " + statement + " frame reference.");
+
250 return frame;
+
251 }
+
252
+
253 SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName, const std::string &statement = "VAL_") {
+
254 SignalInfo *signal = nullptr;
+
255 for (SignalInfo &candidate : frame.signals) {
+
256 if (candidate.name == signalName) {
+
257 if (signal)
+
258 throw std::runtime_error("Ambiguous " + statement + " signal reference: " + signalName);
+
259 signal = &candidate;
+
260 }
+
261 }
+
262 if (!signal)
+
263 throw std::runtime_error("Unresolved " + statement + " signal reference: " + signalName);
+
264 return signal;
+
265 }
+
266
+
267 std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
+
268 if (isLittleEndian)
+
269 return bit + 1U;
+
270 // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
+
271 return bit % 8U == 0U ? bit + 15U : bit - 1U;
+
272 }
+
273
+
274 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
+
275 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
+
276} // namespace
+
277
+
278namespace {
+
279 void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
+
280 /*
+
281 * DBC commonly stores extended identifiers with bit 31 set.
+
282 * Example:
+
283 * raw id = 0x80000000 | actual_29_bit_id
+
284 */
+
285 isExtended = (rawCanId & 0x80000000U) != 0U || rawCanId > 0x7FFU;
+
286 normalizedCanId = rawCanId & 0x1FFFFFFFU;
+
287 }
+
288} // namespace
+
289
+
290FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
+
291 /*
+
292 * Example:
+
293 * BO_ 256 EngineData: 8 EEC1
+
294 */
+
295
+
296 std::istringstream stream(line);
+
297 std::string keyword;
+
298 std::string idText;
+
299 if (!(stream >> keyword >> idText) || keyword != "BO_")
+
300 throw std::runtime_error("Missing CAN ID in frame definition.");
+
301
+
302 const std::uint32_t rawCanId = ParseCanId(idText);
+
303
+
304 std::string nameText;
+
305 if (!std::getline(stream, nameText, ':') || stream.eof())
+
306 throw std::runtime_error("Missing ':' in frame definition.");
+
307 std::istringstream nameStream(nameText);
+
308 FrameInfo frame;
+
309 std::string extra;
+
310 if (!(nameStream >> frame.name) || (nameStream >> extra))
+
311 throw std::runtime_error("Missing or invalid frame name.");
+
312
+
313 std::string lengthText;
+
314 if (!(stream >> lengthText))
+
315 throw std::runtime_error("Missing payload length in frame definition.");
+
316 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
+
317 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
+
318 if (!(stream >> frame.transmitter))
+
319 throw std::runtime_error("Missing transmitter in frame definition.");
+
320 if (stream >> extra)
+
321 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
+
322
+
323 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
324
-
-
325void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
326 if (context.currentFrame == nullptr)
-
327 throw std::runtime_error("Signal found before any frame definition.");
-
328 SignalInfo signal = ParseSignalLine(line);
-
329 if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
-
330 throw std::runtime_error("Duplicate signal name '" + signal.name +
-
331 "' in frame '" + context.currentFrame->name + "'.");
-
332 if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
-
333 context.currentFrame->dlc))
-
334 throw std::runtime_error("Signal exceeds the frame payload length.");
-
335 context.currentFrame->signals.push_back(std::move(signal));
-
336 context.signalLines.back().push_back(context.lineNumber);
-
337}
+
325 return frame;
+
326}
+
327
+
328SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
+
329 if (line.size() < 4U || line.compare(0U, 3U, "SG_") != 0 ||
+
330 (line[3] != ' ' && line[3] != '\t'))
+
331 throw std::runtime_error("Malformed SG_ signal definition.");
+
332 SignalTokens tokens(line, 3U);
+
333 SignalInfo signal;
+
334 std::istringstream nameFields(tokens.Until(':'));
+
335 std::string marker;
+
336 std::string extra;
+
337 nameFields >> signal.name >> marker;
+
338 if (signal.name.empty() || (nameFields >> extra))
+
339 throw std::runtime_error("Invalid signal name or multiplexing marker.");
+
340 signal.multiplexing = ParseMultiplexingMarker(marker);
+
341
+
342 signal.startBit = ParseDecimal(dbc_detail::TrimText(tokens.Until('|')),
+
343 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
+
344 signal.length = ParseDecimal(dbc_detail::TrimText(tokens.Until('@')), 64U, "signal length");
+
345 if (signal.length == 0U)
+
346 throw std::runtime_error("Signal length must be within 1..64 bits.");
+
347 tokens.SkipSpaces();
+
348 const char byteOrder = tokens.Character();
+
349 const char signedness = tokens.Character();
+
350 if ((byteOrder != '0' && byteOrder != '1') || (signedness != '+' && signedness != '-'))
+
351 throw std::runtime_error("Invalid SG_ byte order or signedness.");
+
352 signal.isLittleEndian = byteOrder == '1';
+
353 signal.isSigned = signedness == '-';
+
354
+
355 tokens.Expect('(');
+
356 signal.factor = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(',')), "signal factor");
+
357 signal.offset = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(')')), "signal offset");
+
358 tokens.Expect('[');
+
359 signal.minimum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until('|')), "signal minimum");
+
360 signal.maximum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(']')), "signal maximum");
+
361 if (signal.minimum > signal.maximum)
+
362 throw std::runtime_error("Signal minimum exceeds maximum.");
+
363 tokens.Expect('"');
+
364 signal.unit = tokens.Until('"');
+
365 if (signal.unit.find_first_of("\r\n") != std::string::npos)
+
366 throw std::runtime_error("Invalid newline in SG_ unit.");
+
367 signal.receivers = tokens.Receivers();
+
368 return signal;
+
369}
+
370
+
371
+
372namespace {
+
373 FrameInfo *FindFrameById(DbcDatabase &database,
+
374 std::uint32_t canId,
+
375 bool isExtended) {
+
376 for (FrameInfo &frame : database.frames) {
+
377 if (frame.canId == canId && frame.isExtended == isExtended)
+
378 return &frame;
+
379 }
+
380
+
381 return nullptr;
+
382 }
+
383
+
384 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
+
385 for (SignalInfo &signal : frame.signals) {
+
386 if (signal.name == signalName)
+
387 return &signal;
+
388 }
+
389
+
390 return nullptr;
+
391 }
+
392}// namespace
+
393
+
+
394void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
395 FrameInfo frame = ParseFrameLine(line);
+
396 if (context.enableJ1939 && frame.isExtended) {
+
397 frame.hasPgn = true;
+
398 frame.pgn = ExtractJ1939Pgn(frame.canId);
+
399 }
+
400 if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
+
401 throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
+
402 " frame CAN ID: " + std::to_string(frame.canId));
+
403 context.database.frames.push_back(std::move(frame));
+
404 context.currentFrame = &context.database.frames.back();
+
405 context.signalLines.emplace_back();
+
406}
-
338
-
-
339void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
340 std::istringstream stream(line);
-
341 std::string keyword;
-
342 std::string target;
-
343 stream >> keyword >> target;
-
344 if (keyword != "CM_" || (target != "BO_" && target != "SG_"))
-
345 return;
-
346
-
347 PendingComment pending;
-
348 pending.isSignal = target == "SG_";
-
349 pending.lineNumber = context.lineNumber;
-
350 std::string idText;
-
351 if (!(stream >> idText))
-
352 throw std::runtime_error("Missing CAN ID in CM_ statement.");
-
353 NormalizeCanId(ParseCanId(idText), pending.canId, pending.isExtended);
-
354 if (pending.isSignal && (!(stream >> pending.signalName) ||
-
355 pending.signalName.find_first_of("\";") != std::string::npos))
-
356 throw std::runtime_error("Missing or invalid signal name in CM_ statement.");
-
357
-
358 stream >> std::ws;
-
359 if (stream.get() != '"')
-
360 throw std::runtime_error("Missing opening quote in CM_ statement.");
-
361 bool closed = false;
-
362 char character;
-
363 while (stream.get(character)) {
-
364 if (character == '"') {
-
365 closed = true;
-
366 break;
-
367 }
-
368 if (character == '\\') {
-
369 if (!stream.get(character) || (character != '"' && character != '\\'))
-
370 throw std::runtime_error("Invalid escape in CM_ statement.");
-
371 }
-
372 pending.comment += character;
-
373 }
-
374 if (!closed)
-
375 throw std::runtime_error("Unterminated CM_ comment.");
-
376 stream >> std::ws;
-
377 if (stream.get() != ';')
-
378 throw std::runtime_error("Missing semicolon in CM_ statement.");
-
379 stream >> std::ws;
-
380 if (!stream.eof())
-
381 throw std::runtime_error("Unexpected trailing text in CM_ statement.");
-
382 context.pendingComments.push_back(std::move(pending));
-
383}
-
-
384
-
-
385void DbcParseContext::ResolveComments(const std::string &filePath) {
-
386 for (const PendingComment &pending: pendingComments) {
-
387 FrameInfo *frame = FindFrameById(database, pending.canId, pending.isExtended);
-
388 if (!frame)
-
389 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
-
390 pending.lineNumber, "Unresolved CM_ frame reference.");
-
391 if (pending.isSignal) {
-
392 SignalInfo *signal = FindSignalByName(*frame, pending.signalName);
-
393 if (!signal)
-
394 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
-
395 pending.lineNumber, "Unresolved CM_ signal reference: " + pending.signalName);
-
396 signal->comment = pending.comment;
-
397 } else {
-
398 frame->comment = pending.comment;
-
399 }
-
400 }
-
401}
-
-
402
-
- -
404 RegisterType<DbcHeaderHandler>("VERSION");
-
405 RegisterType<DbcHeaderHandler>("NS_");
-
406 RegisterType<DbcHeaderHandler>("NS_:");
-
407 RegisterType<DbcHeaderHandler>("BS_");
-
408 RegisterType<DbcHeaderHandler>("BS_:");
-
409 RegisterType<DbcHeaderHandler>("BU_");
-
410 RegisterType<DbcHeaderHandler>("BU_:");
-
411 RegisterType<DbcMultiplexingHandler>("SG_MUL_VAL_");
-
412 RegisterType<DbcFrameHandler>("BO_");
-
413 RegisterType<DbcSignalHandler>("SG_");
-
414 RegisterType<DbcUnsupportedSignalTypeHandler>("SIG_VALTYPE_");
-
415 RegisterType<DbcCommentHandler>("CM_");
-
416 RegisterType<DbcValueDescriptionHandler>("VAL_");
-
417 RegisterType<DbcAttributeHandler>("BA_DEF_");
-
418 RegisterType<DbcAttributeHandler>("BA_DEF_DEF_");
-
419 RegisterType<DbcAttributeHandler>("BA_");
+
407
+
+
408void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
409 if (context.currentFrame == nullptr)
+
410 throw std::runtime_error("Signal found before any frame definition.");
+
411 SignalInfo signal = ParseSignalLine(line);
+
412 if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
+
413 throw std::runtime_error("Duplicate signal name '" + signal.name +
+
414 "' in frame '" + context.currentFrame->name + "'.");
+
415 if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
+
416 context.currentFrame->dlc))
+
417 throw std::runtime_error("Signal exceeds the frame payload length.");
+
418 context.currentFrame->signals.push_back(std::move(signal));
+
419 context.signalLines.back().push_back(context.lineNumber);
420}
421
- -
423 throw std::runtime_error("SIG_VALTYPE_ declarations are unsupported; refusing potentially incorrect integer decoding.");
-
424}
+
422void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
423 std::istringstream stream(line);
+
424 std::string keyword;
+
425 std::string target;
+
426 stream >> keyword >> target;
+
427 if (keyword != "CM_" || (target != "BO_" && target != "SG_"))
+
428 return;
+
429
+
430 PendingComment pending;
+
431 pending.isSignal = target == "SG_";
+
432 pending.lineNumber = context.lineNumber;
+
433 std::string idText;
+
434 if (!(stream >> idText))
+
435 throw std::runtime_error("Missing CAN ID in CM_ statement.");
+
436 NormalizeCanId(ParseCanId(idText), pending.canId, pending.isExtended);
+
437 if (pending.isSignal && (!(stream >> pending.signalName) ||
+
438 pending.signalName.find_first_of("\";") != std::string::npos))
+
439 throw std::runtime_error("Missing or invalid signal name in CM_ statement.");
+
440
+
441 stream >> std::ws;
+
442 if (stream.get() != '"')
+
443 throw std::runtime_error("Missing opening quote in CM_ statement.");
+
444 bool closed = false;
+
445 char character;
+
446 while (stream.get(character)) {
+
447 if (character == '"') {
+
448 closed = true;
+
449 break;
+
450 }
+
451 if (character == '\\') {
+
452 if (!stream.get(character) || (character != '"' && character != '\\'))
+
453 throw std::runtime_error("Invalid escape in CM_ statement.");
+
454 }
+
455 pending.comment += character;
+
456 }
+
457 if (!closed)
+
458 throw std::runtime_error("Unterminated CM_ comment.");
+
459 stream >> std::ws;
+
460 if (stream.get() != ';')
+
461 throw std::runtime_error("Missing semicolon in CM_ statement.");
+
462 stream >> std::ws;
+
463 if (!stream.eof())
+
464 throw std::runtime_error("Unexpected trailing text in CM_ statement.");
+
465 context.pendingComments.push_back(std::move(pending));
+
466}
-
425
-
426void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
-
427
-
-
428bool DbcStatementDispatcher::Dispatch(const std::string &line,
-
429 DbcParseContext &context) const {
-
430 std::istringstream stream(line);
-
431 std::string keyword;
-
432 stream >> keyword;
-
433 const HandlerMap::const_iterator handler = m_handlers.find(keyword);
-
434 if (handler == m_handlers.end())
-
435 return false;
-
436 handler->second->Handle(line, context);
-
437 return true;
-
438}
+
467
+
+
468void DbcParseContext::ResolveComments(const std::string &filePath) {
+
469 for (const PendingComment &pending: pendingComments) {
+
470 FrameInfo *frame = FindFrameById(database, pending.canId, pending.isExtended);
+
471 if (!frame)
+
472 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
+
473 pending.lineNumber, "Unresolved CM_ frame reference.");
+
474 if (pending.isSignal) {
+
475 SignalInfo *signal = FindSignalByName(*frame, pending.signalName);
+
476 if (!signal)
+
477 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
+
478 pending.lineNumber, "Unresolved CM_ signal reference: " + pending.signalName);
+
479 signal->comment = pending.comment;
+
480 } else {
+
481 frame->comment = pending.comment;
+
482 }
+
483 }
+
484}
-
439
-
-
440void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
441 static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
-
442 static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
-
443 std::smatch fields;
-
444 if (!std::regex_match(line, fields, header))
-
445 throw std::runtime_error("Malformed or unsupported VAL_ statement.");
-
446 const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
-
447 if ((rawId & 0x60000000U) != 0U)
-
448 throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
- -
450 NormalizeCanId(rawId, pending.canId, pending.isExtended);
-
451 pending.signalName = fields[2].str();
-
452 pending.lineNumber = context.lineNumber;
-
453 std::string rest = fields[3].str();
-
454 while (rest != ";") {
-
455 if (!std::regex_search(rest, fields, pair))
-
456 throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
-
457 const std::string number = fields[1].str();
-
458 const RawSignalValue key = ParseValueKey(number);
-
459 const std::string description = UnescapeDescription(fields[2].str());
-
460 if (!pending.descriptions.emplace(key, description).second)
-
461 throw std::runtime_error("Duplicate VAL_ value key: " + number);
-
462 const std::size_t consumed = static_cast<std::size_t>(fields.length());
-
463 rest.erase(0, consumed);
-
464 if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
-
465 throw std::runtime_error("Missing whitespace between VAL_ pairs.");
-
466 rest = dbc_detail::TrimText(rest);
-
467 }
-
468 if (pending.descriptions.empty())
-
469 throw std::runtime_error("VAL_ requires at least one value/description pair.");
-
470 context.pendingValues.push_back(std::move(pending));
-
471}
+
485
+
+ +
487 RegisterType<DbcHeaderHandler>("VERSION");
+
488 RegisterType<DbcHeaderHandler>("NS_");
+
489 RegisterType<DbcHeaderHandler>("NS_:");
+
490 RegisterType<DbcHeaderHandler>("BS_");
+
491 RegisterType<DbcHeaderHandler>("BS_:");
+
492 RegisterType<DbcHeaderHandler>("BU_");
+
493 RegisterType<DbcHeaderHandler>("BU_:");
+
494 RegisterType<DbcMultiplexingHandler>("SG_MUL_VAL_");
+
495 RegisterType<DbcFrameHandler>("BO_");
+
496 RegisterType<DbcSignalHandler>("SG_");
+
497 RegisterType<DbcUnsupportedSignalTypeHandler>("SIG_VALTYPE_");
+
498 RegisterType<DbcCommentHandler>("CM_");
+
499 RegisterType<DbcValueDescriptionHandler>("VAL_");
+
500 RegisterType<DbcAttributeHandler>("BA_DEF_");
+
501 RegisterType<DbcAttributeHandler>("BA_DEF_DEF_");
+
502 RegisterType<DbcAttributeHandler>("BA_");
+
503}
-
472
-
-
473void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
-
474 for (const PendingValueDescriptions &pending: pendingValues) {
-
475 try {
-
476 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
-
477 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
-
478 for (const ValueDescriptions::value_type &entry: pending.descriptions) {
-
479 if (!signal->valueDescriptions.insert(entry).second)
-
480 throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
-
481 }
-
482 } catch (const std::exception &error) {
-
483 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
-
484 filePath, pending.lineNumber, error.what());
-
485 }
-
486 }
-
487}
+
504
+
+ +
506 throw std::runtime_error("SIG_VALTYPE_ declarations are unsupported; refusing potentially incorrect integer decoding.");
+
507}
-
488
-
-
489void DbcMultiplexingHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
490 static const std::regex syntax(R"(^SG_MUL_VAL_[ \t]+([0-9]+)[ \t]+([^ \t;]+)[ \t]+([^ \t;]+)[ \t]+(.+);[ \t]*$)");
-
491 static const std::regex rangeSyntax(R"(^[ \t]*([0-9]+)[ \t]*-[ \t]*([0-9]+)[ \t]*$)");
-
492 std::smatch fields;
-
493 if (!std::regex_match(line, fields, syntax))
-
494 throw std::runtime_error("Malformed SG_MUL_VAL_ statement.");
-
495 PendingMultiplexing pending;
-
496 NormalizeCanId(ParseCanId(fields[1].str()), pending.canId, pending.isExtended);
-
497 pending.signalName = fields[2].str();
-
498 pending.selectorName = fields[3].str();
-
499 pending.lineNumber = context.lineNumber;
-
500 const std::string list = fields[4].str();
-
501 std::size_t begin = 0U;
-
502 for (;;) {
-
503 const std::size_t comma = list.find(',', begin);
-
504 const std::string item = list.substr(begin, comma == std::string::npos ? comma : comma - begin);
-
505 if (!std::regex_match(item, fields, rangeSyntax))
-
506 throw std::runtime_error("Malformed multiplex range.");
-
507 MultiplexRange range;
-
508 const std::string lower = fields[1].str();
-
509 const std::string upper = fields[2].str();
-
510 const std::from_chars_result low = std::from_chars(lower.data(), lower.data() + lower.size(), range.minimum);
-
511 const std::from_chars_result high = std::from_chars(upper.data(), upper.data() + upper.size(), range.maximum);
-
512 if (low.ec != std::errc() || high.ec != std::errc() || range.minimum > range.maximum)
-
513 throw std::runtime_error("Invalid multiplex range.");
-
514 pending.ranges.push_back(range);
-
515 if (comma == std::string::npos)
-
516 break;
-
517 begin = comma + 1U;
-
518 }
-
519 context.pendingMultiplexing.push_back(std::move(pending));
-
520}
+
508
+
509void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
+
510
+
+
511bool DbcStatementDispatcher::Dispatch(const std::string &line,
+
512 DbcParseContext &context) const {
+
513 std::istringstream stream(line);
+
514 std::string keyword;
+
515 stream >> keyword;
+
516 const HandlerMap::const_iterator handler = m_handlers.find(keyword);
+
517 if (handler == m_handlers.end())
+
518 return false;
+
519 handler->second->Handle(line, context);
+
520 return true;
+
521}
-
521
-
522void DbcParseContext::ResolveMultiplexing(const std::string &filePath) {
-
523 for (const PendingMultiplexing &pending: pendingMultiplexing) {
-
524 try {
-
525 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended, "SG_MUL_VAL_");
-
526 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName, "SG_MUL_VAL_");
-
527 SignalInfo *selector = FindUniqueSignal(*frame, pending.selectorName, "SG_MUL_VAL_");
-
528 if (!IsMultiplexSelector(selector->multiplexing.role) || selector->isSigned)
-
529 throw std::runtime_error("Referenced signal must be an unsigned multiplex selector.");
-
530 if (!signal->multiplexing.selector.empty() && signal->multiplexing.selector != pending.selectorName)
-
531 throw std::runtime_error("Multiple parent selectors for one signal are unsupported.");
-
532 for (const MultiplexRange &range: pending.ranges) {
-
533 if (selector->length < 64U && range.maximum >= (std::uint64_t(1U) << selector->length))
-
534 throw std::runtime_error("Branch value exceeds selector raw range.");
-
535 }
-
536 signal->multiplexing.selector = pending.selectorName;
-
537 signal->multiplexing.ranges.insert(signal->multiplexing.ranges.end(), pending.ranges.begin(), pending.ranges.end());
-
538 if (!IsMultiplexSelector(signal->multiplexing.role))
-
539 signal->multiplexing.role = MultiplexRole::Branch;
-
540 else
-
541 signal->multiplexing.role = MultiplexRole::BranchSelector;
-
542 } catch (const std::exception &error) {
-
543 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, pending.lineNumber, error.what());
-
544 }
-
545 }
-
546}
-
547
-
548namespace {
-
549 std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
-
550 std::vector<std::uint64_t> bits;
-
551 std::uint64_t bit = signal.startBit;
-
552 for (std::uint32_t index = 0U; index < signal.length; ++index) {
-
553 bits.push_back(bit);
-
554 bit = NextSignalBit(bit, signal.isLittleEndian);
-
555 }
-
556 return bits;
-
557 }
-
558
-
559 bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
-
560 const std::vector<std::uint64_t> firstBits = SignalBits(first);
-
561 const std::vector<std::uint64_t> secondBits = SignalBits(second);
-
562 for (const std::uint64_t firstBit : firstBits) {
-
563 for (const std::uint64_t secondBit : secondBits) {
-
564 if (firstBit == secondBit)
-
565 return true;
-
566 }
-
567 }
-
568 return false;
-
569 }
-
570}
-
571
-
-
572void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
-
573 for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
-
574 const FrameInfo &frame = database.frames[frameIndex];
-
575 try {
-
576 const dbc_detail::MultiplexGraph graph(frame.signals);
-
577 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
-
578 const SignalInfo &signal = frame.signals[index];
-
579 if (IsMultiplexSelector(signal.multiplexing.role) && signal.isSigned)
-
580 throw dbc_detail::MultiplexError(index, "Multiplex selectors must be unsigned.");
-
581 for (std::size_t previous = 0U; previous < index; ++previous) {
-
582 if (!graph.AreMutuallyExclusive(index, previous) && SignalsOverlap(signal, frame.signals[previous]))
-
583 throw dbc_detail::MultiplexError(index, "Signals overlap without mutually exclusive multiplexing conditions.");
-
584 }
-
585 }
-
586 } catch (const dbc_detail::MultiplexError &error) {
-
587 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath,
-
588 signalLines[frameIndex][error.index], error.what());
-
589 }
-
590 }
-
591}
+
522
+
+
523void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
524 StatementTokens tokens(line, "VAL_");
+
525 const std::uint32_t rawId = ParseCanId(tokens.Word());
+
526 tokens.RequireSpace();
+ +
528 NormalizeCanId(rawId, pending.canId, pending.isExtended);
+
529 pending.signalName = tokens.Word();
+
530 tokens.RequireSpace();
+
531 pending.lineNumber = context.lineNumber;
+
532 while (!tokens.Consume(';')) {
+
533 const std::string number = tokens.Word();
+
534 const RawSignalValue key = ParseValueKey(number);
+
535 tokens.RequireSpace();
+
536 const std::string description = tokens.Description();
+
537 if (!pending.descriptions.emplace(key, description).second)
+
538 throw std::runtime_error("Duplicate VAL_ value key: " + number);
+
539 const bool separated = tokens.SkipSpaces();
+
540 if (tokens.Consume(';'))
+
541 break;
+
542 if (!separated)
+
543 throw std::runtime_error("Missing whitespace between VAL_ pairs.");
+
544 }
+
545 tokens.End();
+
546 if (pending.descriptions.empty())
+
547 throw std::runtime_error("VAL_ requires at least one value/description pair.");
+
548 context.pendingValues.push_back(std::move(pending));
+
549}
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
550
+
+
551void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
+
552 for (const PendingValueDescriptions &pending: pendingValues) {
+
553 try {
+
554 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
+
555 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
+
556 for (const ValueDescriptions::value_type &entry: pending.descriptions) {
+
557 if (!signal->valueDescriptions.insert(entry).second)
+
558 throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
+
559 }
+
560 } catch (const std::exception &error) {
+
561 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
+
562 filePath, pending.lineNumber, error.what());
+
563 }
+
564 }
+
565}
+
+
566
+
+
567void DbcMultiplexingHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
568 StatementTokens tokens(line, "SG_MUL_VAL_");
+
569 PendingMultiplexing pending;
+
570 NormalizeCanId(ParseCanId(tokens.Word()), pending.canId, pending.isExtended);
+
571 tokens.RequireSpace();
+
572 pending.signalName = tokens.Word();
+
573 tokens.RequireSpace();
+
574 pending.selectorName = tokens.Word();
+
575 tokens.RequireSpace();
+
576 pending.lineNumber = context.lineNumber;
+
577 for (;;) {
+
578 MultiplexRange range;
+
579 range.minimum = tokens.Unsigned();
+
580 tokens.Expect('-');
+
581 range.maximum = tokens.Unsigned();
+
582 if (range.minimum > range.maximum)
+
583 throw std::runtime_error("Invalid multiplex range.");
+
584 pending.ranges.push_back(range);
+
585 tokens.SkipSpaces();
+
586 if (tokens.Consume(';'))
+
587 break;
+
588 tokens.Expect(',');
+
589 }
+
590 tokens.End();
+
591 context.pendingMultiplexing.push_back(std::move(pending));
+
592}
+
+
593
+
594void DbcParseContext::ResolveMultiplexing(const std::string &filePath) {
+
595 for (const PendingMultiplexing &pending: pendingMultiplexing) {
+
596 try {
+
597 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended, "SG_MUL_VAL_");
+
598 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName, "SG_MUL_VAL_");
+
599 SignalInfo *selector = FindUniqueSignal(*frame, pending.selectorName, "SG_MUL_VAL_");
+
600 if (!IsMultiplexSelector(selector->multiplexing.role) || selector->isSigned)
+
601 throw std::runtime_error("Referenced signal must be an unsigned multiplex selector.");
+
602 if (!signal->multiplexing.selector.empty() && signal->multiplexing.selector != pending.selectorName)
+
603 throw std::runtime_error("Multiple parent selectors for one signal are unsupported.");
+
604 for (const MultiplexRange &range: pending.ranges) {
+
605 if (selector->length < 64U && range.maximum >= (std::uint64_t(1U) << selector->length))
+
606 throw std::runtime_error("Branch value exceeds selector raw range.");
+
607 }
+
608 signal->multiplexing.selector = pending.selectorName;
+
609 signal->multiplexing.ranges.insert(signal->multiplexing.ranges.end(), pending.ranges.begin(), pending.ranges.end());
+
610 if (!IsMultiplexSelector(signal->multiplexing.role))
+
611 signal->multiplexing.role = MultiplexRole::Branch;
+
612 else
+
613 signal->multiplexing.role = MultiplexRole::BranchSelector;
+
614 } catch (const std::exception &error) {
+
615 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, pending.lineNumber, error.what());
+
616 }
+
617 }
+
618}
+
619
+
620namespace {
+
621 std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
+
622 std::vector<std::uint64_t> bits;
+
623 std::uint64_t bit = signal.startBit;
+
624 for (std::uint32_t index = 0U; index < signal.length; ++index) {
+
625 bits.push_back(bit);
+
626 bit = NextSignalBit(bit, signal.isLittleEndian);
+
627 }
+
628 return bits;
+
629 }
+
630
+
631 bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
+
632 const std::vector<std::uint64_t> firstBits = SignalBits(first);
+
633 const std::vector<std::uint64_t> secondBits = SignalBits(second);
+
634 for (const std::uint64_t firstBit : firstBits) {
+
635 for (const std::uint64_t secondBit : secondBits) {
+
636 if (firstBit == secondBit)
+
637 return true;
+
638 }
+
639 }
+
640 return false;
+
641 }
+
642}
+
643
+
+
644void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
+
645 for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
+
646 const FrameInfo &frame = database.frames[frameIndex];
+
647 try {
+
648 const dbc_detail::MultiplexGraph graph(frame.signals);
+
649 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
+
650 const SignalInfo &signal = frame.signals[index];
+
651 if (IsMultiplexSelector(signal.multiplexing.role) && signal.isSigned)
+
652 throw dbc_detail::MultiplexError(index, "Multiplex selectors must be unsigned.");
+
653 for (std::size_t previous = 0U; previous < index; ++previous) {
+
654 if (!graph.AreMutuallyExclusive(index, previous) && SignalsOverlap(signal, frame.signals[previous]))
+
655 throw dbc_detail::MultiplexError(index, "Signals overlap without mutually exclusive multiplexing conditions.");
+
656 }
+
657 }
+
658 } catch (const dbc_detail::MultiplexError &error) {
+
659 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath,
+
660 signalLines[frameIndex][error.index], error.what());
+
661 }
+
662 }
+
663}
+
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Parsed DBC content stored in a simple internal form.
@@ -729,11 +801,11 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8cpp_source.ht
std::vector< PendingComment > pendingComments
-
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
+
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
-
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
+
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
std::vector< PendingValueDescriptions > pendingValues
-
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
+
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
std::vector< std::vector< std::size_t > > signalLines
Describes one CAN frame from a DBC file.
Definition frame_info.h:25
diff --git a/docs/html/dbc__statement__handler_8h_source.html b/docs/html/dbc__statement__handler_8h_source.html index 29c127d..67191ca 100644 --- a/docs/html/dbc__statement__handler_8h_source.html +++ b/docs/html/dbc__statement__handler_8h_source.html @@ -281,28 +281,28 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8h_source.html
Defer typed attribute statements for resolution.
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Defer frame and signal comments for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
DbcDiagnosticError(DbcDiagnosticCode code, const std::string &filePath, std::size_t lineNumber, const std::string &message)
Create an exception with a structured source diagnostic.
Parse and append frame definitions.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Parse extended multiplexing conditions for deferred resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Parse and append signals to the current frame.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
void RegisterType(const std::string &keyword)
Register or replace a handler and create its reusable instance.
-
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
DbcStatementDispatcher()
Register supported statement handlers through TypeFactory.
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
Interface for processing a registered DBC statement.
virtual void Handle(const std::string &line, DbcParseContext &context) const =0
Process a trimmed statement using state belonging to this parse.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Defer raw value descriptions for target resolution.
-
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
+
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Registry-based factory for creating objects by runtime identifier.
Definition typefactory.h:59
BasePtr create(const ClassId &id, Args... args) const
Creates an instance for the given id using provided arguments.
Definition typefactory.h:99
void registerType(const ClassId &id)
Registers a Derived type under the given id.
Definition typefactory.h:78
@@ -314,11 +314,11 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8h_source.html
std::vector< PendingComment > pendingComments
-
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
+
void ValidateMultiplexing(const std::string &filePath) const
Validate selectors, branch ranges, and signal overlap.
-
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
+
void ResolveValueDescriptions(const std::string &filePath)
Resolve deferred raw descriptions against unique targets.
std::vector< PendingValueDescriptions > pendingValues
-
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
+
void ResolveComments(const std::string &filePath)
Resolve deferred comments after all definitions are known.
std::vector< std::vector< std::size_t > > signalLines
std::vector< PendingAttribute > pendingAttributes
diff --git a/docs/html/dbc__statement__test_8cpp_source.html b/docs/html/dbc__statement__test_8cpp_source.html new file mode 100644 index 0000000..0f44b3f --- /dev/null +++ b/docs/html/dbc__statement__test_8cpp_source.html @@ -0,0 +1,338 @@ + + + + + + + +DBC Framework: dbc_statement_test.cpp Source File + + + + + + + + + + + + + +
+
+ + + + + + +
+
DBC Framework +
+
DBC parsing and CAN signal decoding framework
+
+
+ + + + + + + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+
+
+
+
+
Loading...
+
Searching...
+
No Matches
+
+
+
+
+ +
+
dbc_statement_test.cpp
+
+
+
+
2#include "dbc_decoder.h"
+
3#include "dbc_parser.h"
+
4#include <QTemporaryFile>
+
5#include <QtTest>
+
6#include <iomanip>
+
7#include <sstream>
+
8#include <stdexcept>
+
9
+
10namespace {
+
11 std::string WriteFixture(QTemporaryFile &file, const std::string &text) {
+
12 if (!file.open() || file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) || !file.flush())
+
13 throw std::runtime_error("Cannot write statement fixture");
+
14 return file.fileName().toStdString();
+
15 }
+
16
+
17 std::string Quote(const std::string &text) {
+
18 std::ostringstream stream;
+
19 stream << std::quoted(text);
+
20 return stream.str();
+
21 }
+
22
+
23 std::string RepeatedRanges() {
+
24 std::string ranges = "1-1";
+
25 for (unsigned index = 1U; index < 10000U; ++index)
+
26 ranges += ",1-1";
+
27 return ranges;
+
28 }
+
29
+
30 const std::string definitions =
+
31 "BO_ 1 Frame: 2 ECU\n"
+
32 "SG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
33 "SG_ Value m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
+
34}// namespace
+
35
+
36class StatementParserTest : public QObject {
+
37 Q_OBJECT
+
38private Q_SLOTS:
+
39 void longValueDescriptions_data();
+
40 void longValueDescriptions();
+
41 void longMultiplexRanges_data();
+
42 void longMultiplexRanges();
+
43 void invalidLongStatements_data();
+
44 void invalidLongStatements();
+
45};
+
46
+
47void StatementParserTest::longValueDescriptions_data() {
+
48 QTest::addColumn<std::string>("description");
+
49 QTest::addColumn<unsigned>("count");
+
50 QTest::addColumn<bool>("beforeDefinitions");
+
51 QTest::newRow("50000-character-description") << std::string(50000U, 'x') << 1U << false;
+
52 std::string escaped;
+
53 for (unsigned index = 0U; index < 12500U; ++index)
+
54 escaped += "\"\\; ";
+
55 QTest::newRow("50000-escaped-characters") << escaped << 1U << true;
+
56 QTest::newRow("10000-pairs") << std::string("Description") << 10000U << true;
+
57 QTest::newRow("50000-spaces") << std::string(50000U, ' ') << 1U << false;
+
58}
+
59
+
60void StatementParserTest::longValueDescriptions() {
+
61 QFETCH(std::string, description);
+
62 QFETCH(unsigned, count);
+
63 QFETCH(bool, beforeDefinitions);
+
64 std::string statement = "VAL_ 1 Value";
+
65 for (unsigned index = 0U; index < count; ++index)
+
66 statement += " " + std::to_string(index) + " " + Quote(description);
+
67 statement += ";\n";
+
68 const std::string frame = "BO_ 1 Frame: 4 ECU\nSG_ Value : 0|32@1+ (1,0) [0|1e10] \"\" ECU\n";
+
69 QTemporaryFile file;
+
70 const std::string path = WriteFixture(file, beforeDefinitions ? statement + frame : frame + statement);
+
71 const DbcParser parser;
+
72 const DbcParseResult result = parser.ParseFileWithDiagnostics(path);
+
73 QVERIFY(!result.HasErrors());
+
74 QVERIFY(result.diagnostics.empty());
+
75 const std::vector<DbcDatabase> databases = {result.database, parser.ParseFile(path)};
+
76 for (const DbcDatabase &database: databases) {
+
77 const ValueDescriptions &values = database.frames.at(0).signals.at(0).valueDescriptions;
+
78 QCOMPARE(values.size(), static_cast<std::size_t>(count));
+
79 for (unsigned index = 0U; index < count; ++index)
+
80 QCOMPARE(values.at(std::uint64_t(index)), description);
+
81 const DecodeDatabase runtime = DbcDecodeBuilder().Build(database);
+
82 RawCanFrame raw;
+
83 raw.canId = 1U;
+
84 const unsigned last = count - 1U;
+
85 raw.data = {static_cast<std::uint8_t>(last), static_cast<std::uint8_t>(last >> 8U), 0U, 0U};
+
86 const DecodedFrameValue decoded = DbcDecoder().Decode(runtime, raw);
+
87 QCOMPARE(decoded.status, FrameDecodeStatus::Valid);
+
88 const std::string *label = decoded.signals.at(0).GetValueDescription();
+
89 QVERIFY(label);
+
90 QCOMPARE(*label, description);
+
91 }
+
92}
+
93
+
94void StatementParserTest::longMultiplexRanges_data() {
+
95 QTest::addColumn<std::string>("ranges");
+
96 QTest::addColumn<unsigned>("count");
+
97 QTest::addColumn<bool>("beforeDefinitions");
+
98 QTest::newRow("10000-ranges") << RepeatedRanges() << 10000U << false;
+
99 const std::string paddedOne = std::string(50000U, '0') + "1";
+
100 QTest::newRow("long-bounds") << paddedOne + "-" + paddedOne << 1U << true;
+
101 QTest::newRow("long-whitespace") << std::string(50000U, ' ') + "1\t-\t1" + std::string(50000U, ' ') << 1U << false;
+
102}
+
103
+
104void StatementParserTest::longMultiplexRanges() {
+
105 QFETCH(std::string, ranges);
+
106 QFETCH(unsigned, count);
+
107 QFETCH(bool, beforeDefinitions);
+
108 const std::string statement = "SG_MUL_VAL_ 1 Value Mode " + ranges + ";\n";
+
109 QTemporaryFile file;
+
110 const std::string path = WriteFixture(file, beforeDefinitions ? statement + definitions : definitions + statement);
+
111 const DbcParser parser;
+
112 const DbcParseResult result = parser.ParseFileWithDiagnostics(path);
+
113 QVERIFY(!result.HasErrors());
+
114 QVERIFY(result.diagnostics.empty());
+
115 const std::vector<DbcDatabase> databases = {result.database, parser.ParseFile(path)};
+
116 for (const DbcDatabase &database: databases) {
+
117 const Multiplexing &mux = database.frames.at(0).signals.at(1).multiplexing;
+
118 QCOMPARE(mux.selector, std::string("Mode"));
+
119 QCOMPARE(mux.ranges.size(), static_cast<std::size_t>(count));
+
120 for (const MultiplexRange &range: mux.ranges) {
+
121 QCOMPARE(range.minimum, std::uint64_t(1));
+
122 QCOMPARE(range.maximum, std::uint64_t(1));
+
123 }
+
124 const DecodeDatabase runtime = DbcDecodeBuilder().Build(database);
+
125 RawCanFrame raw;
+
126 raw.canId = 1U;
+
127 raw.data = {1U, 42U};
+
128 const DecodedFrameValue active = DbcDecoder().Decode(runtime, raw);
+
129 QCOMPARE(active.status, FrameDecodeStatus::Valid);
+
130 QVERIFY(active.signals.at(1).valid);
+
131 QCOMPARE(std::get<std::uint64_t>(active.signals[1].rawValue), std::uint64_t(42));
+
132 raw.data[0] = 2U;
+
133 const DecodedFrameValue inactive = DbcDecoder().Decode(runtime, raw);
+
134 QCOMPARE(inactive.signals.at(1).status, SignalDecodeStatus::Inactive);
+
135 }
+
136}
+
137
+
138void StatementParserTest::invalidLongStatements_data() {
+
139 QTest::addColumn<std::string>("statement");
+
140 QTest::addColumn<DbcDiagnosticCode>("code");
+
141 const std::string text(50000U, 'x');
+
142 const std::string value = "VAL_ 1 Value 0 \"" + text;
+
143 QTest::newRow("val-missing-quote") << value + ";" << DbcDiagnosticCode::InvalidValueDescription;
+
144 QTest::newRow("val-dangling-escape") << value + "\\" << DbcDiagnosticCode::InvalidValueDescription;
+
145 QTest::newRow("val-invalid-escape") << value + "\\q\";" << DbcDiagnosticCode::InvalidValueDescription;
+
146 QTest::newRow("val-missing-semicolon") << value + "\"" << DbcDiagnosticCode::InvalidValueDescription;
+
147 QTest::newRow("val-trailing-text") << value + "\"; junk" << DbcDiagnosticCode::InvalidValueDescription;
+
148 QTest::newRow("val-missing-pair-separator") << value + "\"1 \"On\";" << DbcDiagnosticCode::InvalidValueDescription;
+
149 QTest::newRow("val-missing-number-separator") << "VAL_ 1 Value 0\"" + text + "\";" << DbcDiagnosticCode::InvalidValueDescription;
+
150 QTest::newRow("val-duplicate") << value + "\" 00 \"Duplicate\";" << DbcDiagnosticCode::InvalidValueDescription;
+
151 QTest::newRow("val-overflow") << "VAL_ 1 Value " + std::string(50000U, '9') + " \"Bad\";" << DbcDiagnosticCode::InvalidValueDescription;
+
152 QTest::newRow("val-long-missing-reference") << "VAL_ 1 " + text + " 0 \"Bad\";" << DbcDiagnosticCode::InvalidValueDescription;
+
153 const std::string mux = "SG_MUL_VAL_ 1 Value Mode " + RepeatedRanges();
+
154 QTest::newRow("mux-missing-semicolon") << mux << DbcDiagnosticCode::InvalidMultiplexing;
+
155 QTest::newRow("mux-trailing-comma") << mux + ",;" << DbcDiagnosticCode::InvalidMultiplexing;
+
156 QTest::newRow("mux-empty-range") << mux + ",,1-1;" << DbcDiagnosticCode::InvalidMultiplexing;
+
157 QTest::newRow("mux-reversed-range") << mux + ",2-1;" << DbcDiagnosticCode::InvalidMultiplexing;
+
158 QTest::newRow("mux-signed-bound") << mux + ",+1-1;" << DbcDiagnosticCode::InvalidMultiplexing;
+
159 QTest::newRow("mux-suffix") << mux + "x;" << DbcDiagnosticCode::InvalidMultiplexing;
+
160 QTest::newRow("mux-overflow") << mux + ",0-18446744073709551616;" << DbcDiagnosticCode::InvalidMultiplexing;
+
161 QTest::newRow("mux-long-overflow") << "SG_MUL_VAL_ 1 Value Mode 0-" + std::string(50000U, '9') + ";" << DbcDiagnosticCode::InvalidMultiplexing;
+
162 QTest::newRow("mux-trailing-text") << mux + "; junk" << DbcDiagnosticCode::InvalidMultiplexing;
+
163 QTest::newRow("mux-long-missing-reference") << "SG_MUL_VAL_ 1 " + text + " Mode 1-1;" << DbcDiagnosticCode::InvalidMultiplexing;
+
164}
+
165
+
166void StatementParserTest::invalidLongStatements() {
+
167 QFETCH(std::string, statement);
+
168 QFETCH(DbcDiagnosticCode, code);
+
169 QTemporaryFile file;
+
170 const std::string path = WriteFixture(file, "\n" + statement + "\n" + definitions);
+
171 const DbcParser parser;
+
172 const DbcParseResult result = parser.ParseFileWithDiagnostics(path);
+
173 QVERIFY(result.HasErrors());
+
174 QVERIFY(result.database.frames.empty());
+
175 QCOMPARE(result.diagnostics.size(), std::size_t(1));
+
176 const DbcDiagnostic &error = result.diagnostics[0];
+
177 QCOMPARE(error.severity, DbcDiagnosticSeverity::Error);
+
178 QCOMPARE(error.code, code);
+
179 QCOMPARE(error.filePath, path);
+
180 QCOMPARE(error.lineNumber, std::size_t(2));
+
181 try {
+
182 parser.ParseFile(path);
+
183 QFAIL("Malformed statements must throw through ParseFile");
+
184 } catch (const std::runtime_error &exception) {
+
185 QCOMPARE(std::string(exception.what()), path + ":2: " + error.message);
+
186 }
+
187}
+
188
+
189int RunStatementParserTests(int argc, char **argv) {
+
190 StatementParserTest tests;
+
191 return QTest::qExec(&tests, argc, argv);
+
192}
+
193
+
194#include "dbc_statement_test.moc"
+
Converts parsed DBC data into runtime decode database.
+
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
+
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:87
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
Minimal DBC parser.
Definition dbc_parser.h:45
+
DbcParseResult ParseFileWithDiagnostics(const std::string &filePath) const
Parse with warnings and a structured first error instead of throwing.
+
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
+
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
+
Runtime decode database builder.
+
CAN frame decoding API and result types.
+
DBC file parser and interpretation options.
+
Parsed DBC content stored in a simple internal form.
+
std::vector< FrameInfo > frames
+
Diagnostic message and physical source location.
+
std::string message
+
DbcDiagnosticSeverity severity
+
DbcDiagnosticCode code
+
std::string filePath
+
std::size_t lineNumber
+
Parsed database and diagnostics; the database is empty on error.
+
std::vector< DbcDiagnostic > diagnostics
+
bool HasErrors() const
Return whether any diagnostic has error severity.
+
DbcDatabase database
+
Fully decoded frame.
Definition dbc_decoder.h:74
+ +
FrameDecodeStatus status
Definition dbc_decoder.h:79
+
Inclusive raw selector range, without expanding individual values.
+
Basic markers and optional explicit extended multiplexing condition.
+
std::vector< MultiplexRange > ranges
+
std::string selector
+
Raw CAN frame used for runtime or trace decoding.
Definition dbc_decoder.h:25
+
std::vector< std::uint8_t > data
Definition dbc_decoder.h:28
+
std::uint32_t canId
Definition dbc_decoder.h:26
+
+
+ + + + diff --git a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html index 5207138..a81ea0b 100644 --- a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html +++ b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html @@ -106,6 +106,8 @@ Files    dbc_pipeline_test.cpp   + dbc_statement_test.cpp +   dbc_tree_model_test.cpp    dbc_validation_test.cpp diff --git a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js index 4fc2bf7..46b5cbb 100644 --- a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js +++ b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js @@ -5,6 +5,7 @@ var dir_59425e443f801f1f2fd8bbe4959a3ccf = [ "dbc_lifetime_test.cpp", "dbc__lifetime__test_8cpp_source.html", null ], [ "dbc_parser_test.cpp", "dbc__parser__test_8cpp_source.html", null ], [ "dbc_pipeline_test.cpp", "dbc__pipeline__test_8cpp_source.html", null ], + [ "dbc_statement_test.cpp", "dbc__statement__test_8cpp_source.html", null ], [ "dbc_tree_model_test.cpp", "dbc__tree__model__test_8cpp_source.html", null ], [ "dbc_validation_test.cpp", "dbc__validation__test_8cpp_source.html", null ], [ "tests/main.cpp", "tests_2main_8cpp_source.html", null ], diff --git a/docs/html/files.html b/docs/html/files.html index 146d728..308aa06 100644 --- a/docs/html/files.html +++ b/docs/html/files.html @@ -135,10 +135,11 @@ $(document).ready(function(){initNavTree('files.html',''); initResizable(); });  dbc_lifetime_test.cpp  dbc_parser_test.cpp  dbc_pipeline_test.cpp - dbc_tree_model_test.cpp - dbc_validation_test.cpp - main.cpp - tree_node_test.cpp + dbc_statement_test.cpp + dbc_tree_model_test.cpp + dbc_validation_test.cpp + main.cpp + tree_node_test.cpp
diff --git a/docs/html/index.qhp b/docs/html/index.qhp index f2c5a58..5ea7412 100644 --- a/docs/html/index.qhp +++ b/docs/html/index.qhp @@ -168,9 +168,10 @@
-
-
-
+
+
+
+
@@ -779,6 +780,7 @@
+
@@ -1020,9 +1022,9 @@ + - @@ -1180,6 +1182,7 @@ dbc__pipeline__test_8cpp_source.html dbc__statement__handler_8cpp_source.html dbc__statement__handler_8h_source.html +dbc__statement__test_8cpp_source.html dbc__tree__builder_8cpp.html dbc__tree__builder_8cpp_source.html dbc__tree__builder_8h.html diff --git a/docs/html/md_README.html b/docs/html/md_README.html index 994ef31..7e19e3d 100644 --- a/docs/html/md_README.html +++ b/docs/html/md_README.html @@ -645,7 +645,7 @@ Multiplexing
SG_ Value m0 : 16|8@1+ (1,0) [0|255] "" ECU
SG_MUL_VAL_ 1 Child Root 1-2, 4-4;
SG_MUL_VAL_ 1 Value Child 3-5;
-

Here Value is active when Root is 1, 2, or 4 and Child is 3 through 5. Explicit ranges override the marker value. Repeated statements for the same signal/selector merge their range alternatives. Each signal has at most one parent selector; conflicting parent references are rejected. Multiple independent root selectors are supported. Statements and selectors may precede or follow their targets. Decimal values retain the full uint64_t range and must fit the parent's bit width. Ranges are stored without expanding individual values. Malformed ranges, unresolved references, signed selectors, ambiguous implicit selectors, and dependency cycles produce InvalidMultiplexing diagnostics. Statement errors retain the SG_MUL_VAL_ source line; graph and overlap errors identify the affected signal declaration.

+

Here Value is active when Root is 1, 2, or 4 and Child is 3 through 5. Explicit ranges override the marker value. Repeated statements for the same signal/selector merge their range alternatives. Each signal has at most one parent selector; conflicting parent references are rejected. Multiple independent root selectors are supported. Statements and selectors may precede or follow their targets. Decimal values retain the full uint64_t range and must fit the parent's bit width. Ranges are stored without expanding individual values. SG_MUL_VAL_ headers, decimal bounds, and range lists are scanned iteratively, without regular expressions or a new statement-length limit. Long valid lists are preserved; malformed ranges and trailing text produce source-located errors. Malformed ranges, unresolved references, signed selectors, ambiguous implicit selectors, and dependency cycles produce InvalidMultiplexing diagnostics. Statement errors retain the SG_MUL_VAL_ source line; graph and overlap errors identify the affected signal declaration.

Signal bits may overlap only when the complete ancestor conditions prove the signals mutually exclusive: at least one shared selector has disjoint ranges. This includes nested siblings using the same payload bits and independent selectors, whose branches may be active simultaneously. Validation uses Intel or Motorola bit traversal, including mixed byte orders. Metadata survives runtime and tree builders; the optional Qt adapter does not add multiplexing roles yet.

The decoder checks ancestors from root to leaf using raw unsigned values. An inactive ancestor leaves descendants inactive even if their selector bytes are absent. Missing bytes on an otherwise active selector path produce SelectorUnavailable. Selective decoding reads required ancestors internally; result order remains selection or definition order. Factor, offset, and VAL_ descriptions do not influence branch selection.

Each result has SignalDecodeStatus status:

@@ -876,7 +876,7 @@ Signal value descriptions (<tt>VAL_</tt>)
BO_ 256 Status: 8 ECU
SG_ State : 0|8@1+ (1,0) [0|255] "" ECU
VAL_ 256 State 0 "Off" 1 "On" 2 "Error";
-

Each statement must occupy one physical line, contain at least one integer/text pair, and end with a semicolon. Spaces and tabs are accepted between fields; descriptions may contain whitespace, semicolons, and empty text. Inside quoted text, escape a quote or a backslash with a preceding backslash; other escapes are rejected. Keys are decimal integers in INT64_MIN..UINT64_MAX, with an optional minus sign; plus signs, fractions, hexadecimal numbers, exponents, and overflow are rejected. Keys never pass through double.

+

Each statement must occupy one physical line, contain at least one integer/text pair, and end with a semicolon. Spaces and tabs are accepted between fields; descriptions may contain whitespace, semicolons, and empty text. Inside quoted text, escape a quote or a backslash with a preceding backslash; other escapes are rejected. Headers, quoted descriptions, and pairs are scanned iteratively, without regular expressions or repeatedly removing the parsed prefix. Long valid descriptions and lists are preserved without a new length limit; malformed input returns the same source-located diagnostics as short statements. Keys are decimal integers in INT64_MIN..UINT64_MAX, with an optional minus sign; plus signs, fractions, hexadecimal numbers, exponents, and overflow are rejected. Keys never pass through double.

VAL_ may appear before or after its frame and signal declarations. References are resolved after reading the file using the same CAN ID normalization as BO_, including bit 31 for extended identifiers. Standard and extended frames with the same normalized ID remain distinct. Missing or ambiguous references, malformed statements, and duplicate integer keys produce path:line: description errors at the originating VAL_ line. Distinct keys from multiple statements are merged; 0, 00, and -0 are the same key. Keys need not fit the referenced signal's bit width or signedness; unreachable keys simply cannot match a decode.

Mappings are available as SignalInfo::valueDescriptions and DecodeSignal::valueDescriptions, and survive both database builders, including TreeNode::GetSignal(). Consumers can look up a decoded result directly:

const DecodedSignalValue &value = decodedFrame.signals.at(0);
@@ -972,7 +972,7 @@ Build Integration

core/core_files.pri lists the library sources and headers. Consumers include core/core.pri for include paths and library linkage; the test source lists live in tests/parser_tests.pri and tests/decoder_tests.pri. When building a consumer separately, pass DBC_CORE_BUILD_DIR=/absolute/path/to/build/core to qmake.

Local core regression checks

-

Run bash tests/run_core_ci.sh release or bash tests/run_core_ci.sh sanitize to build the core archive with Qt 6.8.2, run all six core QtTest suites (parser, decoder, coverage, lifetime, validation, and pipeline/builder integration), and run both the qmake and standalone Qt-free tree tests. The sanitizer mode uses AddressSanitizer and UndefinedBehaviorSanitizer, with leak detection enabled by default and fatal sanitizer errors.

+

Run bash tests/run_core_ci.sh release or bash tests/run_core_ci.sh sanitize to build the core archive with Qt 6.8.2, run all seven core QtTest suites (parser, statement robustness, decoder, coverage, lifetime, validation, and pipeline/builder integration), and run both the qmake and standalone Qt-free tree tests. The sanitizer mode uses AddressSanitizer and UndefinedBehaviorSanitizer, with leak detection enabled by default and fatal sanitizer errors.

See Local regression checks for prerequisites and environment overrides. No automatic CI workflow is configured.

Build core without Qt libraries

@@ -1080,7 +1080,7 @@ Regression tests

See tests/README.md for QtTest build and execution commands. Tests cover frame validation and error locations, Intel and Motorola extraction, signal syntax and payload bounds, VAL_ syntax and reference validation, raw description lookup and preservation through both builders, statement dispatch, signed and unsigned 64-bit boundaries, physical conversion, numeric output, and invalid signal payloads. dbc_pipeline_test.cpp follows temporary DBC fixtures through parsed metadata, runtime frame/signal lookups, decoded raw/physical values and descriptions, and tree nodes. It verifies all supported metadata in both builders, standard and extended frames sharing a numeric ID, empty databases, comments, and replacement with another fixture or an empty database.

Code review and additional checks

-

Attribute numeric parsing shares one locale-independent converter for integer and floating-point values. Default application skips unrelated scopes, CAN ID normalization uses the validated identifier mask, and signal layout validation owns the length checks. Statement handlers continue to use TypeFactory. Core, adapter, example, and test C++ sources use explicit return types and contain no lambdas. Public metadata and parser-state comments use Doxygen descriptions.

+

Attribute numeric parsing shares one locale-independent converter for integer and floating-point values. Default application skips unrelated scopes, CAN ID normalization uses the validated identifier mask, and signal layout validation owns the length checks. Statement handlers continue to use TypeFactory. Core, adapter, example, and test C++ sources use explicit return types and contain no lambdas. DBC statement parsing uses iterative scanners rather than regular expressions. Public metadata and parser-state comments use Doxygen descriptions.

See tests/README.md for attribute boundary regressions, sanitizer commands, and the latest validation results.

Measuring code coverage

diff --git a/docs/html/md_changelog.html b/docs/html/md_changelog.html index d449a6f..30d4825 100644 --- a/docs/html/md_changelog.html +++ b/docs/html/md_changelog.html @@ -115,6 +115,8 @@ Testing

Fixed

    +
  • Replace the remaining VAL_ and SG_MUL_VAL_ regular expressions with a shared forward-only statement reader. Long descriptions and range lists no longer exhaust the stack. Preserve quoted escapes, exact integer values, deferred references, duplicate checks, and source-located diagnostics without imposing a new statement-length limit. Avoid repeatedly erasing VAL_ prefixes.
  • +
  • Add and register a dedicated statement robustness suite for 50,000-character fields, 10,000 value pairs/ranges, malformed long inputs, both parser APIs, metadata preservation, value lookup, and active/inactive branch decoding. Release and ASan/UBSan builds with the optional Qt adapter each pass 2,449 core checks, seven model checks, and the qmake tree test. One locale case skips; leak checking remains disabled due to ptrace.
  • Reject all SIG_VALTYPE_ declarations with a source-located UnsupportedSignalType error instead of silently decoding their payload as integers. Both parser APIs reject the unsupported operator, including type 0; the diagnostic API publishes no partial database. Namespace symbol listings remain quiet. No permissive mode is introduced.
  • Display parser warnings and errors in the demo, and stop before building or decoding when parsing fails. Add parser and pipeline regressions for float32, float64, source locations, declaration order, and unchanged integer decoding. Verify Release and ASan/UBSan builds with the optional Qt adapter: 2,420 core checks, seven model checks, and the qmake tree test pass in each build. One named-locale case skips; leak checking remains disabled due to ptrace. Build the demo and verify error exits, visible warnings, and integer input.
  • Replace recursive regular-expression matching for SG_ statements with an iterative field scanner. Long unit strings no longer overflow the stack.
  • diff --git a/docs/html/md_tests_2README.html b/docs/html/md_tests_2README.html index 8ed1d8b..6da8a0a 100644 --- a/docs/html/md_tests_2README.html +++ b/docs/html/md_tests_2README.html @@ -109,10 +109,10 @@ $(document).ready(function(){initNavTree('md_tests_2README.html',''); initResiza
    DBC_CORE_BUILD_DIR=/absolute/path/to/build/core
    make
    make check
    -

parser_tests.pri and decoder_tests.pri include the original test sources; tests.pro also includes dbc_coverage_test.cpp, dbc_lifetime_test.cpp, dbc_validation_test.cpp, and dbc_pipeline_test.cpp. ../core/core.pri supplies the library include path and linkage.

+

parser_tests.pri includes dbc_parser_test.cpp and dbc_statement_test.cpp; decoder_tests.pri includes the decoder test source; tests.pro also includes dbc_coverage_test.cpp, dbc_lifetime_test.cpp, dbc_validation_test.cpp, and dbc_pipeline_test.cpp. ../core/core.pri supplies the library include path and linkage.

Local regression checks

-

run_core_ci.sh builds the core archive, runs all six QtTest suites and the qmake tree target with make check, then compiles and runs the standalone C++17 tree/builder test without Qt. A failed command returns a nonzero status. The sanitize mode applies ASan/UBSan flags to core, tests, and the standalone executable, with leak detection enabled by default and sanitizer errors fatal. The optional adapter is not built. No automatic CI workflow is configured.

+

run_core_ci.sh builds the core archive, runs all seven QtTest suites and the qmake tree target with make check, then compiles and runs the standalone C++17 tree/builder test without Qt. A failed command returns a nonzero status. The sanitize mode applies ASan/UBSan flags to core, tests, and the standalone executable, with leak detection enabled by default and sanitizer errors fatal. The optional adapter is not built. No automatic CI workflow is configured.

From the repository root, with Qt 6.8.2 available:

bash tests/run_core_ci.sh release
bash tests/run_core_ci.sh sanitize
@@ -160,15 +160,20 @@ Attribute coverage

Parser slots attributes, invalidAttributes, and attributeWarnings cover all five scopes and types, deferred definitions/defaults/targets, explicit values overriding defaults, repeated assignments, escaped strings, typed range/scope errors, duplicate definitions/defaults, unknown attributes and missing targets. They check source diagnostics and the throwing API, distinct standard/extended targets, frame cycle times and enum labels, unsupported formats and ID conflicts, unchanged J1939 opt-in, and runtime/tree propagation. Existing unsupported-statement checks now use relation forms, which remain unsupported, instead of supported attribute forms.

Attribute changes were verified with qmake6 ../dbc.pro, make -j4, and make check in build/: all six QtTest suites and the qmake tree target passed. The standalone C++17 tree test also passed. The existing named comma-decimal C locale test skipped because the locale is not installed.

+Long value descriptions and multiplex ranges

+

dbc_statement_test.cpp adds a separate StatementParserTest suite to the existing parser qmake source list and the main test runner. Both parser APIs are checked with 50,000-character descriptions (including quotes, backslashes, semicolons, and spaces), 10,000 VAL_ pairs, 10,000 multiplex ranges, and long numeric bounds and whitespace. Valid metadata must survive parsing without truncation, support value-description lookup after decoding, and activate or skip multiplex branches correctly. Statements may precede or follow definitions.

+

Malformed long statements cover incomplete quotes/escapes, invalid escapes, missing separators and semicolons, duplicate keys, numeric overflow, empty and reversed ranges, trailing text, and unresolved long names. The diagnostic API must return an empty database with the correct category, path, and source line; the throwing API must report that same location.

+

Verified with Qt 6.8.2 in separate Release and ASan/UBSan builds with CONFIG+=dbc_qt: each passed 2,449 core QtTest checks (including 29 checks in the new suite), seven model checks, and the qmake tree test. One named-locale case skipped because the locale is unavailable. Sanitizer errors were fatal; local leak checking used detect_leaks=0 because of the documented ptrace restriction.

+

Unsupported signal value types

unsupportedSignalValueTypes verifies that both parser APIs reject SIG_VALTYPE_ declarations with UnsupportedSignalType, the original file path and physical line, and an empty database. Cases include float32/float64, declarations before signal definitions, extended IDs, tabs/CRLF, no final newline, type 0, unknown types, and malformed declarations. Prior warnings are retained; SIG_VALTYPE_ symbols inside NS_ lists remain quiet.

The pipeline suite checks the IEEE 754 bytes for float32 and float64 1.0. Adding a type declaration must prevent publication of an integer definition, so building from the error result cannot produce a valid decoded signal. The same payloads still decode to exact integers when defined only by supported SG_ statements. New cases are included in the existing qmake targets.

Verified with Qt 6.8.2 in separate Release and ASan/UBSan builds with CONFIG+=dbc_qt: each passed 2,420 core QtTest checks, seven model checks, and the qmake tree test. One named-locale case skipped because its locale is unavailable. ASan/UBSan used fatal errors and detect_leaks=0 because of the previously documented ptrace restriction. The separately built demo was checked with float32 and float64 declarations (exit 2, source-located error, no decode output), a supported integer definition (exit 0), and an unknown extension (exit 0 with a visible warning).

-

+

Long signal definitions

longSignalDefinitions reproduces the former stack overflow with a 50,000-character unit string. Both parser APIs are exercised with long units, signal names, receivers, decimal numbers, and a list of 10,001 receivers. Valid input must parse without truncating units; malformed input (including missing quotes, delimiters, receiver separators, numeric suffixes, overflow, and trailing receiver commas) must return an empty database and an InvalidStatement diagnostic at the signal's source line, or throw a source-located error through ParseFile(). These cases run as part of the existing parser target and sanitizer builds.

Verified with Qt 6.8.2 in separate Release and ASan/UBSan builds, both with CONFIG+=dbc_qt: make check passed 2,409 core QtTest checks, seven model checks, and the qmake tree test in each build. The named comma-decimal C locale test skipped because the required locale is not installed. The sanitizer run used ASAN_OPTIONS=detect_leaks=0:halt_on_error=1 and UBSAN_OPTIONS=halt_on_error=1; address and undefined-behavior checks passed, while leak checking was disabled because of the environment's previously documented ptrace restriction.

-

+

Extended validation

attributeNumbers checks signed 64-bit limits, leading plus signs, decimal HEX values, FLOAT fractions/exponents, and default overrides under a comma-decimal C++ locale. Node/environment defaults remain on definitions without becoming database attributes. invalidAttributeNumbers supplies 39 data rows, each checked in both definition bounds, a default, and an explicit assignment. The throwing and diagnostic APIs must reject malformed signs, trailing text, hexadecimal notation, non-finite values, overflow, and underflow with the correct error line. Additional malformed-attribute cases cover token separators, scope/type names, reversed FLOAT bounds, value types/ranges, and invalid CAN references.

Run AddressSanitizer and UndefinedBehaviorSanitizer in a separate directory:

diff --git a/docs/html/navtreedata.js b/docs/html/navtreedata.js index a9eb8eb..23dc634 100644 --- a/docs/html/navtreedata.js +++ b/docs/html/navtreedata.js @@ -181,9 +181,10 @@ var NAVTREE = [ "Database validation coverage", "md_tests_2README.html#autotoc_md167", null ], [ "End-to-end pipeline coverage", "md_tests_2README.html#autotoc_md168", null ], [ "Attribute coverage", "md_tests_2README.html#autotoc_md169", null ], - [ "Unsupported signal value types", "md_tests_2README.html#autotoc_md170", null ], - [ "Long signal definitions", "md_tests_2README.html#autotoc_md171", null ], - [ "Extended validation", "md_tests_2README.html#autotoc_md172", null ] + [ "Long value descriptions and multiplex ranges", "md_tests_2README.html#autotoc_md170", null ], + [ "Unsupported signal value types", "md_tests_2README.html#autotoc_md171", null ], + [ "Long signal definitions", "md_tests_2README.html#autotoc_md172", null ], + [ "Extended validation", "md_tests_2README.html#autotoc_md173", null ] ] ], [ "Classes", "annotated.html", [ [ "Class List", "annotated.html", "annotated_dup" ], @@ -211,7 +212,7 @@ var NAVTREE = var NAVTREEINDEX = [ "annotated.html", -"md_README.html#autotoc_md85" +"md_README.html#autotoc_md83" ]; var SYNCONMSG = 'click to disable panel synchronisation'; diff --git a/docs/html/navtreeindex0.js b/docs/html/navtreeindex0.js index fe46832..2242d62 100644 --- a/docs/html/navtreeindex0.js +++ b/docs/html/navtreeindex0.js @@ -141,14 +141,15 @@ var NAVTREEINDEX0 = "dbc__pipeline__test_8cpp_source.html":[5,0,3,4], "dbc__statement__handler_8cpp_source.html":[5,0,0,10], "dbc__statement__handler_8h_source.html":[5,0,0,11], +"dbc__statement__test_8cpp_source.html":[5,0,3,5], "dbc__tree__builder_8cpp.html":[5,0,0,12], "dbc__tree__builder_8cpp_source.html":[5,0,0,12], "dbc__tree__builder_8h.html":[5,0,0,13], "dbc__tree__builder_8h_source.html":[5,0,0,13], "dbc__tree__model_8cpp_source.html":[5,0,2,0], "dbc__tree__model_8h_source.html":[5,0,2,1], -"dbc__tree__model__test_8cpp_source.html":[5,0,3,5], -"dbc__validation__test_8cpp_source.html":[5,0,3,6], +"dbc__tree__model__test_8cpp_source.html":[5,0,3,6], +"dbc__validation__test_8cpp_source.html":[5,0,3,7], "decode__database_8cpp_source.html":[5,0,0,14], "decode__database_8h.html":[5,0,0,15], "decode__database_8h.html#aaeb92d42f5a6e27b8ba19f18d69d142b":[5,0,0,15,4], @@ -248,6 +249,5 @@ var NAVTREEINDEX0 = "md_README.html#autotoc_md76":[2,15], "md_README.html#autotoc_md78":[2,16], "md_README.html#autotoc_md80":[2,17], -"md_README.html#autotoc_md82":[2,18], -"md_README.html#autotoc_md83":[2,19] +"md_README.html#autotoc_md82":[2,18] }; diff --git a/docs/html/navtreeindex1.js b/docs/html/navtreeindex1.js index ef20a31..23ab8b5 100644 --- a/docs/html/navtreeindex1.js +++ b/docs/html/navtreeindex1.js @@ -1,5 +1,6 @@ var NAVTREEINDEX1 = { +"md_README.html#autotoc_md83":[2,19], "md_README.html#autotoc_md85":[2,20], "md_README.html#autotoc_md87":[2,20,0], "md_README.html#autotoc_md89":[2,21], @@ -63,6 +64,7 @@ var NAVTREEINDEX1 = "md_tests_2README.html#autotoc_md170":[3,9], "md_tests_2README.html#autotoc_md171":[3,10], "md_tests_2README.html#autotoc_md172":[3,11], +"md_tests_2README.html#autotoc_md173":[3,12], "multiplex__graph_8h_source.html":[5,0,0,19], "multiplexing_8h_source.html":[5,0,0,20], "pages.html":[], @@ -231,7 +233,7 @@ var NAVTREEINDEX1 = "structSignalKey.html#abab371cce740f05111fd3b4013ca6944":[4,0,41,3], "structSignalKeyHasher.html":[4,0,42], "structSignalKeyHasher.html#af8869bcb714857af7ad005fbd13bea80":[4,0,42,0], -"tests_2main_8cpp_source.html":[5,0,3,7], +"tests_2main_8cpp_source.html":[5,0,3,8], "text__utils_8h_source.html":[5,0,0,23], "tree__node_8cpp.html":[5,0,0,24], "tree__node_8cpp_source.html":[5,0,0,24], @@ -241,7 +243,7 @@ var NAVTREEINDEX1 = "tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16a3bb3e8c8a24891ba0f7608bcc96f8b0a":[5,0,0,25,1,1], "tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16afa03eb688ad8aa1db593d33dabd89bad":[5,0,0,25,1,0], "tree__node_8h_source.html":[5,0,0,25], -"tree__node__test_8cpp_source.html":[5,0,3,8], +"tree__node__test_8cpp_source.html":[5,0,3,9], "typefactory_8h_source.html":[5,0,0,26], "value__descriptions_8h_source.html":[5,0,0,27] }; diff --git a/docs/html/search/all_10.js b/docs/html/search/all_10.js index bb1e80a..f954909 100644 --- a/docs/html/search/all_10.js +++ b/docs/html/search/all_10.js @@ -13,6 +13,7 @@ var searchData= ['live_20path_10',['live path',['../md_README.html#autotoc_md127',1,'']]], ['local_20core_20regression_20checks_11',['Local core regression checks',['../md_README.html#autotoc_md147',1,'']]], ['local_20regression_20checks_12',['Local regression checks',['../md_tests_2README.html#autotoc_md161',1,'']]], - ['long_20signal_20definitions_13',['Long signal definitions',['../md_tests_2README.html#autotoc_md171',1,'']]], - ['lookup_14',['J1939 Opt-in and PGN Lookup',['../md_README.html#autotoc_md114',1,'']]] + ['long_20signal_20definitions_13',['Long signal definitions',['../md_tests_2README.html#autotoc_md172',1,'']]], + ['long_20value_20descriptions_20and_20multiplex_20ranges_14',['Long value descriptions and multiplex ranges',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['lookup_15',['J1939 Opt-in and PGN Lookup',['../md_README.html#autotoc_md114',1,'']]] ]; diff --git a/docs/html/search/all_11.js b/docs/html/search/all_11.js index 18a1b68..d59f568 100644 --- a/docs/html/search/all_11.js +++ b/docs/html/search/all_11.js @@ -6,14 +6,15 @@ var searchData= ['measuring_20code_20coverage_3',['Measuring code coverage',['../md_README.html#autotoc_md156',1,'']]], ['message_4',['message',['../structDbcDiagnostic.html#a124bae82eebeaef46426bd124685ef1b',1,'DbcDiagnostic']]], ['metadata_5',['Attributes and frame metadata',['../md_README.html#autotoc_md97',1,'']]], - ['minimum_6',['minimum',['../structSignalInfo.html#ada7bf94fbeaa26bd08c73982b5af255b',1,'SignalInfo::minimum'],['../structDecodeSignal.html#a0fb84e19a0ab3bd50b6f65c1075ebc25',1,'DecodeSignal::minimum'],['../structAttributeDefinition.html#a18245dba995929a61fd6de1fcdbc8a89',1,'AttributeDefinition::minimum']]], + ['minimum_6',['minimum',['../structAttributeDefinition.html#a18245dba995929a61fd6de1fcdbc8a89',1,'AttributeDefinition::minimum'],['../structDecodeSignal.html#a0fb84e19a0ab3bd50b6f65c1075ebc25',1,'DecodeSignal::minimum'],['../structSignalInfo.html#ada7bf94fbeaa26bd08c73982b5af255b',1,'SignalInfo::minimum']]], ['model_20adapter_7',['Optional Qt model adapter',['../md_README.html#autotoc_md149',1,'']]], ['model_20tests_8',['Tree and optional model tests',['../md_tests_2README.html#autotoc_md164',1,'']]], ['module_20documentation_9',['DBC Parser and Runtime Decode Module Documentation',['../md_README.html',1,'']]], ['module_20layout_10',['Module Layout',['../md_README.html#autotoc_md55',1,'']]], ['motorola_20extraction_11',['Intel and Motorola Extraction',['../md_README.html#autotoc_md118',1,'']]], - ['multiplexerror_12',['MultiplexError',['../classdbc__detail_1_1MultiplexError.html',1,'dbc_detail']]], - ['multiplexgraph_13',['MultiplexGraph',['../classdbc__detail_1_1MultiplexGraph.html',1,'dbc_detail']]], - ['multiplexing_14',['multiplexing',['../md_README.html#autotoc_md108',1,'Multiplexing'],['../structSignalInfo.html#a8a2e29f307c75926a1bd824266d23ca8',1,'SignalInfo::multiplexing'],['../structDecodeSignal.html#aea41c055cb255fe7bd178ed5727f28c9',1,'DecodeSignal::multiplexing'],['../structMultiplexing.html',1,'Multiplexing']]], - ['multiplexrange_15',['MultiplexRange',['../structMultiplexRange.html',1,'']]] + ['multiplex_20ranges_12',['Long value descriptions and multiplex ranges',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['multiplexerror_13',['MultiplexError',['../classdbc__detail_1_1MultiplexError.html',1,'dbc_detail']]], + ['multiplexgraph_14',['MultiplexGraph',['../classdbc__detail_1_1MultiplexGraph.html',1,'dbc_detail']]], + ['multiplexing_15',['multiplexing',['../structSignalInfo.html#a8a2e29f307c75926a1bd824266d23ca8',1,'SignalInfo::multiplexing'],['../md_README.html#autotoc_md108',1,'Multiplexing'],['../structDecodeSignal.html#aea41c055cb255fe7bd178ed5727f28c9',1,'DecodeSignal::multiplexing'],['../structMultiplexing.html',1,'Multiplexing']]], + ['multiplexrange_16',['MultiplexRange',['../structMultiplexRange.html',1,'']]] ]; diff --git a/docs/html/search/all_16.js b/docs/html/search/all_16.js index e773b32..8a67bf5 100644 --- a/docs/html/search/all_16.js +++ b/docs/html/search/all_16.js @@ -1,14 +1,14 @@ var searchData= [ - ['ranges_0',['ranges',['../structMultiplexing.html#aa174ac4a3af9b361d2de7517dfd4b69e',1,'Multiplexing']]], + ['ranges_0',['ranges',['../md_tests_2README.html#autotoc_md170',1,'Long value descriptions and multiplex ranges'],['../structMultiplexing.html#aa174ac4a3af9b361d2de7517dfd4b69e',1,'Multiplexing::ranges']]], ['raw_20frame_1',['Decode a raw frame',['../md_README.html#autotoc_md124',1,'']]], ['raw_20value_20representation_20and_20precision_2',['Raw value representation and precision',['../md_README.html#autotoc_md87',1,'']]], ['rawcanframe_3',['RawCanFrame',['../structRawCanFrame.html',1,'']]], ['rawcanframe_20tt_4',['<tt>RawCanFrame</tt>',['../md_README.html#autotoc_md83',1,'']]], ['rawvalue_5',['rawValue',['../structDecodedSignalValue.html#a503c90b4b1a610b8252c45e466367c28',1,'DecodedSignalValue']]], - ['receivers_6',['receivers',['../structSignalInfo.html#a2c95562fd0b29327a7fd7b1e6feee7f3',1,'SignalInfo::receivers'],['../structDecodeSignal.html#abdbae62a64d2df716bf60c6b67b30eaa',1,'DecodeSignal::receivers']]], + ['receivers_6',['receivers',['../structDecodeSignal.html#abdbae62a64d2df716bf60c6b67b30eaa',1,'DecodeSignal::receivers'],['../structSignalInfo.html#a2c95562fd0b29327a7fd7b1e6feee7f3',1,'SignalInfo::receivers']]], ['recommended_20next_20steps_7',['Recommended Next Steps',['../md_README.html#autotoc_md140',1,'']]], - ['registertype_8',['registertype',['../classTypeFactory.html#ad6d85795285ee21193ac03e1b24762d3',1,'TypeFactory::registerType()'],['../classDbcStatementDispatcher.html#a2c6e9a614ed747275d43bcccc50d0b2d',1,'DbcStatementDispatcher::RegisterType()']]], + ['registertype_8',['registertype',['../classDbcStatementDispatcher.html#a2c6e9a614ed747275d43bcccc50d0b2d',1,'DbcStatementDispatcher::RegisterType()'],['../classTypeFactory.html#ad6d85795285ee21193ac03e1b24762d3',1,'TypeFactory::registerType()']]], ['regression_20checks_9',['regression checks',['../md_README.html#autotoc_md147',1,'Local core regression checks'],['../md_tests_2README.html#autotoc_md161',1,'Local regression checks']]], ['regression_20coverage_10',['Additional regression coverage',['../md_tests_2README.html#autotoc_md165',1,'']]], ['regression_20tests_11',['regression tests',['../md_tests_2README.html',1,'QtTest regression tests'],['../md_README.html#autotoc_md154',1,'Regression tests']]], diff --git a/docs/html/search/all_17.js b/docs/html/search/all_17.js index bb7eabb..e6fdca1 100644 --- a/docs/html/search/all_17.js +++ b/docs/html/search/all_17.js @@ -5,11 +5,11 @@ var searchData= ['settree_2',['SetTree',['../classDbcTreeModel.html#a8cb5a81d456cdead42b75fd3462ee00a',1,'DbcTreeModel']]], ['severity_3',['severity',['../structDbcDiagnostic.html#a75040a94f0313a606478020b0491f81d',1,'DbcDiagnostic']]], ['signal_20definition_4',['Signal definition',['../md_README.html#autotoc_md104',1,'']]], - ['signal_20definitions_5',['Long signal definitions',['../md_tests_2README.html#autotoc_md171',1,'']]], + ['signal_20definitions_5',['Long signal definitions',['../md_tests_2README.html#autotoc_md172',1,'']]], ['signal_20identifiers_6',['Stable frame and signal identifiers',['../md_README.html#autotoc_md91',1,'']]], ['signal_20validation_20and_20parse_20errors_7',['Signal validation and parse errors',['../md_README.html#autotoc_md106',1,'']]], ['signal_20value_20descriptions_20tt_20val_5f_20tt_8',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], - ['signal_20value_20types_9',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['signal_20value_20types_9',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md171',1,'']]], ['signal_5finfo_2eh_10',['signal_info.h',['../signal__info_8h.html',1,'']]], ['signaldecodestatus_11',['SignalDecodeStatus',['../dbc__decoder_8h.html#a5f4a41ee2810487020135b45e8d7559a',1,'dbc_decoder.h']]], ['signalinfo_12',['SignalInfo',['../structSignalInfo.html',1,'']]], diff --git a/docs/html/search/all_18.js b/docs/html/search/all_18.js index 3d2746e..ca37960 100644 --- a/docs/html/search/all_18.js +++ b/docs/html/search/all_18.js @@ -38,5 +38,5 @@ var searchData= ['type_35',['type',['../structAttributeDefinition.html#a5de5484174a8c5f6b309041089d20afc',1,'AttributeDefinition']]], ['typefactory_36',['TypeFactory',['../classTypeFactory.html',1,'']]], ['typefactory_3c_20std_3a_3astring_2c_20dbcstatementhandler_20_3e_37',['TypeFactory< std::string, DbcStatementHandler >',['../classTypeFactory.html',1,'']]], - ['types_38',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md170',1,'']]] + ['types_38',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md171',1,'']]] ]; diff --git a/docs/html/search/all_19.js b/docs/html/search/all_19.js index 27d47b3..1bbd464 100644 --- a/docs/html/search/all_19.js +++ b/docs/html/search/all_19.js @@ -7,7 +7,7 @@ var searchData= ['unified_20decode_20strategy_4',['Unified Decode Strategy',['../md_README.html#autotoc_md126',1,'']]], ['unit_5',['unit',['../structDecodeSignal.html#a38eed9ca7581423fd8bfbedcffc12092',1,'DecodeSignal::unit'],['../structSignalInfo.html#a1f214f03e94ef5794de2d3367bfcbd01',1,'SignalInfo::unit']]], ['unreleased_6',['Unreleased',['../md_changelog.html#autotoc_md7',1,'']]], - ['unsupported_20signal_20value_20types_7',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['unsupported_20signal_20value_20types_7',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md171',1,'']]], ['usage_8',['Example Usage',['../md_README.html#autotoc_md120',1,'']]], ['use_20in_20frametap_9',['Intended Use in FrameTap',['../md_README.html#autotoc_md130',1,'']]] ]; diff --git a/docs/html/search/all_1a.js b/docs/html/search/all_1a.js index 8dbe1b5..cb6c9ae 100644 --- a/docs/html/search/all_1a.js +++ b/docs/html/search/all_1a.js @@ -4,15 +4,16 @@ var searchData= ['val_5f_20tt_1',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], ['valid_2',['valid',['../structDecodedFrameValue.html#a4f51c31110dba1f73a763c596372beb0',1,'DecodedFrameValue::valid'],['../structDecodedSignalValue.html#aac0ed361af545cde48d0e6f41a859686',1,'DecodedSignalValue::valid']]], ['validatemultiplexing_3',['ValidateMultiplexing',['../structDbcParseContext.html#a908056de0a97316a4ebecb3f99cc400b',1,'DbcParseContext']]], - ['validation_4',['Extended validation',['../md_tests_2README.html#autotoc_md172',1,'']]], + ['validation_4',['Extended validation',['../md_tests_2README.html#autotoc_md173',1,'']]], ['validation_20and_20parse_20errors_5',['validation and parse errors',['../md_README.html#autotoc_md102',1,'Frame validation and parse errors'],['../md_README.html#autotoc_md106',1,'Signal validation and parse errors']]], ['validation_20coverage_6',['Database validation coverage',['../md_tests_2README.html#autotoc_md167',1,'']]], ['value_7',['value',['../structMultiplexing.html#a79158d4fdbfd68a3d51a8ce79d34fd67',1,'Multiplexing::value'],['../structAttributeAssignment.html#a93de8c51c5154219bf18beadcec5708f',1,'AttributeAssignment::value']]], - ['value_20descriptions_20tt_20val_5f_20tt_8',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], - ['value_20representation_20and_20precision_9',['Raw value representation and precision',['../md_README.html#autotoc_md87',1,'']]], - ['value_20types_10',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md170',1,'']]], - ['valuedescriptions_11',['valuedescriptions',['../structSignalInfo.html#a43f47d0433d0cc39076bd7278b46af9c',1,'SignalInfo::valueDescriptions'],['../structDecodeSignal.html#a822155d9bf4ed5f3300c9e9befe606ff',1,'DecodeSignal::valueDescriptions']]], - ['valuetype_12',['valuetype',['../structDecodeSignal.html#a9d37638129263b8902ba5be59baf39a8',1,'DecodeSignal::valueType'],['../decode__database_8h.html#ad9971b6ef33e02ba2c75d19c1d2518a1',1,'ValueType: decode_database.h']]], - ['valuetype_20tt_13',['<tt>ValueType</tt>',['../md_README.html#autotoc_md72',1,'']]], - ['variants_20in_20qt_20creator_14',['Configure all build variants in Qt Creator',['../md_README.html#autotoc_md159',1,'']]] + ['value_20descriptions_20and_20multiplex_20ranges_8',['Long value descriptions and multiplex ranges',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['value_20descriptions_20tt_20val_5f_20tt_9',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], + ['value_20representation_20and_20precision_10',['Raw value representation and precision',['../md_README.html#autotoc_md87',1,'']]], + ['value_20types_11',['Unsupported signal value types',['../md_tests_2README.html#autotoc_md171',1,'']]], + ['valuedescriptions_12',['valuedescriptions',['../structSignalInfo.html#a43f47d0433d0cc39076bd7278b46af9c',1,'SignalInfo::valueDescriptions'],['../structDecodeSignal.html#a822155d9bf4ed5f3300c9e9befe606ff',1,'DecodeSignal::valueDescriptions']]], + ['valuetype_13',['valuetype',['../structDecodeSignal.html#a9d37638129263b8902ba5be59baf39a8',1,'DecodeSignal::valueType'],['../decode__database_8h.html#ad9971b6ef33e02ba2c75d19c1d2518a1',1,'ValueType: decode_database.h']]], + ['valuetype_20tt_14',['<tt>ValueType</tt>',['../md_README.html#autotoc_md72',1,'']]], + ['variants_20in_20qt_20creator_15',['Configure all build variants in Qt Creator',['../md_README.html#autotoc_md159',1,'']]] ]; diff --git a/docs/html/search/all_5.js b/docs/html/search/all_5.js index 4c3cc13..d3077e4 100644 --- a/docs/html/search/all_5.js +++ b/docs/html/search/all_5.js @@ -3,7 +3,7 @@ var searchData= ['a_20raw_20frame_0',['Decode a raw frame',['../md_README.html#autotoc_md124',1,'']]], ['adapter_1',['Optional Qt model adapter',['../md_README.html#autotoc_md149',1,'']]], ['addchild_2',['AddChild',['../classTreeNode.html#af87331684d4b40240aec75e3286921d3',1,'TreeNode']]], - ['added_3',['added',['../md_changelog.html#autotoc_md29',1,'Added'],['../md_changelog.html#autotoc_md44',1,'Added'],['../md_changelog.html#autotoc_md33',1,'Added'],['../md_changelog.html#autotoc_md26',1,'Added'],['../md_changelog.html#autotoc_md23',1,'Added'],['../md_changelog.html#autotoc_md21',1,'Added'],['../md_changelog.html#autotoc_md16',1,'Added'],['../md_changelog.html#autotoc_md14',1,'Added'],['../md_changelog.html#autotoc_md9',1,'Added']]], + ['added_3',['added',['../md_changelog.html#autotoc_md26',1,'Added'],['../md_changelog.html#autotoc_md44',1,'Added'],['../md_changelog.html#autotoc_md33',1,'Added'],['../md_changelog.html#autotoc_md29',1,'Added'],['../md_changelog.html#autotoc_md23',1,'Added'],['../md_changelog.html#autotoc_md21',1,'Added'],['../md_changelog.html#autotoc_md16',1,'Added'],['../md_changelog.html#autotoc_md14',1,'Added'],['../md_changelog.html#autotoc_md9',1,'Added']]], ['additional_20checks_4',['Code review and additional checks',['../md_README.html#autotoc_md155',1,'']]], ['additional_20regression_20coverage_5',['Additional regression coverage',['../md_tests_2README.html#autotoc_md165',1,'']]], ['all_20build_20variants_20in_20qt_20creator_6',['Configure all build variants in Qt Creator',['../md_README.html#autotoc_md159',1,'']]], @@ -14,23 +14,24 @@ var searchData= ['and_20lcov_20on_20debian_20ubuntu_11',['GCC, gcov, and LCOV on Debian/Ubuntu',['../md_README.html#autotoc_md157',1,'']]], ['and_20maintenance_12',['and maintenance',['../md_changelog.html#autotoc_md37',1,'Documentation and maintenance'],['../md_changelog.html#autotoc_md45',1,'Documentation and maintenance']]], ['and_20motorola_20extraction_13',['Intel and Motorola Extraction',['../md_README.html#autotoc_md118',1,'']]], - ['and_20optional_20model_20tests_14',['Tree and optional model tests',['../md_tests_2README.html#autotoc_md164',1,'']]], - ['and_20parse_20errors_15',['and parse errors',['../md_README.html#autotoc_md102',1,'Frame validation and parse errors'],['../md_README.html#autotoc_md106',1,'Signal validation and parse errors']]], - ['and_20pgn_20lookup_16',['J1939 Opt-in and PGN Lookup',['../md_README.html#autotoc_md114',1,'']]], - ['and_20precision_17',['Raw value representation and precision',['../md_README.html#autotoc_md87',1,'']]], - ['and_20reload_18',['Definition ownership and reload',['../md_README.html#autotoc_md90',1,'']]], - ['and_20reload_20coverage_19',['Definition lifetime and reload coverage',['../md_tests_2README.html#autotoc_md166',1,'']]], - ['and_20run_20the_20demo_20',['Build and run the demo',['../md_README.html#autotoc_md151',1,'']]], - ['and_20runtime_20decode_20module_20documentation_21',['DBC Parser and Runtime Decode Module Documentation',['../md_README.html',1,'']]], - ['and_20signal_20identifiers_22',['Stable frame and signal identifiers',['../md_README.html#autotoc_md91',1,'']]], - ['and_20ui_20path_23',['1. Parse and UI path',['../md_README.html#autotoc_md50',1,'']]], - ['api_20documentation_24',['Generate API documentation',['../md_README.html#autotoc_md152',1,'']]], - ['architecture_25',['High-Level Architecture',['../md_README.html#autotoc_md49',1,'']]], - ['attribute_20coverage_26',['Attribute coverage',['../md_tests_2README.html#autotoc_md169',1,'']]], - ['attributeassignment_27',['AttributeAssignment',['../structAttributeAssignment.html',1,'']]], - ['attributeassignments_28',['attributeAssignments',['../structDbcDatabase.html#aa7bdf56eb8b7ab5b873e1c096b46d8b6',1,'DbcDatabase']]], - ['attributedefinition_29',['AttributeDefinition',['../structAttributeDefinition.html',1,'']]], - ['attributedefinitions_30',['attributeDefinitions',['../structDbcDatabase.html#a14862245081989f2abf126b5ea6dfdfa',1,'DbcDatabase']]], - ['attributes_31',['attributes',['../structSignalInfo.html#a781766eddbf6641071623c8e5b9acb4a',1,'SignalInfo::attributes'],['../structFrameInfo.html#a9ef2736053b593cf34ba2608cf8275c0',1,'FrameInfo::attributes'],['../structDecodeFrame.html#ad506b03108a40df7e8005638c9c59653',1,'DecodeFrame::attributes'],['../structDecodeSignal.html#a204f65c7ea45a2c148f19c12d922f99b',1,'DecodeSignal::attributes'],['../structDbcDatabase.html#a321839ad81af176444358eb23114eaee',1,'DbcDatabase::attributes']]], - ['attributes_20and_20frame_20metadata_32',['Attributes and frame metadata',['../md_README.html#autotoc_md97',1,'']]] + ['and_20multiplex_20ranges_14',['Long value descriptions and multiplex ranges',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['and_20optional_20model_20tests_15',['Tree and optional model tests',['../md_tests_2README.html#autotoc_md164',1,'']]], + ['and_20parse_20errors_16',['and parse errors',['../md_README.html#autotoc_md102',1,'Frame validation and parse errors'],['../md_README.html#autotoc_md106',1,'Signal validation and parse errors']]], + ['and_20pgn_20lookup_17',['J1939 Opt-in and PGN Lookup',['../md_README.html#autotoc_md114',1,'']]], + ['and_20precision_18',['Raw value representation and precision',['../md_README.html#autotoc_md87',1,'']]], + ['and_20reload_19',['Definition ownership and reload',['../md_README.html#autotoc_md90',1,'']]], + ['and_20reload_20coverage_20',['Definition lifetime and reload coverage',['../md_tests_2README.html#autotoc_md166',1,'']]], + ['and_20run_20the_20demo_21',['Build and run the demo',['../md_README.html#autotoc_md151',1,'']]], + ['and_20runtime_20decode_20module_20documentation_22',['DBC Parser and Runtime Decode Module Documentation',['../md_README.html',1,'']]], + ['and_20signal_20identifiers_23',['Stable frame and signal identifiers',['../md_README.html#autotoc_md91',1,'']]], + ['and_20ui_20path_24',['1. Parse and UI path',['../md_README.html#autotoc_md50',1,'']]], + ['api_20documentation_25',['Generate API documentation',['../md_README.html#autotoc_md152',1,'']]], + ['architecture_26',['High-Level Architecture',['../md_README.html#autotoc_md49',1,'']]], + ['attribute_20coverage_27',['Attribute coverage',['../md_tests_2README.html#autotoc_md169',1,'']]], + ['attributeassignment_28',['AttributeAssignment',['../structAttributeAssignment.html',1,'']]], + ['attributeassignments_29',['attributeAssignments',['../structDbcDatabase.html#aa7bdf56eb8b7ab5b873e1c096b46d8b6',1,'DbcDatabase']]], + ['attributedefinition_30',['AttributeDefinition',['../structAttributeDefinition.html',1,'']]], + ['attributedefinitions_31',['attributeDefinitions',['../structDbcDatabase.html#a14862245081989f2abf126b5ea6dfdfa',1,'DbcDatabase']]], + ['attributes_32',['attributes',['../structSignalInfo.html#a781766eddbf6641071623c8e5b9acb4a',1,'SignalInfo::attributes'],['../structFrameInfo.html#a9ef2736053b593cf34ba2608cf8275c0',1,'FrameInfo::attributes'],['../structDecodeFrame.html#ad506b03108a40df7e8005638c9c59653',1,'DecodeFrame::attributes'],['../structDecodeSignal.html#a204f65c7ea45a2c148f19c12d922f99b',1,'DecodeSignal::attributes'],['../structDbcDatabase.html#a321839ad81af176444358eb23114eaee',1,'DbcDatabase::attributes']]], + ['attributes_20and_20frame_20metadata_33',['Attributes and frame metadata',['../md_README.html#autotoc_md97',1,'']]] ]; diff --git a/docs/html/search/all_8.js b/docs/html/search/all_8.js index 47f9a17..697cfa1 100644 --- a/docs/html/search/all_8.js +++ b/docs/html/search/all_8.js @@ -66,14 +66,15 @@ var searchData= ['definition_63',['definition',['../structDecodedSignalValue.html#a828b5ef7faefc14fbf5dbd46249bcad2',1,'DecodedSignalValue::definition'],['../structDecodedFrameValue.html#a33e971ba10030506007aa201c89f351d',1,'DecodedFrameValue::definition'],['../structPgnLookupResult.html#aa5b79b61d2aef48fd6ae070cf5f901c1',1,'PgnLookupResult::definition'],['../md_README.html#autotoc_md100',1,'Frame definition'],['../md_README.html#autotoc_md104',1,'Signal definition']]], ['definition_20lifetime_20and_20reload_20coverage_64',['Definition lifetime and reload coverage',['../md_tests_2README.html#autotoc_md166',1,'']]], ['definition_20ownership_20and_20reload_65',['Definition ownership and reload',['../md_README.html#autotoc_md90',1,'']]], - ['definitions_66',['Long signal definitions',['../md_tests_2README.html#autotoc_md171',1,'']]], + ['definitions_66',['Long signal definitions',['../md_tests_2README.html#autotoc_md172',1,'']]], ['demo_67',['Build and run the demo',['../md_README.html#autotoc_md151',1,'']]], ['descriptions_68',['descriptions',['../structPendingValueDescriptions.html#a10ecbaeaed2cc3ac82fada73e7e041dd',1,'PendingValueDescriptions']]], - ['descriptions_20tt_20val_5f_20tt_69',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], - ['diagnostic_70',['diagnostic',['../classDbcDiagnosticError.html#ab3965082f01d66b6d5c20525af44235d',1,'DbcDiagnosticError']]], - ['diagnostics_71',['diagnostics',['../structDbcParseResult.html#a3a1987b4381bb1e0a34dc09e31483679',1,'DbcParseResult::diagnostics'],['../md_README.html#autotoc_md96',1,'Structured diagnostics']]], - ['dispatch_72',['Dispatch',['../classDbcStatementDispatcher.html#a495c2d5c7ebb8103b1987cdd87c142ed',1,'DbcStatementDispatcher']]], - ['dlc_73',['dlc',['../structDecodeFrame.html#a51111ac13988be372d5673d12cf3e060',1,'DecodeFrame::dlc'],['../structFrameInfo.html#aa2b198ea0f55e560c123d35e7b3ecab9',1,'FrameInfo::dlc']]], - ['documentation_74',['documentation',['../md_README.html',1,'DBC Parser and Runtime Decode Module Documentation'],['../md_changelog.html#autotoc_md42',1,'Documentation'],['../md_README.html#autotoc_md152',1,'Generate API documentation']]], - ['documentation_20and_20maintenance_75',['documentation and maintenance',['../md_changelog.html#autotoc_md45',1,'Documentation and maintenance'],['../md_changelog.html#autotoc_md37',1,'Documentation and maintenance']]] + ['descriptions_20and_20multiplex_20ranges_69',['Long value descriptions and multiplex ranges',['../md_tests_2README.html#autotoc_md170',1,'']]], + ['descriptions_20tt_20val_5f_20tt_70',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md136',1,'']]], + ['diagnostic_71',['diagnostic',['../classDbcDiagnosticError.html#ab3965082f01d66b6d5c20525af44235d',1,'DbcDiagnosticError']]], + ['diagnostics_72',['diagnostics',['../structDbcParseResult.html#a3a1987b4381bb1e0a34dc09e31483679',1,'DbcParseResult::diagnostics'],['../md_README.html#autotoc_md96',1,'Structured diagnostics']]], + ['dispatch_73',['Dispatch',['../classDbcStatementDispatcher.html#a495c2d5c7ebb8103b1987cdd87c142ed',1,'DbcStatementDispatcher']]], + ['dlc_74',['dlc',['../structDecodeFrame.html#a51111ac13988be372d5673d12cf3e060',1,'DecodeFrame::dlc'],['../structFrameInfo.html#aa2b198ea0f55e560c123d35e7b3ecab9',1,'FrameInfo::dlc']]], + ['documentation_75',['documentation',['../md_README.html',1,'DBC Parser and Runtime Decode Module Documentation'],['../md_changelog.html#autotoc_md42',1,'Documentation'],['../md_README.html#autotoc_md152',1,'Generate API documentation']]], + ['documentation_20and_20maintenance_76',['documentation and maintenance',['../md_changelog.html#autotoc_md45',1,'Documentation and maintenance'],['../md_changelog.html#autotoc_md37',1,'Documentation and maintenance']]] ]; diff --git a/docs/html/search/all_9.js b/docs/html/search/all_9.js index 2a3a87f..eb9d7ff 100644 --- a/docs/html/search/all_9.js +++ b/docs/html/search/all_9.js @@ -9,6 +9,6 @@ var searchData= ['example_20usage_6',['Example Usage',['../md_README.html#autotoc_md120',1,'']]], ['examples_2fmain_2ecpp_7',['main.cpp',['../examples_2main_8cpp.html',1,'']]], ['exists_8',['Why the Runtime Decode Layer Exists',['../md_README.html#autotoc_md53',1,'']]], - ['extended_20validation_9',['Extended validation',['../md_tests_2README.html#autotoc_md172',1,'']]], + ['extended_20validation_9',['Extended validation',['../md_tests_2README.html#autotoc_md173',1,'']]], ['extraction_10',['Intel and Motorola Extraction',['../md_README.html#autotoc_md118',1,'']]] ]; diff --git a/docs/html/search/functions_b.js b/docs/html/search/functions_b.js index be5983e..539f29d 100644 --- a/docs/html/search/functions_b.js +++ b/docs/html/search/functions_b.js @@ -1,6 +1,6 @@ var searchData= [ - ['registertype_0',['registertype',['../classTypeFactory.html#ad6d85795285ee21193ac03e1b24762d3',1,'TypeFactory::registerType()'],['../classDbcStatementDispatcher.html#a2c6e9a614ed747275d43bcccc50d0b2d',1,'DbcStatementDispatcher::RegisterType()']]], + ['registertype_0',['registertype',['../classDbcStatementDispatcher.html#a2c6e9a614ed747275d43bcccc50d0b2d',1,'DbcStatementDispatcher::RegisterType()'],['../classTypeFactory.html#ad6d85795285ee21193ac03e1b24762d3',1,'TypeFactory::registerType()']]], ['resolveattributes_1',['ResolveAttributes',['../structDbcParseContext.html#a1ac7f4a54550aaddef08749806f87001',1,'DbcParseContext']]], ['resolvecomments_2',['ResolveComments',['../structDbcParseContext.html#aeae343c57a244634489a0e0b960155d7',1,'DbcParseContext']]], ['resolvevaluedescriptions_3',['ResolveValueDescriptions',['../structDbcParseContext.html#ab5552dad944fde7fa6add6464ddb5369',1,'DbcParseContext']]], diff --git a/docs/html/structDbcDatabase.html b/docs/html/structDbcDatabase.html index 230c8cd..558a99e 100644 --- a/docs/html/structDbcDatabase.html +++ b/docs/html/structDbcDatabase.html @@ -133,7 +133,7 @@ Public Attributes

Definition at line 23 of file dbc_database.h.

-

Referenced by DbcTreeBuilder::Build(), DbcDecodeBuilder::Build(), DbcFrameHandler::Handle(), DbcParseContext::ResolveAttributes(), and DbcParseContext::ValidateMultiplexing().

+

Referenced by DbcTreeBuilder::Build(), DbcDecodeBuilder::Build(), DbcFrameHandler::Handle(), DbcParseContext::ResolveAttributes(), and DbcParseContext::ValidateMultiplexing().

diff --git a/docs/html/structDbcParseContext.html b/docs/html/structDbcParseContext.html index 3b8c062..6f46611 100644 --- a/docs/html/structDbcParseContext.html +++ b/docs/html/structDbcParseContext.html @@ -159,7 +159,7 @@ Public Attributes
-

Definition at line 522 of file dbc_statement_handler.cpp.

+

Definition at line 594 of file dbc_statement_handler.cpp.

@@ -181,7 +181,7 @@ Public Attributes

Validate selectors, branch ranges, and signal overlap.

-

Definition at line 572 of file dbc_statement_handler.cpp.

+

Definition at line 644 of file dbc_statement_handler.cpp.

References database, DbcDatabase::frames, SignalInfo::isSigned, SignalInfo::multiplexing, and signalLines.

@@ -205,7 +205,7 @@ Public Attributes

Resolve deferred comments after all definitions are known.

-

Definition at line 385 of file dbc_statement_handler.cpp.

+

Definition at line 468 of file dbc_statement_handler.cpp.

References FrameInfo::comment, SignalInfo::comment, database, and pendingComments.

@@ -263,7 +263,7 @@ Public Attributes

Resolve deferred raw descriptions against unique targets.

-

Definition at line 473 of file dbc_statement_handler.cpp.

+

Definition at line 551 of file dbc_statement_handler.cpp.

References database, pendingValues, and SignalInfo::valueDescriptions.

@@ -285,7 +285,7 @@ Public Attributes

Definition at line 61 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle(), ResolveAttributes(), ResolveComments(), ResolveValueDescriptions(), and ValidateMultiplexing().

+

Referenced by DbcFrameHandler::Handle(), ResolveAttributes(), ResolveComments(), ResolveValueDescriptions(), and ValidateMultiplexing().

@@ -304,7 +304,7 @@ Public Attributes

Definition at line 62 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle(), and DbcSignalHandler::Handle().

+

Referenced by DbcFrameHandler::Handle(), and DbcSignalHandler::Handle().

@@ -323,7 +323,7 @@ Public Attributes

Definition at line 63 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle().

+

Referenced by DbcFrameHandler::Handle().

@@ -342,7 +342,7 @@ Public Attributes

Definition at line 64 of file dbc_statement_handler.h.

-

Referenced by DbcSignalHandler::Handle(), DbcCommentHandler::Handle(), DbcAttributeHandler::Handle(), DbcValueDescriptionHandler::Handle(), and DbcMultiplexingHandler::Handle().

+

Referenced by DbcSignalHandler::Handle(), DbcCommentHandler::Handle(), DbcAttributeHandler::Handle(), DbcValueDescriptionHandler::Handle(), and DbcMultiplexingHandler::Handle().

@@ -361,7 +361,7 @@ Public Attributes

Definition at line 65 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle(), and ResolveValueDescriptions().

+

Referenced by DbcValueDescriptionHandler::Handle(), and ResolveValueDescriptions().

@@ -396,7 +396,7 @@ Public Attributes

Definition at line 70 of file dbc_statement_handler.h.

-

Referenced by DbcFrameHandler::Handle(), DbcSignalHandler::Handle(), and ValidateMultiplexing().

+

Referenced by DbcFrameHandler::Handle(), DbcSignalHandler::Handle(), and ValidateMultiplexing().

@@ -415,7 +415,7 @@ Public Attributes

Definition at line 74 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle(), and ResolveComments().

+

Referenced by DbcCommentHandler::Handle(), and ResolveComments().

diff --git a/docs/html/structFrameInfo.html b/docs/html/structFrameInfo.html index 376e421..4d18633 100644 --- a/docs/html/structFrameInfo.html +++ b/docs/html/structFrameInfo.html @@ -149,7 +149,7 @@ Public Attributes

Definition at line 26 of file frame_info.h.

-

Referenced by DbcSignalHandler::Handle().

+

Referenced by DbcSignalHandler::Handle().

@@ -168,7 +168,7 @@ Public Attributes

Definition at line 27 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

@@ -187,7 +187,7 @@ Public Attributes

Definition at line 28 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcFrameHandler::Handle().

@@ -206,7 +206,7 @@ Public Attributes

Definition at line 29 of file frame_info.h.

-

Referenced by DbcFrameHandler::Handle().

+

Referenced by DbcFrameHandler::Handle().

@@ -225,7 +225,7 @@ Public Attributes

Definition at line 30 of file frame_info.h.

-

Referenced by DbcFrameHandler::Handle().

+

Referenced by DbcFrameHandler::Handle().

@@ -244,7 +244,7 @@ Public Attributes

Definition at line 31 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -280,7 +280,7 @@ Public Attributes

Definition at line 33 of file frame_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

diff --git a/docs/html/structPendingComment.html b/docs/html/structPendingComment.html index cf55ae3..9c1f805 100644 --- a/docs/html/structPendingComment.html +++ b/docs/html/structPendingComment.html @@ -137,7 +137,7 @@ Public Attributes

Definition at line 23 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -156,7 +156,7 @@ Public Attributes

Definition at line 24 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -175,7 +175,7 @@ Public Attributes

Definition at line 25 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -194,7 +194,7 @@ Public Attributes

Definition at line 26 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -213,7 +213,7 @@ Public Attributes

Definition at line 27 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

@@ -232,7 +232,7 @@ Public Attributes

Definition at line 28 of file dbc_statement_handler.h.

-

Referenced by DbcCommentHandler::Handle().

+

Referenced by DbcCommentHandler::Handle().

diff --git a/docs/html/structPendingValueDescriptions.html b/docs/html/structPendingValueDescriptions.html index 919ad39..64ebf81 100644 --- a/docs/html/structPendingValueDescriptions.html +++ b/docs/html/structPendingValueDescriptions.html @@ -135,7 +135,7 @@ Public Attributes

Definition at line 14 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -154,7 +154,7 @@ Public Attributes

Definition at line 15 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -173,7 +173,7 @@ Public Attributes

Definition at line 16 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -192,7 +192,7 @@ Public Attributes

Definition at line 17 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

@@ -211,7 +211,7 @@ Public Attributes

Definition at line 18 of file dbc_statement_handler.h.

-

Referenced by DbcValueDescriptionHandler::Handle().

+

Referenced by DbcValueDescriptionHandler::Handle().

diff --git a/docs/html/structSignalInfo.html b/docs/html/structSignalInfo.html index e701400..2e63ac6 100644 --- a/docs/html/structSignalInfo.html +++ b/docs/html/structSignalInfo.html @@ -155,7 +155,7 @@ Public Attributes

Definition at line 26 of file signal_info.h.

-

Referenced by DbcParseContext::ValidateMultiplexing().

+

Referenced by DbcParseContext::ValidateMultiplexing().

@@ -174,7 +174,7 @@ Public Attributes

Definition at line 27 of file signal_info.h.

-

Referenced by DbcSignalHandler::Handle().

+

Referenced by DbcSignalHandler::Handle().

@@ -193,7 +193,7 @@ Public Attributes

Definition at line 28 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -212,7 +212,7 @@ Public Attributes

Definition at line 29 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -231,7 +231,7 @@ Public Attributes

Definition at line 30 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

+

Referenced by DbcTreeModel::data(), and DbcSignalHandler::Handle().

@@ -250,7 +250,7 @@ Public Attributes

Definition at line 31 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ValidateMultiplexing().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ValidateMultiplexing().

@@ -373,7 +373,7 @@ Public Attributes

Definition at line 38 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveValueDescriptions().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveValueDescriptions().

@@ -392,7 +392,7 @@ Public Attributes

Definition at line 39 of file signal_info.h.

-

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

+

Referenced by DbcTreeModel::data(), and DbcParseContext::ResolveComments().

diff --git a/docs/html/tests_2main_8cpp_source.html b/docs/html/tests_2main_8cpp_source.html index d5a1ffe..cfc271a 100644 --- a/docs/html/tests_2main_8cpp_source.html +++ b/docs/html/tests_2main_8cpp_source.html @@ -102,25 +102,27 @@ $(document).ready(function(){initNavTree('tests_2main_8cpp_source.html',''); ini
2#include <QtTest>
3
4int RunParserTests(int argc, char **argv);
-
5int RunDecoderTests(int argc, char **argv);
-
6int RunCoverageTests(int argc, char **argv);
-
7
-
8int RunLifetimeTests(int argc, char **argv);
-
9
-
10int RunValidationTests(int argc, char **argv);
-
11
-
12int RunPipelineTests(int argc, char **argv);
-
13
-
14int main(int argc, char **argv) {
-
15 QCoreApplication application(argc, argv);
-
16 const int parserResult = RunParserTests(argc, argv);
-
17 const int decoderResult = RunDecoderTests(argc, argv);
-
18 const int coverageResult = RunCoverageTests(argc, argv);
-
19 const int lifetimeResult = RunLifetimeTests(argc, argv);
-
20 const int validationResult = RunValidationTests(argc, argv);
-
21 const int pipelineResult = RunPipelineTests(argc, argv);
-
22 return pipelineResult | validationResult | parserResult | decoderResult | coverageResult | lifetimeResult;
-
23}
+
5int RunStatementParserTests(int argc, char **argv);
+
6int RunDecoderTests(int argc, char **argv);
+
7int RunCoverageTests(int argc, char **argv);
+
8
+
9int RunLifetimeTests(int argc, char **argv);
+
10
+
11int RunValidationTests(int argc, char **argv);
+
12
+
13int RunPipelineTests(int argc, char **argv);
+
14
+
15int main(int argc, char **argv) {
+
16 QCoreApplication application(argc, argv);
+
17 const int parserResult = RunParserTests(argc, argv);
+
18 const int statementResult = RunStatementParserTests(argc, argv);
+
19 const int decoderResult = RunDecoderTests(argc, argv);
+
20 const int coverageResult = RunCoverageTests(argc, argv);
+
21 const int lifetimeResult = RunLifetimeTests(argc, argv);
+
22 const int validationResult = RunValidationTests(argc, argv);
+
23 const int pipelineResult = RunPipelineTests(argc, argv);
+
24 return statementResult | pipelineResult | validationResult | parserResult | decoderResult | coverageResult | lifetimeResult;
+
25}
int main(int argc, char *argv[])
Parse a DBC file and demonstrate metadata lookup and CAN decoding.
diff --git a/tests/README.md b/tests/README.md index 0639370..71f8eb3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -40,14 +40,15 @@ make make check ``` -`parser_tests.pri` and `decoder_tests.pri` include the original test sources; +`parser_tests.pri` includes `dbc_parser_test.cpp` and `dbc_statement_test.cpp`; +`decoder_tests.pri` includes the decoder test source; `tests.pro` also includes `dbc_coverage_test.cpp`, `dbc_lifetime_test.cpp`, `dbc_validation_test.cpp`, and `dbc_pipeline_test.cpp`. `../core/core.pri` supplies the library include path and linkage. ## Local regression checks -`run_core_ci.sh` builds the core archive, runs all six QtTest suites and the +`run_core_ci.sh` builds the core archive, runs all seven QtTest suites and the qmake tree target with `make check`, then compiles and runs the standalone C++17 tree/builder test without Qt. A failed command returns a nonzero status. The `sanitize` mode applies ASan/UBSan flags to core, tests, and the standalone @@ -295,6 +296,28 @@ Attribute changes were verified with `qmake6 ../dbc.pro`, `make -j4`, and passed. The standalone C++17 tree test also passed. The existing named comma-decimal C locale test skipped because the locale is not installed. +## Long value descriptions and multiplex ranges + +`dbc_statement_test.cpp` adds a separate `StatementParserTest` suite to the +existing parser qmake source list and the main test runner. Both parser APIs are +checked with 50,000-character descriptions (including quotes, backslashes, +semicolons, and spaces), 10,000 `VAL_` pairs, 10,000 multiplex ranges, and long +numeric bounds and whitespace. Valid metadata must survive parsing without +truncation, support value-description lookup after decoding, and activate or +skip multiplex branches correctly. Statements may precede or follow definitions. + +Malformed long statements cover incomplete quotes/escapes, invalid escapes, +missing separators and semicolons, duplicate keys, numeric overflow, empty and +reversed ranges, trailing text, and unresolved long names. The diagnostic API +must return an empty database with the correct category, path, and source line; +the throwing API must report that same location. + +Verified with Qt 6.8.2 in separate Release and ASan/UBSan builds with +`CONFIG+=dbc_qt`: each passed 2,449 core QtTest checks (including 29 checks in the +new suite), seven model checks, and the qmake tree test. One named-locale case +skipped because the locale is unavailable. Sanitizer errors were fatal; local +leak checking used `detect_leaks=0` because of the documented ptrace restriction. + ## Unsupported signal value types `unsupportedSignalValueTypes` verifies that both parser APIs reject `SIG_VALTYPE_` diff --git a/tests/dbc_statement_test.cpp b/tests/dbc_statement_test.cpp new file mode 100644 index 0000000..437d987 --- /dev/null +++ b/tests/dbc_statement_test.cpp @@ -0,0 +1,194 @@ +#include "dbc_decode_builder.h" +#include "dbc_decoder.h" +#include "dbc_parser.h" +#include +#include +#include +#include +#include + +namespace { + std::string WriteFixture(QTemporaryFile &file, const std::string &text) { + if (!file.open() || file.write(text.data(), static_cast(text.size())) != static_cast(text.size()) || !file.flush()) + throw std::runtime_error("Cannot write statement fixture"); + return file.fileName().toStdString(); + } + + std::string Quote(const std::string &text) { + std::ostringstream stream; + stream << std::quoted(text); + return stream.str(); + } + + std::string RepeatedRanges() { + std::string ranges = "1-1"; + for (unsigned index = 1U; index < 10000U; ++index) + ranges += ",1-1"; + return ranges; + } + + const std::string definitions = + "BO_ 1 Frame: 2 ECU\n" + "SG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + "SG_ Value m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"; +}// namespace + +class StatementParserTest : public QObject { + Q_OBJECT +private Q_SLOTS: + void longValueDescriptions_data(); + void longValueDescriptions(); + void longMultiplexRanges_data(); + void longMultiplexRanges(); + void invalidLongStatements_data(); + void invalidLongStatements(); +}; + +void StatementParserTest::longValueDescriptions_data() { + QTest::addColumn("description"); + QTest::addColumn("count"); + QTest::addColumn("beforeDefinitions"); + QTest::newRow("50000-character-description") << std::string(50000U, 'x') << 1U << false; + std::string escaped; + for (unsigned index = 0U; index < 12500U; ++index) + escaped += "\"\\; "; + QTest::newRow("50000-escaped-characters") << escaped << 1U << true; + QTest::newRow("10000-pairs") << std::string("Description") << 10000U << true; + QTest::newRow("50000-spaces") << std::string(50000U, ' ') << 1U << false; +} + +void StatementParserTest::longValueDescriptions() { + QFETCH(std::string, description); + QFETCH(unsigned, count); + QFETCH(bool, beforeDefinitions); + std::string statement = "VAL_ 1 Value"; + for (unsigned index = 0U; index < count; ++index) + statement += " " + std::to_string(index) + " " + Quote(description); + statement += ";\n"; + const std::string frame = "BO_ 1 Frame: 4 ECU\nSG_ Value : 0|32@1+ (1,0) [0|1e10] \"\" ECU\n"; + QTemporaryFile file; + const std::string path = WriteFixture(file, beforeDefinitions ? statement + frame : frame + statement); + const DbcParser parser; + const DbcParseResult result = parser.ParseFileWithDiagnostics(path); + QVERIFY(!result.HasErrors()); + QVERIFY(result.diagnostics.empty()); + const std::vector databases = {result.database, parser.ParseFile(path)}; + for (const DbcDatabase &database: databases) { + const ValueDescriptions &values = database.frames.at(0).signals.at(0).valueDescriptions; + QCOMPARE(values.size(), static_cast(count)); + for (unsigned index = 0U; index < count; ++index) + QCOMPARE(values.at(std::uint64_t(index)), description); + const DecodeDatabase runtime = DbcDecodeBuilder().Build(database); + RawCanFrame raw; + raw.canId = 1U; + const unsigned last = count - 1U; + raw.data = {static_cast(last), static_cast(last >> 8U), 0U, 0U}; + const DecodedFrameValue decoded = DbcDecoder().Decode(runtime, raw); + QCOMPARE(decoded.status, FrameDecodeStatus::Valid); + const std::string *label = decoded.signals.at(0).GetValueDescription(); + QVERIFY(label); + QCOMPARE(*label, description); + } +} + +void StatementParserTest::longMultiplexRanges_data() { + QTest::addColumn("ranges"); + QTest::addColumn("count"); + QTest::addColumn("beforeDefinitions"); + QTest::newRow("10000-ranges") << RepeatedRanges() << 10000U << false; + const std::string paddedOne = std::string(50000U, '0') + "1"; + QTest::newRow("long-bounds") << paddedOne + "-" + paddedOne << 1U << true; + QTest::newRow("long-whitespace") << std::string(50000U, ' ') + "1\t-\t1" + std::string(50000U, ' ') << 1U << false; +} + +void StatementParserTest::longMultiplexRanges() { + QFETCH(std::string, ranges); + QFETCH(unsigned, count); + QFETCH(bool, beforeDefinitions); + const std::string statement = "SG_MUL_VAL_ 1 Value Mode " + ranges + ";\n"; + QTemporaryFile file; + const std::string path = WriteFixture(file, beforeDefinitions ? statement + definitions : definitions + statement); + const DbcParser parser; + const DbcParseResult result = parser.ParseFileWithDiagnostics(path); + QVERIFY(!result.HasErrors()); + QVERIFY(result.diagnostics.empty()); + const std::vector databases = {result.database, parser.ParseFile(path)}; + for (const DbcDatabase &database: databases) { + const Multiplexing &mux = database.frames.at(0).signals.at(1).multiplexing; + QCOMPARE(mux.selector, std::string("Mode")); + QCOMPARE(mux.ranges.size(), static_cast(count)); + for (const MultiplexRange &range: mux.ranges) { + QCOMPARE(range.minimum, std::uint64_t(1)); + QCOMPARE(range.maximum, std::uint64_t(1)); + } + const DecodeDatabase runtime = DbcDecodeBuilder().Build(database); + RawCanFrame raw; + raw.canId = 1U; + raw.data = {1U, 42U}; + const DecodedFrameValue active = DbcDecoder().Decode(runtime, raw); + QCOMPARE(active.status, FrameDecodeStatus::Valid); + QVERIFY(active.signals.at(1).valid); + QCOMPARE(std::get(active.signals[1].rawValue), std::uint64_t(42)); + raw.data[0] = 2U; + const DecodedFrameValue inactive = DbcDecoder().Decode(runtime, raw); + QCOMPARE(inactive.signals.at(1).status, SignalDecodeStatus::Inactive); + } +} + +void StatementParserTest::invalidLongStatements_data() { + QTest::addColumn("statement"); + QTest::addColumn("code"); + const std::string text(50000U, 'x'); + const std::string value = "VAL_ 1 Value 0 \"" + text; + QTest::newRow("val-missing-quote") << value + ";" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-dangling-escape") << value + "\\" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-invalid-escape") << value + "\\q\";" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-missing-semicolon") << value + "\"" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-trailing-text") << value + "\"; junk" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-missing-pair-separator") << value + "\"1 \"On\";" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-missing-number-separator") << "VAL_ 1 Value 0\"" + text + "\";" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-duplicate") << value + "\" 00 \"Duplicate\";" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-overflow") << "VAL_ 1 Value " + std::string(50000U, '9') + " \"Bad\";" << DbcDiagnosticCode::InvalidValueDescription; + QTest::newRow("val-long-missing-reference") << "VAL_ 1 " + text + " 0 \"Bad\";" << DbcDiagnosticCode::InvalidValueDescription; + const std::string mux = "SG_MUL_VAL_ 1 Value Mode " + RepeatedRanges(); + QTest::newRow("mux-missing-semicolon") << mux << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-trailing-comma") << mux + ",;" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-empty-range") << mux + ",,1-1;" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-reversed-range") << mux + ",2-1;" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-signed-bound") << mux + ",+1-1;" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-suffix") << mux + "x;" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-overflow") << mux + ",0-18446744073709551616;" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-long-overflow") << "SG_MUL_VAL_ 1 Value Mode 0-" + std::string(50000U, '9') + ";" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-trailing-text") << mux + "; junk" << DbcDiagnosticCode::InvalidMultiplexing; + QTest::newRow("mux-long-missing-reference") << "SG_MUL_VAL_ 1 " + text + " Mode 1-1;" << DbcDiagnosticCode::InvalidMultiplexing; +} + +void StatementParserTest::invalidLongStatements() { + QFETCH(std::string, statement); + QFETCH(DbcDiagnosticCode, code); + QTemporaryFile file; + const std::string path = WriteFixture(file, "\n" + statement + "\n" + definitions); + const DbcParser parser; + const DbcParseResult result = parser.ParseFileWithDiagnostics(path); + QVERIFY(result.HasErrors()); + QVERIFY(result.database.frames.empty()); + QCOMPARE(result.diagnostics.size(), std::size_t(1)); + const DbcDiagnostic &error = result.diagnostics[0]; + QCOMPARE(error.severity, DbcDiagnosticSeverity::Error); + QCOMPARE(error.code, code); + QCOMPARE(error.filePath, path); + QCOMPARE(error.lineNumber, std::size_t(2)); + try { + parser.ParseFile(path); + QFAIL("Malformed statements must throw through ParseFile"); + } catch (const std::runtime_error &exception) { + QCOMPARE(std::string(exception.what()), path + ":2: " + error.message); + } +} + +int RunStatementParserTests(int argc, char **argv) { + StatementParserTest tests; + return QTest::qExec(&tests, argc, argv); +} + +#include "dbc_statement_test.moc" diff --git a/tests/main.cpp b/tests/main.cpp index 9511c77..161d843 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -2,6 +2,7 @@ #include int RunParserTests(int argc, char **argv); +int RunStatementParserTests(int argc, char **argv); int RunDecoderTests(int argc, char **argv); int RunCoverageTests(int argc, char **argv); @@ -14,10 +15,11 @@ int RunPipelineTests(int argc, char **argv); int main(int argc, char **argv) { QCoreApplication application(argc, argv); const int parserResult = RunParserTests(argc, argv); + const int statementResult = RunStatementParserTests(argc, argv); const int decoderResult = RunDecoderTests(argc, argv); const int coverageResult = RunCoverageTests(argc, argv); const int lifetimeResult = RunLifetimeTests(argc, argv); const int validationResult = RunValidationTests(argc, argv); const int pipelineResult = RunPipelineTests(argc, argv); - return pipelineResult | validationResult | parserResult | decoderResult | coverageResult | lifetimeResult; + return statementResult | pipelineResult | validationResult | parserResult | decoderResult | coverageResult | lifetimeResult; } diff --git a/tests/parser_tests.pri b/tests/parser_tests.pri index f46d193..8eb6bf4 100644 --- a/tests/parser_tests.pri +++ b/tests/parser_tests.pri @@ -1 +1 @@ -SOURCES += $$PWD/dbc_parser_test.cpp +SOURCES += $$PWD/dbc_parser_test.cpp $$PWD/dbc_statement_test.cpp