This commit is contained in:
deeaitch committed 2026-10-06 17:04:13 -04:00
1 parent 65142fdfe3
commit e5d818d9b2
14 files changed
+393 -39

No files matched your search

+67 -6
View File
@@ -463,6 +463,7 @@ The current parser supports the following DBC elements:
- `SG_`
- `CM_ BO_`
- `CM_ SG_`
- `VAL_` signal value descriptions
---
@@ -799,6 +800,7 @@ lines:
- `BO_` → `DbcFrameHandler`
- `SG_` → `DbcSignalHandler`
- `CM_` → `DbcCommentHandler`
- `VAL_` → `DbcValueDescriptionHandler`
Grammar implementations live in `core/dbc_statement_handler.cpp`; their interface,
context, and dispatcher live in `core/dbc_statement_handler.h`. `core/typefactory.h`
@@ -814,7 +816,7 @@ void Handle(const std::string &line, DbcParseContext &context) const override;
Register it in the dispatcher constructor:
```cpp
RegisterType<DbcValueHandler>("VAL_"); // after implementing DbcValueHandler
RegisterType<MyCustomHandler>("CUSTOM_"); // after implementing MyCustomHandler
```
Registration creates one handler; replacing a registration replaces that instance.
@@ -829,6 +831,63 @@ and database builders remain independent of statement dispatch.
---
## Signal value descriptions (`VAL_`)
Signal descriptions attach text to exact raw integer values:
```dbc
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, `\"` and `\\` represent a quote and a 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`.
`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:
```cpp
const DecodedSignalValue &value = decodedFrame.signals.at(0);
if (const std::string *description = value.GetValueDescription()) {
std::cout << *description;
}
```
`GetValueDescription()` uses the exact raw integer before factor/offset scaling.
It returns `nullptr` for an invalid decode or an unknown value; numeric decoding
and `physicalValue` are unchanged. A mapped empty string returns a non-null
pointer. The pointer belongs to the signal definition and stays valid while that
definition and mapping remain alive and unchanged.
For metadata or standalone raw values, use
`FindValueDescription(signal.valueDescriptions, rawValue)`, where `rawValue` is
`RawSignalValue` (`std::variant<std::int64_t, std::uint64_t>`). The helper matches
nonnegative signed and unsigned values identically and keeps negative values
distinct from large unsigned values. Stored keys use negative `int64_t` and
nonnegative `uint64_t`; when constructing mappings manually, normalize keys
with `NormalizeValueKey()` before insertion.
Named `VAL_TABLE_` tables, environment-variable mappings, multiline statements,
and Qt presentation are outside this implementation.
---
## Current Limitations
This is still a minimal implementation.
@@ -836,7 +895,7 @@ This is still a minimal implementation.
Not supported yet:
- multiplexed signals
- `VAL_` tables
- named `VAL_TABLE_` tables and environment-variable value descriptions
- `BA_` attributes
- `BA_DEF_` definitions
- advanced comment handling
@@ -847,8 +906,9 @@ Not supported yet:
Additional behavior to account for:
- unregistered DBC keywords are ignored
- an `SG_` statement before the first frame is ignored
- duplicate frame and signal definitions are not rejected
- an `SG_` statement before the first frame is rejected
- duplicate frame and signal definitions are not rejected globally; ambiguous
`VAL_` references to them are rejected
- payload lengths are checked against storage bounds (`0..255`), rather than
CAN or CAN FD protocol limits
- comment handling is limited to the supported forms and already parsed targets
@@ -868,6 +928,7 @@ A practical development order would be:
- `BO_`
- `SG_`
- `CM_`
- `VAL_` signal value descriptions
- normalized CAN ID
- `isExtended`
- transmitter
@@ -883,7 +944,6 @@ Recommended additions:
- parent pointer in `TreeNode`
- Qt model adapter
- `VAL_` support for enum-style signals
- better display strings for UI
- selective decoding of only chosen signals
@@ -981,6 +1041,7 @@ Open `docs/html/index.html` to view the generated reference.
See [tests/README.md](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, statement dispatch, signed and unsigned
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.
+20 -3
View File
@@ -1,11 +1,28 @@
# Changelog
This changelog summarizes the Git history through `f17ddb6`. Entries are listed
newest first. Dates follow the commit author dates. `v1.0` is the existing Git
tag; subsequent changes have no release tag and are listed as unreleased.
This changelog summarizes release and development history. Entries are listed
newest first. Historical dates follow the commit author dates. `v1.0` is the
existing Git tag; subsequent changes are listed as unreleased.
## Unreleased
### 2026-10-06
#### Added
- Added `VAL_` signal value descriptions through the statement dispatcher and
factory, with complete syntax validation, exact signed/unsigned 64-bit keys,
deferred reference resolution, duplicate detection, and source error locations.
Distinct keys from multiple statements merge; standard and extended frames
remain distinct under existing CAN ID normalization.
- Preserved descriptions in parsed metadata, runtime decode definitions, and UI
tree metadata. Added `DecodedSignalValue::GetValueDescription()` and
`FindValueDescription()` for raw integer lookup before physical scaling.
Unknown values retain normal numeric decoding.
- Added regression coverage for syntax, quoting, whitespace, integer boundaries,
declaration order, invalid references, duplicates, scaling, and both builders;
documented syntax, lookup behavior, and limitations in README.
### 2026-10-05
#### Changed
+1
View File
@@ -16,5 +16,6 @@ HEADERS += \
$$PWD/decode_database.h \
$$PWD/frame_info.h \
$$PWD/signal_info.h \
$$PWD/value_descriptions.h \
$$PWD/tree_node.h \
$$PWD/typefactory.h
+1
View File
@@ -43,6 +43,7 @@ DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
targetSignal.unit = sourceSignal.unit;
targetSignal.receivers = sourceSignal.receivers;
targetSignal.comment = sourceSignal.comment;
targetSignal.valueDescriptions = sourceSignal.valueDescriptions;
targetFrame.signals.push_back(targetSignal);
}
+9 -4
View File
@@ -35,10 +35,15 @@ struct RawCanFrame {
* @brief One decoded signal value.
*/
struct DecodedSignalValue {
const DecodeSignal *definition; /**< Signal definition. */
std::variant<std::int64_t, std::uint64_t> rawValue; /**< Exact signed or unsigned raw value. */
double physicalValue; /**< Converted physical value. */
bool valid; /**< true if decoding succeeded. */
const DecodeSignal *definition; /**< Signal definition. */
RawSignalValue rawValue; /**< Exact signed or unsigned raw value. */
double physicalValue; /**< Converted physical value. */
bool valid; /**< true if decoding succeeded. */
/** Lookup using the raw integer; nullptr for invalid or unmapped values. */
const std::string *GetValueDescription() const {
return valid && definition ? FindValueDescription(definition->valueDescriptions, rawValue) : nullptr;
}
DecodedSignalValue()
: definition(nullptr), rawValue(std::uint64_t(0U)), physicalValue(0.0), valid(false) {
+2
View File
@@ -55,6 +55,7 @@ DbcDatabase DbcParser::ParseFile(const std::string &filePath) const {
continue;
try {
context.lineNumber = lineNumber;
dispatcher.Dispatch(line, context);
} catch (const std::exception &error) {
throw std::runtime_error(filePath + ":" + std::to_string(lineNumber) +
@@ -62,6 +63,7 @@ DbcDatabase DbcParser::ParseFile(const std::string &filePath) const {
}
}
context.ResolveValueDescriptions(filePath);
return database;
}
+2 -1
View File
@@ -24,11 +24,12 @@
* - SG_
* - CM_ BO_
* - CM_ SG_
* - VAL_ signal value descriptions
*
* Ignores:
* - attributes
* - multiplexing
* - value tables
* - named VAL_TABLE_ tables
*/
class DbcParser {
public:
+86
View File
@@ -309,6 +309,7 @@ DbcStatementDispatcher::DbcStatementDispatcher() {
RegisterType<DbcFrameHandler>("BO_");
RegisterType<DbcSignalHandler>("SG_");
RegisterType<DbcCommentHandler>("CM_");
RegisterType<DbcValueDescriptionHandler>("VAL_");
}
bool DbcStatementDispatcher::Dispatch(const std::string &line,
@@ -322,3 +323,88 @@ bool DbcStatementDispatcher::Dispatch(const std::string &line,
handler->second->Handle(line, context);
return true;
}
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 auto rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
if ((rawId & 0x60000000U) != 0U)
throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
PendingValueDescriptions pending;
NormalizeCanId(rawId, pending.canId, pending.isExtended);
pending.signalName = fields[2].str();
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();
RawSignalValue key;
if (!number.empty() && number[0] == '-') {
std::int64_t value = 0;
const auto parsed = std::from_chars(number.data(), number.data() + number.size(), value);
if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = NormalizeValueKey(RawSignalValue(value));
} else {
std::uint64_t value = 0;
const auto parsed = std::from_chars(number.data(), number.data() + number.size(), value);
if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = value;
}
std::string description;
const std::string quoted = fields[2].str();
for (std::size_t i = 0; i < quoted.size(); ++i) {
if (quoted[i] == '\\')
++i;
description += quoted[i];
}
if (!pending.descriptions.emplace(key, description).second)
throw std::runtime_error("Duplicate VAL_ value key: " + number);
const auto consumed = static_cast<std::size_t>(fields.length());
rest.erase(0, consumed);
if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
throw std::runtime_error("Missing whitespace between VAL_ pairs.");
rest = TrimText(rest);
}
if (pending.descriptions.empty())
throw std::runtime_error("VAL_ requires at least one value/description pair.");
context.pendingValues.push_back(std::move(pending));
}
void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
for (const auto &pending: pendingValues) {
try {
FrameInfo *frame = nullptr;
for (auto &candidate: database.frames) {
if (candidate.canId == pending.canId && candidate.isExtended == pending.isExtended) {
if (frame)
throw std::runtime_error("Ambiguous VAL_ frame reference.");
frame = &candidate;
}
}
if (!frame)
throw std::runtime_error("Unresolved VAL_ frame reference.");
SignalInfo *signal = nullptr;
for (auto &candidate: frame->signals) {
if (candidate.name == pending.signalName) {
if (signal)
throw std::runtime_error("Ambiguous VAL_ signal reference: " + pending.signalName);
signal = &candidate;
}
}
if (!signal)
throw std::runtime_error("Unresolved VAL_ signal reference: " + pending.signalName);
for (const auto &entry: pending.descriptions) {
if (!signal->valueDescriptions.insert(entry).second)
throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
}
} catch (const std::exception &error) {
throw std::runtime_error(filePath + ":" + std::to_string(pending.lineNumber) + ": " + error.what());
}
}
}
+17
View File
@@ -7,10 +7,22 @@
#include <string>
#include <unordered_map>
struct PendingValueDescriptions {
std::uint32_t canId;
bool isExtended;
std::string signalName;
ValueDescriptions descriptions;
std::size_t lineNumber;
};
/** Mutable state belonging to one file parse. Handlers do not retain it. */
struct DbcParseContext {
DbcDatabase &database;
FrameInfo *currentFrame = nullptr;
std::size_t lineNumber = 0U;
std::vector<PendingValueDescriptions> pendingValues = {};
void ResolveValueDescriptions(const std::string &filePath);
};
class DbcStatementHandler {
@@ -43,6 +55,11 @@ private:
static void ParseCommentLine(const std::string &line, DbcDatabase &database);
};
class DbcValueDescriptionHandler : public DbcStatementHandler {
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
};
/** Exact-key dispatch to handlers created once per registration via TypeFactory. */
class DbcStatementDispatcher {
public:
+14 -12
View File
@@ -12,6 +12,7 @@
#ifndef DECODE_DATABASE_H
#define DECODE_DATABASE_H
#include "value_descriptions.h"
#include <cstdint>
#include <string>
#include <unordered_map>
@@ -37,18 +38,19 @@ enum class ValueType {
* @brief Runtime-ready signal definition.
*/
struct DecodeSignal {
std::string name; /**< Signal name. */
std::uint32_t startBit; /**< DBC start bit. */
std::uint32_t length; /**< Signal length in bits. */
ByteOrder byteOrder; /**< Intel or Motorola. */
ValueType valueType; /**< Signed or unsigned. */
double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Receivers. */
std::string comment; /**< Comment. */
std::string name; /**< Signal name. */
std::uint32_t startBit; /**< DBC start bit. */
std::uint32_t length; /**< Signal length in bits. */
ByteOrder byteOrder; /**< Intel or Motorola. */
ValueType valueType; /**< Signed or unsigned. */
double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Receivers. */
ValueDescriptions valueDescriptions; /**< Raw integer state descriptions. */
std::string comment; /**< Comment. */
DecodeSignal()
: name(), startBit(0U), length(0U), byteOrder(ByteOrder::Intel), valueType(ValueType::Unsigned), factor(1.0), offset(0.0), minimum(0.0), maximum(0.0), unit(), receivers(), comment() {
+14 -12
View File
@@ -12,6 +12,7 @@
#ifndef SIGNAL_INFO_H
#define SIGNAL_INFO_H
#include "value_descriptions.h"
#include <cstdint>
#include <string>
#include <vector>
@@ -20,18 +21,19 @@
* @brief Describes one signal inside a DBC frame.
*/
struct SignalInfo {
std::string name; /**< Signal name. */
std::uint32_t startBit; /**< Start bit in DBC notation. */
std::uint32_t length; /**< Signal length in bits. */
bool isLittleEndian; /**< true for Intel, false for Motorola. */
bool isSigned; /**< true if signal is signed. */
double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Signal receivers. */
std::string comment; /**< Optional signal comment. */
std::string name; /**< Signal name. */
std::uint32_t startBit; /**< Start bit in DBC notation. */
std::uint32_t length; /**< Signal length in bits. */
bool isLittleEndian; /**< true for Intel, false for Motorola. */
bool isSigned; /**< true if signal is signed. */
double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Signal receivers. */
ValueDescriptions valueDescriptions; /**< Raw integer state descriptions. */
std::string comment; /**< Optional signal comment. */
SignalInfo()
: name(), startBit(0U), length(0U), isLittleEndian(true), isSigned(false), factor(1.0), offset(0.0), minimum(0.0), maximum(0.0), unit(), receivers(), comment() {
+30
View File
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
#ifndef VALUE_DESCRIPTIONS_H
#define VALUE_DESCRIPTIONS_H
#include <cstdint>
#include <map>
#include <string>
#include <variant>
using RawSignalValue = std::variant<std::int64_t, std::uint64_t>;
/** Canonical keys: negative int64_t, nonnegative uint64_t. */
inline RawSignalValue NormalizeValueKey(const RawSignalValue &value) {
if (const auto *number = std::get_if<std::int64_t>(&value)) {
if (*number >= 0)
return static_cast<std::uint64_t>(*number);
}
return value;
}
using ValueDescriptions = std::map<RawSignalValue, std::string>;
/** Returns a borrowed string, or nullptr; never converts integers to double. */
inline const std::string *FindValueDescription(const ValueDescriptions &descriptions,
const RawSignalValue &rawValue) {
const auto found = descriptions.find(NormalizeValueKey(rawValue));
return found == descriptions.end() ? nullptr : &found->second;
}
#endif
+4
View File
@@ -50,6 +50,10 @@ error paths and line numbers, and integration with signals and comments.
Signal tests cover complete syntax and numeric validation, scientific notation,
finite physical parameters and ordered ranges, spaces and tabs, receiver lists,
unsupported multiplexing, and Intel/Motorola declared-payload boundaries.
Value-description tests cover full `VAL_` syntax, whitespace, escaped quotes,
empty text, exact integer boundaries, duplicate keys within and across statements,
deferred and normalized frame references, missing targets, error locations,
raw lookup with scaling, unknown/invalid values, and both database builders.
Temporary DBC fixtures are created in the system temporary directory and removed
automatically.
+126 -1
View File
@@ -1,5 +1,9 @@
#include "dbc_decode_builder.h"
#include "dbc_decoder.h"
#include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include "dbc_tree_builder.h"
#include <limits>
#include <QtTest>
#include <chrono>
@@ -66,6 +70,8 @@ private Q_SLOTS:
void validSignals();
void dispatch();
void independentSessions();
void valueDescriptions();
void invalidValueDescriptions();
};
void DbcParserTest::validFrames() {
@@ -256,7 +262,7 @@ void DbcParserTest::dispatch() {
"Factory dispatch and handler reuse");
CHECK(!dispatcher.Dispatch("TEST_OTHER data", context) &&
!dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) &&
!dispatcher.Dispatch("VAL_ 1 Speed 0 \"Stopped\";", context) &&
!dispatcher.Dispatch("VAL_TABLE_ States 0 \"Stopped\";", context) &&
CountingHandler::calls == 2,
"Exact and unknown keyword dispatch");
}
@@ -281,6 +287,125 @@ void DbcParserTest::independentSessions() {
"Fresh parse after error");
}
void DbcParserTest::valueDescriptions() {
Fixture fixture;
fixture.Write(
"VAL_\t256\tState\t0\t\"Off\"\t1 \"On\" 2 \"Error with spaces; and \\\"quotes\\\"\";\n"
"BO_ 256 Status: 8 ECU\n"
"SG_ State : 0|8@1+ (2,10) [0|520] \"\" ECU\n"
"VAL_ 256 State 3 \"\";\n"
"VAL_ 2147483904 State -9223372036854775808 \"Minimum\" -1 \"Negative\" 9223372036854775807 \"Maximum\";\n"
"BO_ 2147483904 Extended: 8 ECU\n"
"SG_ State : 0|64@1- (1,0) [-1e20|1e20] \"\" ECU\n"
"BO_ 2048 Unsigned: 8 ECU\n"
"SG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n"
"VAL_ 2147485696 State 9223372036854775808 \"High\" 18446744073709551615 \"Full\";\n");
const auto parsed = DbcParser().ParseFile(fixture.path.string());
const auto runtime = DbcDecodeBuilder().Build(parsed);
const auto tree = DbcTreeBuilder().Build(parsed);
CHECK(tree->GetChild(0)->GetSignal() == nullptr, "Frame node");
for (std::size_t index = 0; index < parsed.frames.size(); ++index) {
CHECK(tree->GetChild(index)->GetChild(0)->GetSignal()->valueDescriptions ==
parsed.frames[index].signals[0].valueDescriptions &&
runtime.frames[index].signals[0].valueDescriptions ==
parsed.frames[index].signals[0].valueDescriptions,
"All mappings survive both builders");
}
CHECK(tree->GetChild(0)->GetChild(0)->GetSignal()->valueDescriptions ==
runtime.frames[0].signals[0].valueDescriptions,
"Both builders preserve descriptions");
RawCanFrame frame;
frame.canId = 256;
frame.data.resize(8);
const std::string expected[] = {"Off", "On", "Error with spaces; and \"quotes\"", ""};
for (unsigned value = 0; value < 5; ++value) {
frame.data[0] = value;
const auto decoded = DbcDecoder().Decode(runtime, frame).signals[0];
CHECK(decoded.valid && decoded.physicalValue == value * 2 + 10, "Numeric scaling unchanged");
const auto description = decoded.GetValueDescription();
CHECK(value < 4 ? description && *description == expected[value] : !description,
"Raw lookup and unknown value");
}
frame.isExtended = true;
struct Boundary {
std::uint64_t bits;
const char *description;
};
for (const auto &test: {Boundary{0x8000000000000000ULL, "Minimum"},
Boundary{0xFFFFFFFFFFFFFFFFULL, "Negative"},
Boundary{0x7FFFFFFFFFFFFFFFULL, "Maximum"}}) {
for (unsigned byte = 0; byte < 8; ++byte)
frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
const auto decoded = DbcDecoder().Decode(runtime, frame).signals[0];
CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
"Signed boundaries and distinct extended frame");
}
frame.canId = 2048;
for (const auto &test: {Boundary{0x8000000000000000ULL, "High"},
Boundary{0xFFFFFFFFFFFFFFFFULL, "Full"}}) {
for (unsigned byte = 0; byte < 8; ++byte)
frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
const auto decoded = DbcDecoder().Decode(runtime, frame).signals[0];
CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
"Unsigned boundaries");
}
CHECK(FindValueDescription(runtime.frames[0].signals[0].valueDescriptions, std::int64_t(1)) != nullptr,
"Nonnegative signed lookup matches unsigned key");
frame.data.clear();
CHECK(!DbcDecoder().Decode(runtime, frame).signals[0].GetValueDescription(), "Invalid decode has no description");
}
void DbcParserTest::invalidValueDescriptions() {
Fixture fixture;
const std::string definitions = "BO_ 1 Frame: 8 ECU\nSG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n";
for (const std::string statement: {
"VAL_", "VAL_ 1 State;", "VAL_ 1 State ;", "VAL_ 1 State 0 \"Off\"",
"VAL_ 1 State 0 \"Off\"; junk", "VAL_ 1 State 0 Off;",
"VAL_ 1 State 0 \"Unclosed;", "VAL_ 1 State 0 \"Off\"1 \"On\";",
"VAL_ 1 State 0 \"Off\" 00 \"Duplicate\";",
"VAL_ 1 State 0 \"Off\" -0 \"Duplicate\";",
"VAL_ 1 State 0 \"Off\";\nVAL_ 1 State 0 \"Duplicate\";",
"VAL_ 2 State 0 \"Missing frame\";", "VAL_ 1 Missing 0 \"Missing signal\";",
"VAL_ -1 State 0 \"Bad ID\";", "VAL_ 536870912 State 0 \"Bad ID\";",
"VAL_ 4294967296 State 0 \"Bad ID\";", "VAL_ Environment 0 \"Unsupported\";",
"VAL_ 1 State 0 \"Bad\\q escape\";"}) {
fixture.Write(definitions + statement + '\n');
const auto line = statement.find('\n') == std::string::npos ? 3 : 4;
try {
DbcParser().ParseFile(fixture.path.string());
CHECK(false, statement);
} catch (const std::runtime_error &error) {
CHECK(std::string(error.what()).find(fixture.path.string() + ":" + std::to_string(line) + ": ") == 0,
statement + " " + error.what());
}
}
for (const std::string suffix: {
"BO_ 1 Other: 8 ECU\n", // Ambiguous normalized frame reference.
"SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n",// Ambiguous signal reference.
""}) {
fixture.Write("VAL_ 1 " + std::string(suffix.empty() ? "Missing" : "State") +
" 0 \"Description\";\n" + definitions + suffix);
try {
DbcParser().ParseFile(fixture.path.string());
CHECK(false, "Deferred missing or ambiguous reference");
} catch (const std::runtime_error &error) {
CHECK(std::string(error.what()).find(fixture.path.string() + ":1: ") == 0,
"Deferred reference retains source location");
}
}
for (const std::string number: {"+1", "1x", "0x10", "1.5", "1e2", "--1",
"-9223372036854775809", "18446744073709551616"}) {
fixture.Write("VAL_ 1 State " + number + " \"Bad\";\n" + definitions);
try {
DbcParser().ParseFile(fixture.path.string());
CHECK(false, number);
} catch (const std::runtime_error &error) {
CHECK(std::string(error.what()).find(fixture.path.string() + ":1: ") == 0, number);
}
}
}
int RunParserTests(int argc, char **argv) {
DbcParserTest tests;
return QTest::qExec(&tests, argc, argv);