Closes
This commit is contained in:
1 parent
65142fdfe3
commit
e5d818d9b2
14 files changed
+393
-39
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user