Compare commits

...
2 Commits
Author SHA1 Message Date
deeaitch b91cf4aae5 Closes #7 2026-10-05 18:37:20 -04:00
deeaitch 8d82b13dad Closes #6 2026-10-05 18:33:19 -04:00
24 changed files with 1248 additions and 1096 deletions

No files matched your search

+65
View File
@@ -0,0 +1,65 @@
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never
+33 -5
View File
@@ -532,9 +532,8 @@ one-based physical line number, including blank lines:
example.dbc:3: Invalid CAN ID: invalid
```
This change validates frame definitions. Signal syntax and unsupported DBC
constructs retain their existing behavior; comprehensive validation is still
planned. File-open errors contain the path but have no line number.
This change validates frame definitions. Signal definitions are also validated as described below; comprehensive
DBC grammar coverage is still planned. File-open errors contain the path but have no line number.
---
@@ -562,6 +561,35 @@ Parsed fields:
---
### Signal validation and parse errors
`SG_` statements are matched completely. Signal names must be nonempty and
contain no whitespace. Spaces and tabs are accepted between syntax elements;
byte order and signedness remain adjacent (`@0+`, `@0-`, `@1+`, or `@1-`).
A quoted unit (which may be empty) and a nonempty comma-separated receiver
list are required. Unexpected trailing fields are rejected.
Start bits must be unsigned decimal integers fitting `uint32_t`; lengths must
be within `1..64` bits. Signs, fractional values, numeric suffixes, and overflow
are rejected for these fields. Every signal bit must fit within the frame's
declared payload length, using Intel increasing bit positions or Motorola
sawtooth traversal. This uses the stored payload length without introducing
an additional CAN or CAN FD payload limit.
Factor, offset, minimum, and maximum accept signed decimal numbers, fractions,
and scientific notation. The entire numeric token must be valid; hexadecimal
numbers, NaN, infinity, conversion overflow, and conversion underflow are
rejected. Minimum must not exceed maximum. Zero and negative factors are allowed.
Multiplexing markers (`M`, `mN`, and combinations) are currently rejected rather
than interpreted as part of a signal name. Multiplexing support and duplicate
frame/signal detection remain separate work.
Invalid signal definitions stop parsing with `path:line: description`, including
when the signal exceeds the declared payload length.
---
## Comments
Frame comment example:
@@ -805,8 +833,8 @@ RegisterType<DbcValueHandler>("VAL_"); // after implementing DbcValueHandler
Registration creates one handler; replacing a registration replaces that instance.
Register during initialization before dispatching. A line is routed using its
full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`.
Unregistered keywords remain ignored. Existing `SG_` and `CM_` syntax policies
are preserved, including requiring a literal space after those keywords.
Unregistered keywords remain ignored. `SG_` accepts spaces and tabs after its keyword; `CM_` retains its existing
policy requiring a literal space.
This factory handles statements inside DBC files. A separate format-level
factory can be introduced when another file format is implemented. The decoder
+1 -1
View File
@@ -20,7 +20,7 @@
* @brief Parsed DBC content stored in a simple internal form.
*/
struct DbcDatabase {
std::vector<FrameInfo> frames; /**< All frames found in the DBC file. */
std::vector<FrameInfo> frames; /**< All frames found in the DBC file. */
};
#endif /* DBC_DATABASE_H */
+35 -35
View File
@@ -11,46 +11,46 @@
#include "dbc_decode_builder.h"
DecodeDatabase DbcDecodeBuilder::Build (const DbcDatabase &source) const {
DecodeDatabase result;
DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
DecodeDatabase result;
for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) {
const FrameInfo &sourceFrame = source.frames[frameIndex];
DecodeFrame targetFrame;
for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) {
const FrameInfo &sourceFrame = source.frames[frameIndex];
DecodeFrame targetFrame;
targetFrame.name = sourceFrame.name;
targetFrame.canId = sourceFrame.canId;
targetFrame.isExtended = sourceFrame.isExtended;
targetFrame.dlc = sourceFrame.dlc;
targetFrame.pgn = sourceFrame.pgn;
targetFrame.hasPgn = sourceFrame.hasPgn;
targetFrame.transmitter = sourceFrame.transmitter;
targetFrame.comment = sourceFrame.comment;
targetFrame.name = sourceFrame.name;
targetFrame.canId = sourceFrame.canId;
targetFrame.isExtended = sourceFrame.isExtended;
targetFrame.dlc = sourceFrame.dlc;
targetFrame.pgn = sourceFrame.pgn;
targetFrame.hasPgn = sourceFrame.hasPgn;
targetFrame.transmitter = sourceFrame.transmitter;
targetFrame.comment = sourceFrame.comment;
for (std::size_t signalIndex = 0U; signalIndex < sourceFrame.signals.size(); ++signalIndex) {
const SignalInfo &sourceSignal = sourceFrame.signals[signalIndex];
DecodeSignal targetSignal;
for (std::size_t signalIndex = 0U; signalIndex < sourceFrame.signals.size(); ++signalIndex) {
const SignalInfo &sourceSignal = sourceFrame.signals[signalIndex];
DecodeSignal targetSignal;
targetSignal.name = sourceSignal.name;
targetSignal.startBit = sourceSignal.startBit;
targetSignal.length = sourceSignal.length;
targetSignal.byteOrder = sourceSignal.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola;
targetSignal.valueType = sourceSignal.isSigned ? ValueType::Signed : ValueType::Unsigned;
targetSignal.factor = sourceSignal.factor;
targetSignal.offset = sourceSignal.offset;
targetSignal.minimum = sourceSignal.minimum;
targetSignal.maximum = sourceSignal.maximum;
targetSignal.unit = sourceSignal.unit;
targetSignal.receivers = sourceSignal.receivers;
targetSignal.comment = sourceSignal.comment;
targetSignal.name = sourceSignal.name;
targetSignal.startBit = sourceSignal.startBit;
targetSignal.length = sourceSignal.length;
targetSignal.byteOrder = sourceSignal.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola;
targetSignal.valueType = sourceSignal.isSigned ? ValueType::Signed : ValueType::Unsigned;
targetSignal.factor = sourceSignal.factor;
targetSignal.offset = sourceSignal.offset;
targetSignal.minimum = sourceSignal.minimum;
targetSignal.maximum = sourceSignal.maximum;
targetSignal.unit = sourceSignal.unit;
targetSignal.receivers = sourceSignal.receivers;
targetSignal.comment = sourceSignal.comment;
targetFrame.signals.push_back (targetSignal);
targetFrame.signals.push_back(targetSignal);
}
result.frames.push_back(targetFrame);
result.frameIndexByKey[FrameKey(targetFrame.canId, targetFrame.isExtended)] =
result.frames.size() - 1U;
}
result.frames.push_back (targetFrame);
result.frameIndexByKey[FrameKey (targetFrame.canId, targetFrame.isExtended)] =
result.frames.size() - 1U;
}
return result;
return result;
}
+2 -2
View File
@@ -19,13 +19,13 @@
* @brief Converts parsed DBC data into runtime decode database.
*/
class DbcDecodeBuilder {
public:
public:
/**
* @brief Build runtime decode database.
* @param source Parsed DBC database.
* @return Runtime-ready decode database.
*/
DecodeDatabase Build (const DbcDatabase &source) const;
DecodeDatabase Build(const DbcDatabase &source) const;
};
#endif /* DBC_DECODE_BUILDER_H */
+101 -99
View File
@@ -11,104 +11,105 @@
#include "dbc_decoder.h"
const DecodeFrame *DbcDecoder::FindFrame (const DecodeDatabase &database,
std::uint32_t canId,
bool isExtended) const {
const FrameKey key (canId, isExtended);
const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
database.frameIndexByKey.find (key);
const DecodeFrame *DbcDecoder::FindFrame(const DecodeDatabase &database,
std::uint32_t canId,
bool isExtended) const {
const FrameKey key(canId, isExtended);
const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
database.frameIndexByKey.find(key);
if (it == database.frameIndexByKey.end())
return nullptr;
if (it == database.frameIndexByKey.end())
return nullptr;
const std::size_t index = it->second;
if (index >= database.frames.size())
return nullptr;
const std::size_t index = it->second;
if (index >= database.frames.size())
return nullptr;
return &database.frames[index];
return &database.frames[index];
}
DecodedFrameValue DbcDecoder::Decode (const DecodeDatabase &database,
const RawCanFrame &frame) const {
DecodedFrameValue result;
const DecodeFrame *definition = FindFrame (database, frame.canId, frame.isExtended);
DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database,
const RawCanFrame &frame) const {
DecodedFrameValue result;
const DecodeFrame *definition = FindFrame(database, frame.canId, frame.isExtended);
if (definition == nullptr)
return result;
if (definition == nullptr)
return result;
result.definition = definition;
result.valid = true;
result.definition = definition;
result.valid = true;
for (std::size_t signalIndex = 0U; signalIndex < definition->signals.size(); ++signalIndex) {
const DecodeSignal &signal = definition->signals[signalIndex];
DecodedSignalValue decoded;
std::uint64_t unsignedValue = 0U;
for (std::size_t signalIndex = 0U; signalIndex < definition->signals.size(); ++signalIndex) {
const DecodeSignal &signal = definition->signals[signalIndex];
DecodedSignalValue decoded;
std::uint64_t unsignedValue = 0U;
decoded.definition = &signal;
decoded.definition = &signal;
if (!ExtractUnsigned (frame.data, signal, unsignedValue)) {
decoded.valid = false;
result.signals.push_back (decoded);
continue;
if (!ExtractUnsigned(frame.data, signal, unsignedValue)) {
decoded.valid = false;
result.signals.push_back(decoded);
continue;
}
if (signal.valueType == ValueType::Signed)
decoded.rawValue = SignExtend(unsignedValue, signal.length);
else
decoded.rawValue = unsignedValue;
decoded.physicalValue =
std::visit([&signal](auto rawValue) {
return (static_cast<double>(rawValue) * signal.factor) + signal.offset;
},
decoded.rawValue);
decoded.valid = true;
result.signals.push_back(decoded);
}
if (signal.valueType == ValueType::Signed)
decoded.rawValue = SignExtend (unsignedValue, signal.length);
else
decoded.rawValue = unsignedValue;
decoded.physicalValue =
std::visit ([&signal] (auto rawValue) {
return (static_cast<double> (rawValue) * signal.factor) + signal.offset;
}, decoded.rawValue);
decoded.valid = true;
result.signals.push_back (decoded);
}
return result;
return result;
}
bool DbcDecoder::ExtractUnsigned (const std::vector<std::uint8_t> &data,
const DecodeSignal &signal,
std::uint64_t &value) {
if ((signal.length == 0U) || (signal.length > 64U))
return false;
bool DbcDecoder::ExtractUnsigned(const std::vector<std::uint8_t> &data,
const DecodeSignal &signal,
std::uint64_t &value) {
if ((signal.length == 0U) || (signal.length > 64U))
return false;
if (signal.byteOrder == ByteOrder::Intel)
return ExtractIntel (data, signal.startBit, signal.length, value);
if (signal.byteOrder == ByteOrder::Intel)
return ExtractIntel(data, signal.startBit, signal.length, value);
return ExtractMotorola (data, signal.startBit, signal.length, value);
return ExtractMotorola(data, signal.startBit, signal.length, value);
}
bool DbcDecoder::ExtractIntel (const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value) {
value = 0U;
bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value) {
value = 0U;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
const std::uint32_t absoluteBit = startBit + bitIndex;
const std::uint32_t byteIndex = absoluteBit / 8U;
const std::uint32_t bitInByte = absoluteBit % 8U;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
const std::uint32_t absoluteBit = startBit + bitIndex;
const std::uint32_t byteIndex = absoluteBit / 8U;
const std::uint32_t bitInByte = absoluteBit % 8U;
if (byteIndex >= data.size())
return false;
if (byteIndex >= data.size())
return false;
const std::uint64_t bitValue =
(static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
const std::uint64_t bitValue =
(static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
value |= bitValue;
}
value |= bitValue;
}
return true;
return true;
}
bool DbcDecoder::ExtractMotorola (const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value) {
/*
bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value) {
/*
* DBC Motorola bit numbering:
* - startBit points to the most significant bit of the signal
* - inside a byte, bit numbering goes 7..0
@@ -116,40 +117,41 @@ bool DbcDecoder::ExtractMotorola (const std::vector<std::uint8_t> &data,
* Example: a signal starting at 7 follows 7..0, 15..8, 23..16.
*/
value = 0U;
value = 0U;
std::size_t byteIndex = startBit / 8U;
std::uint32_t bitInByte = startBit % 8U;
std::size_t byteIndex = startBit / 8U;
std::uint32_t bitInByte = startBit % 8U;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
if (byteIndex >= data.size())
return false;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
if (byteIndex >= data.size())
return false;
value <<= 1U;
value |= static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U);
value <<= 1U;
value |= static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U);
if (bitInByte == 0U) {
++byteIndex;
bitInByte = 7U;
} else
--bitInByte;
}
if (bitInByte == 0U) {
++byteIndex;
bitInByte = 7U;
} else
--bitInByte;
}
return true;
return true;
}
std::int64_t DbcDecoder::SignExtend (std::uint64_t value, std::uint32_t bitLength) {
if ((bitLength == 0U) || (bitLength > 64U))
return 0;
std::int64_t DbcDecoder::SignExtend(std::uint64_t value, std::uint32_t bitLength) {
if ((bitLength == 0U) || (bitLength > 64U))
return 0;
const std::uint64_t signMask = std::uint64_t (1U) << (bitLength - 1U);
const std::uint64_t valueMask = (bitLength == 64U)
? ~std::uint64_t (0U) : (std::uint64_t (1U) << bitLength) - 1U;
value &= valueMask;
const std::uint64_t signMask = std::uint64_t(1U) << (bitLength - 1U);
const std::uint64_t valueMask = (bitLength == 64U)
? ~std::uint64_t(0U)
: (std::uint64_t(1U) << bitLength) - 1U;
value &= valueMask;
if ((value & signMask) == 0U)
return static_cast<std::int64_t> (value);
if ((value & signMask) == 0U)
return static_cast<std::int64_t>(value);
// The complemented magnitude fits int64_t, including for INT64_MIN.
return -1 - static_cast<std::int64_t> ((~value) & valueMask);
// The complemented magnitude fits int64_t, including for INT64_MIN.
return -1 - static_cast<std::int64_t>((~value) & valueMask);
}
+39 -46
View File
@@ -12,9 +12,9 @@
#ifndef DBC_DECODER_H
#define DBC_DECODER_H
#include <vector>
#include <cstdint>
#include <variant>
#include <vector>
#include "decode_database.h"
@@ -22,54 +22,47 @@
* @brief Raw CAN frame used for runtime or trace decoding.
*/
struct RawCanFrame {
std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */
std::vector<std::uint8_t> data; /**< Payload bytes. */
std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */
std::vector<std::uint8_t> data; /**< Payload bytes. */
RawCanFrame()
: canId (0U)
, isExtended (false)
, data() {
}
RawCanFrame()
: canId(0U), isExtended(false), data() {
}
};
/**
* @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. */
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. */
DecodedSignalValue()
: definition (nullptr)
, rawValue (std::uint64_t (0U))
, physicalValue (0.0)
, valid (false) {
}
DecodedSignalValue()
: definition(nullptr), rawValue(std::uint64_t(0U)), physicalValue(0.0), valid(false) {
}
};
/**
* @brief Fully decoded frame.
*/
struct DecodedFrameValue {
const DecodeFrame *definition; /**< Frame definition. */
std::vector<DecodedSignalValue> signals; /**< Decoded signal values. */
bool valid; /**< true if frame was matched. */
const DecodeFrame *definition; /**< Frame definition. */
std::vector<DecodedSignalValue> signals; /**< Decoded signal values. */
bool valid; /**< true if frame was matched. */
DecodedFrameValue()
: definition (nullptr)
, signals()
, valid (false) {
}
DecodedFrameValue()
: definition(nullptr), signals(), valid(false) {
}
};
/**
* @brief Runtime CAN decoder using prebuilt decode database.
*/
class DbcDecoder {
public:
public:
/**
* @brief Find frame definition by CAN ID.
* @param database Runtime decode database.
@@ -77,9 +70,9 @@ class DbcDecoder {
* @param isExtended true for extended frame.
* @return Pointer to frame definition or nullptr.
*/
const DecodeFrame *FindFrame (const DecodeDatabase &database,
std::uint32_t canId,
bool isExtended) const;
const DecodeFrame *FindFrame(const DecodeDatabase &database,
std::uint32_t canId,
bool isExtended) const;
/**
* @brief Decode one raw CAN frame.
@@ -87,25 +80,25 @@ class DbcDecoder {
* @param frame Raw CAN frame.
* @return Decoded frame value.
*/
DecodedFrameValue Decode (const DecodeDatabase &database,
const RawCanFrame &frame) const;
DecodedFrameValue Decode(const DecodeDatabase &database,
const RawCanFrame &frame) const;
private:
static bool ExtractUnsigned (const std::vector<std::uint8_t> &data,
const DecodeSignal &signal,
std::uint64_t &value);
private:
static bool ExtractUnsigned(const std::vector<std::uint8_t> &data,
const DecodeSignal &signal,
std::uint64_t &value);
static bool ExtractIntel (const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value);
static bool ExtractIntel(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value);
static bool ExtractMotorola (const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value);
static bool ExtractMotorola(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value);
static std::int64_t SignExtend (std::uint64_t value, std::uint32_t bitLength);
static std::int64_t SignExtend(std::uint64_t value, std::uint32_t bitLength);
};
#endif /* DBC_DECODER_H */
+41 -41
View File
@@ -12,59 +12,59 @@
#include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include <cctype>
#include <fstream>
#include <stdexcept>
#include <cctype>
namespace {
/**
/**
* @brief Remove leading and trailing spaces.
* @param text Input text.
* @return Trimmed text.
*/
std::string TrimText (const std::string &text) {
std::string::size_type begin = 0U;
while ((begin < text.size()) &&
std::isspace (static_cast<unsigned char> (text[begin])))
++begin;
std::string TrimText(const std::string &text) {
std::string::size_type begin = 0U;
while ((begin < text.size()) &&
std::isspace(static_cast<unsigned char>(text[begin])))
++begin;
std::string::size_type end = text.size();
while ((end > begin) &&
std::isspace (static_cast<unsigned char> (text[end - 1U])))
--end;
std::string::size_type end = text.size();
while ((end > begin) &&
std::isspace(static_cast<unsigned char>(text[end - 1U])))
--end;
return text.substr (begin, end - begin);
}
}
DbcDatabase DbcParser::ParseFile (const std::string &filePath) const {
std::ifstream input (filePath.c_str());
if (!input.is_open())
throw std::runtime_error ("Failed to open DBC file: " + filePath);
DbcDatabase database;
DbcParseContext context {database, nullptr};
const DbcStatementDispatcher dispatcher;
std::size_t lineNumber = 0U;
std::string line;
while (std::getline (input, line)) {
++lineNumber;
line = Trim (line);
if (line.empty())
continue;
try {
dispatcher.Dispatch (line, context);
} catch (const std::exception &error) {
throw std::runtime_error (filePath + ":" + std::to_string (lineNumber) +
": " + error.what());
return text.substr(begin, end - begin);
}
}
}// namespace
return database;
DbcDatabase DbcParser::ParseFile(const std::string &filePath) const {
std::ifstream input(filePath.c_str());
if (!input.is_open())
throw std::runtime_error("Failed to open DBC file: " + filePath);
DbcDatabase database;
DbcParseContext context{database, nullptr};
const DbcStatementDispatcher dispatcher;
std::size_t lineNumber = 0U;
std::string line;
while (std::getline(input, line)) {
++lineNumber;
line = Trim(line);
if (line.empty())
continue;
try {
dispatcher.Dispatch(line, context);
} catch (const std::exception &error) {
throw std::runtime_error(filePath + ":" + std::to_string(lineNumber) +
": " + error.what());
}
}
return database;
}
std::string DbcParser::Trim (const std::string &text) {
return TrimText (text);
std::string DbcParser::Trim(const std::string &text) {
return TrimText(text);
}
+4 -4
View File
@@ -31,7 +31,7 @@
* - value tables
*/
class DbcParser {
public:
public:
/**
* @brief Parse DBC file.
* @param filePath Path to input file.
@@ -39,10 +39,10 @@ class DbcParser {
* @throws std::runtime_error on file or parse errors.
* Line parse errors include the file path and one-based line number.
*/
DbcDatabase ParseFile (const std::string &filePath) const;
DbcDatabase ParseFile(const std::string &filePath) const;
private:
static std::string Trim (const std::string &text);
private:
static std::string Trim(const std::string &text);
};
#endif /* DBC_PARSER_H */
+243 -278
View File
@@ -1,358 +1,323 @@
// SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h"
#include <sstream>
#include <stdexcept>
#include <cctype>
#include <charconv>
#include <cmath>
#include <limits>
#include <regex>
#include <sstream>
#include <stdexcept>
namespace {
std::uint32_t ParseDecimal (const std::string &text,
std::uint32_t maximum,
const char *field) {
std::uint32_t value = 0U;
const auto parsed = std::from_chars (text.data(), text.data() + text.size(), value);
if (text.empty() || parsed.ec != std::errc() ||
parsed.ptr != text.data() + text.size() || value > maximum)
throw std::runtime_error (std::string ("Invalid ") + field + ": " + text);
return value;
}
std::uint32_t ParseDecimal(const std::string &text,
std::uint32_t maximum,
const char *field) {
std::uint32_t value = 0U;
const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value);
if (text.empty() || parsed.ec != std::errc() ||
parsed.ptr != text.data() + text.size() || value > maximum)
throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
return value;
}
/**
/**
* @brief Remove leading and trailing spaces.
* @param text Input text.
* @return Trimmed text.
*/
std::string TrimText (const std::string &text) {
std::string::size_type begin = 0U;
while ((begin < text.size()) &&
std::isspace (static_cast<unsigned char> (text[begin])))
++begin;
std::string TrimText(const std::string &text) {
std::string::size_type begin = 0U;
while ((begin < text.size()) &&
std::isspace(static_cast<unsigned char>(text[begin])))
++begin;
std::string::size_type end = text.size();
while ((end > begin) &&
std::isspace (static_cast<unsigned char> (text[end - 1U])))
--end;
std::string::size_type end = text.size();
while ((end > begin) &&
std::isspace(static_cast<unsigned char>(text[end - 1U])))
--end;
return text.substr (begin, end - begin);
}
FrameInfo *FindFrameById (DbcDatabase &, std::uint32_t, bool);
SignalInfo *FindSignalByName (FrameInfo &, const std::string &);
}
return text.substr(begin, end - begin);
}
FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
}// namespace
namespace {
std::vector<std::string> SplitReceivers (const std::string &text) {
std::vector<std::string> receivers;
std::string token;
std::istringstream stream (text);
std::vector<std::string> SplitReceivers(const std::string &text) {
std::vector<std::string> receivers;
std::string token;
std::istringstream stream(text);
while (std::getline (stream, token, ',')) {
token = TrimText (token);
if (!token.empty())
receivers.push_back (token);
}
while (std::getline(stream, token, ',')) {
token = TrimText(token);
if (!token.empty())
receivers.push_back(token);
}
return receivers;
}
return receivers;
}
void NormalizeCanId (std::uint32_t rawCanId,
std::uint32_t &normalizedCanId,
bool &isExtended) {
/*
void NormalizeCanId(std::uint32_t rawCanId,
std::uint32_t &normalizedCanId,
bool &isExtended) {
/*
* DBC commonly stores extended identifiers with bit 31 set.
* Example:
* raw id = 0x80000000 | actual_29_bit_id
*/
if ((rawCanId & 0x80000000U) != 0U) {
isExtended = true;
normalizedCanId = (rawCanId & 0x1FFFFFFFU);
} else {
isExtended = (rawCanId > 0x7FFU);
normalizedCanId = rawCanId;
}
}
if ((rawCanId & 0x80000000U) != 0U) {
isExtended = true;
normalizedCanId = (rawCanId & 0x1FFFFFFFU);
} else {
isExtended = (rawCanId > 0x7FFU);
normalizedCanId = rawCanId;
}
}
std::uint32_t TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn) {
hasPgn = false;
std::uint32_t TryExtractPgn(std::uint32_t canId, bool isExtended, bool &hasPgn) {
hasPgn = false;
if (!isExtended)
return 0U;
if (!isExtended)
return 0U;
if ((canId & 0x1FFFFFFFU) != canId)
return 0U;
if ((canId & 0x1FFFFFFFU) != canId)
return 0U;
const std::uint32_t pf = (canId >> 16U) & 0xFFU;
const std::uint32_t ps = (canId >> 8U) & 0xFFU;
const std::uint32_t dp = (canId >> 24U) & 0x01U;
const std::uint32_t pf = (canId >> 16U) & 0xFFU;
const std::uint32_t ps = (canId >> 8U) & 0xFFU;
const std::uint32_t dp = (canId >> 24U) & 0x01U;
std::uint32_t pgn = 0U;
std::uint32_t pgn = 0U;
if (pf < 240U)
pgn = (dp << 16U) | (pf << 8U);
else
pgn = (dp << 16U) | (pf << 8U) | ps;
if (pf < 240U)
pgn = (dp << 16U) | (pf << 8U);
else
pgn = (dp << 16U) | (pf << 8U) | ps;
hasPgn = true;
return pgn;
}
hasPgn = true;
return pgn;
}
}
}// namespace
FrameInfo DbcFrameHandler::ParseFrameLine (const std::string &line) {
/*
FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
/*
* Example:
* BO_ 256 EngineData: 8 EEC1
*/
std::istringstream stream (line);
std::string keyword;
std::string idText;
if (!(stream >> keyword >> idText) || keyword != "BO_")
throw std::runtime_error ("Missing CAN ID in frame definition.");
std::istringstream stream(line);
std::string keyword;
std::string idText;
if (!(stream >> keyword >> idText) || keyword != "BO_")
throw std::runtime_error("Missing CAN ID in frame definition.");
const std::uint32_t rawCanId = ParseDecimal (
idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
// Only bit 31 may accompany the actual 29-bit identifier.
if ((rawCanId & 0x60000000U) != 0U)
throw std::runtime_error ("CAN ID exceeds the 29-bit identifier range.");
const std::uint32_t rawCanId = ParseDecimal(
idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
// Only bit 31 may accompany the actual 29-bit identifier.
if ((rawCanId & 0x60000000U) != 0U)
throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
std::string nameText;
if (!std::getline (stream, nameText, ':') || stream.eof())
throw std::runtime_error ("Missing ':' in frame definition.");
std::istringstream nameStream (nameText);
FrameInfo frame;
std::string extra;
if (!(nameStream >> frame.name) || (nameStream >> extra))
throw std::runtime_error ("Missing or invalid frame name.");
std::string nameText;
if (!std::getline(stream, nameText, ':') || stream.eof())
throw std::runtime_error("Missing ':' in frame definition.");
std::istringstream nameStream(nameText);
FrameInfo frame;
std::string extra;
if (!(nameStream >> frame.name) || (nameStream >> extra))
throw std::runtime_error("Missing or invalid frame name.");
std::string lengthText;
if (!(stream >> lengthText))
throw std::runtime_error ("Missing payload length in frame definition.");
frame.dlc = static_cast<std::uint8_t> (ParseDecimal (
lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
if (!(stream >> frame.transmitter))
throw std::runtime_error ("Missing transmitter in frame definition.");
if (stream >> extra)
throw std::runtime_error ("Unexpected trailing field in frame definition: " + extra);
std::string lengthText;
if (!(stream >> lengthText))
throw std::runtime_error("Missing payload length in frame definition.");
frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
if (!(stream >> frame.transmitter))
throw std::runtime_error("Missing transmitter in frame definition.");
if (stream >> extra)
throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
NormalizeCanId (rawCanId, frame.canId, frame.isExtended);
frame.pgn = TryExtractPgn (frame.canId, frame.isExtended, frame.hasPgn);
NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn);
return frame;
return frame;
}
SignalInfo DbcSignalHandler::ParseSignalLine (const std::string &line) {
/*
* Example:
* SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
*/
SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
// Match the complete statement before converting any numeric fields.
static const std::regex syntax(
R"dbc(^SG_[ \t]+([^:]+):[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*\([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*"([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
std::smatch fields;
if (!std::regex_match(line, fields, syntax))
throw std::runtime_error("Malformed or unsupported SG_ signal definition.");
SignalInfo signal;
SignalInfo signal;
signal.name = TrimText(fields[1].str());
if (signal.name.empty() || signal.name.find_first_of(" \t\r\n") != std::string::npos)
throw std::runtime_error("Invalid signal name or unsupported multiplexing syntax.");
std::string work = TrimText (line);
if (work.compare (0U, 4U, "SG_ ") != 0)
throw std::runtime_error ("Invalid signal line: " + line);
signal.startBit = ParseDecimal(fields[2].str(),
std::numeric_limits<std::uint32_t>::max(), "signal start bit");
signal.length = ParseDecimal(fields[3].str(), 64U, "signal length");
if (signal.length == 0U)
throw std::runtime_error("Signal length must be within 1..64 bits.");
signal.isLittleEndian = fields[4].str() == "1";
signal.isSigned = fields[5].str() == "-";
work.erase (0U, 4U);
const std::string::size_type colonPos = work.find (':');
if (colonPos == std::string::npos)
throw std::runtime_error ("Signal line missing ':' : " + line);
signal.name = TrimText (work.substr (0U, colonPos));
std::string rest = TrimText (work.substr (colonPos + 1U));
const std::string::size_type pipePos = rest.find ('|');
const std::string::size_type atPos = rest.find ('@');
const std::string::size_type signPos = rest.find_first_of ("+-", atPos);
const std::string::size_type factorBegin = rest.find ('(');
const std::string::size_type factorComma = rest.find (',', factorBegin);
const std::string::size_type factorEnd = rest.find (')', factorComma);
const std::string::size_type rangeBegin = rest.find ('[');
const std::string::size_type rangeSep = rest.find ('|', rangeBegin);
const std::string::size_type rangeEnd = rest.find (']', rangeSep);
const std::string::size_type unitBegin = rest.find ('"', rangeEnd);
const std::string::size_type unitEnd = rest.find ('"', unitBegin + 1U);
if ((pipePos == std::string::npos) ||
(atPos == std::string::npos) ||
(signPos == std::string::npos) ||
(factorBegin == std::string::npos) ||
(factorComma == std::string::npos) ||
(factorEnd == std::string::npos) ||
(rangeBegin == std::string::npos) ||
(rangeSep == std::string::npos) ||
(rangeEnd == std::string::npos) ||
(unitBegin == std::string::npos) ||
(unitEnd == std::string::npos))
throw std::runtime_error ("Unsupported signal syntax: " + line);
signal.startBit = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (0U, pipePos)))
);
signal.length = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (pipePos + 1U, atPos - pipePos - 1U)))
);
{
if ((atPos + 1U) >= rest.size())
throw std::runtime_error ("Invalid endianness in signal: " + line);
const char endianChar = rest[atPos + 1U];
signal.isLittleEndian = (endianChar == '1');
}
{
const char signChar = rest[signPos];
signal.isSigned = (signChar == '-');
}
signal.factor = std::stod (
TrimText (rest.substr (factorBegin + 1U, factorComma - factorBegin - 1U))
);
signal.offset = std::stod (
TrimText (rest.substr (factorComma + 1U, factorEnd - factorComma - 1U))
);
signal.minimum = std::stod (
TrimText (rest.substr (rangeBegin + 1U, rangeSep - rangeBegin - 1U))
);
signal.maximum = std::stod (
TrimText (rest.substr (rangeSep + 1U, rangeEnd - rangeSep - 1U))
);
signal.unit = rest.substr (unitBegin + 1U, unitEnd - unitBegin - 1U);
{
const std::string receiversText = TrimText (rest.substr (unitEnd + 1U));
signal.receivers = SplitReceivers (receiversText);
}
return signal;
const auto parsePhysical = [](const std::string &text, const char *field) {
static const std::regex number(
R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
if (!std::regex_match(text, number))
throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
std::size_t consumed = 0U;
double value;
try {
value = std::stod(text, &consumed);
} catch (const std::exception &) {
throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
}
if (consumed != text.size() || !std::isfinite(value))
throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
return value;
};
signal.factor = parsePhysical(fields[6].str(), "signal factor");
signal.offset = parsePhysical(fields[7].str(), "signal offset");
signal.minimum = parsePhysical(fields[8].str(), "signal minimum");
signal.maximum = parsePhysical(fields[9].str(), "signal maximum");
if (signal.minimum > signal.maximum)
throw std::runtime_error("Signal minimum exceeds maximum.");
signal.unit = fields[10].str();
signal.receivers = SplitReceivers(fields[11].str());
return signal;
}
void DbcCommentHandler::ParseCommentLine (const std::string &line, DbcDatabase &database) {
/*
void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &database) {
/*
* Examples:
* CM_ BO_ 256 "Frame comment";
* CM_ SG_ 256 EngineSpeed "Signal comment";
*/
std::istringstream stream (line);
std::string token;
stream >> token;
std::istringstream stream(line);
std::string token;
stream >> token;
if (token != "CM_")
return;
if (token != "CM_")
return;
stream >> token;
stream >> token;
if (token == "BO_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
if (token == "BO_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
stream >> rawCanId;
NormalizeCanId (rawCanId, canId, isExtended);
stream >> rawCanId;
NormalizeCanId(rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind ('"');
const std::string::size_type quoteBegin = line.find('"');
const std::string::size_type quoteEnd = line.rfind('"');
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr)
frame->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
} else if (token == "SG_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
std::string signalName;
FrameInfo *frame = FindFrameById(database, canId, isExtended);
if (frame != nullptr)
frame->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
} else if (token == "SG_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
std::string signalName;
stream >> rawCanId;
stream >> signalName;
stream >> rawCanId;
stream >> signalName;
NormalizeCanId (rawCanId, canId, isExtended);
NormalizeCanId(rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind ('"');
const std::string::size_type quoteBegin = line.find('"');
const std::string::size_type quoteEnd = line.rfind('"');
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr) {
SignalInfo *signal = FindSignalByName (*frame, signalName);
if (signal != nullptr)
signal->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
FrameInfo *frame = FindFrameById(database, canId, isExtended);
if (frame != nullptr) {
SignalInfo *signal = FindSignalByName(*frame, signalName);
if (signal != nullptr)
signal->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
}
}
}
}
namespace {
FrameInfo *FindFrameById (DbcDatabase &database,
std::uint32_t canId,
bool isExtended) {
for (std::size_t index = 0U; index < database.frames.size(); ++index) {
if ((database.frames[index].canId == canId) &&
(database.frames[index].isExtended == isExtended))
return &database.frames[index];
}
FrameInfo *FindFrameById(DbcDatabase &database,
std::uint32_t canId,
bool isExtended) {
for (std::size_t index = 0U; index < database.frames.size(); ++index) {
if ((database.frames[index].canId == canId) &&
(database.frames[index].isExtended == isExtended))
return &database.frames[index];
}
return nullptr;
return nullptr;
}
SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
if (frame.signals[index].name == signalName)
return &frame.signals[index];
}
return nullptr;
}
}// namespace
void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
context.database.frames.push_back(ParseFrameLine(line));
context.currentFrame = &context.database.frames.back();
}
SignalInfo *FindSignalByName (FrameInfo &frame, const std::string &signalName) {
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
if (frame.signals[index].name == signalName)
return &frame.signals[index];
}
return nullptr;
}
void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
if (context.currentFrame == nullptr)
throw std::runtime_error("Signal found before any frame definition.");
SignalInfo signal = ParseSignalLine(line);
const std::uint64_t payloadBits = static_cast<std::uint64_t>(context.currentFrame->dlc) * 8U;
std::uint64_t bit = signal.startBit;
for (std::uint32_t index = 0U; index < signal.length; ++index) {
if (bit >= payloadBits)
throw std::runtime_error("Signal exceeds the frame payload length.");
if (signal.isLittleEndian)
++bit;
else
bit = (bit % 8U == 0U) ? bit + 15U : bit - 1U;
}
context.currentFrame->signals.push_back(std::move(signal));
}
void DbcFrameHandler::Handle (const std::string &line, DbcParseContext &context) const {
context.database.frames.push_back (ParseFrameLine (line));
context.currentFrame = &context.database.frames.back();
}
void DbcSignalHandler::Handle (const std::string &line, DbcParseContext &context) const {
// Preserve the existing signal syntax policy during this refactor.
if (line.compare (0U, 4U, "SG_ ") != 0)
return;
if (context.currentFrame == nullptr)
throw std::runtime_error ("Signal found before any frame definition.");
context.currentFrame->signals.push_back (ParseSignalLine (line));
}
void DbcCommentHandler::Handle (const std::string &line, DbcParseContext &context) const {
if (line.compare (0U, 4U, "CM_ ") == 0)
ParseCommentLine (line, context.database);
void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
if (line.compare(0U, 4U, "CM_ ") == 0)
ParseCommentLine(line, context.database);
}
DbcStatementDispatcher::DbcStatementDispatcher() {
RegisterType<DbcFrameHandler> ("BO_");
RegisterType<DbcSignalHandler> ("SG_");
RegisterType<DbcCommentHandler> ("CM_");
RegisterType<DbcFrameHandler>("BO_");
RegisterType<DbcSignalHandler>("SG_");
RegisterType<DbcCommentHandler>("CM_");
}
bool DbcStatementDispatcher::Dispatch (const std::string &line,
DbcParseContext &context) const {
std::istringstream stream (line);
std::string keyword;
stream >> keyword;
const auto handler = m_handlers.find (keyword);
if (handler == m_handlers.end())
return false;
handler->second->Handle (line, context);
return true;
bool DbcStatementDispatcher::Dispatch(const std::string &line,
DbcParseContext &context) const {
std::istringstream stream(line);
std::string keyword;
stream >> keyword;
const auto handler = m_handlers.find(keyword);
if (handler == m_handlers.end())
return false;
handler->second->Handle(line, context);
return true;
}
+27 -24
View File
@@ -9,54 +9,57 @@
/** Mutable state belonging to one file parse. Handlers do not retain it. */
struct DbcParseContext {
DbcDatabase &database;
FrameInfo *currentFrame = nullptr;
DbcDatabase &database;
FrameInfo *currentFrame = nullptr;
};
class DbcStatementHandler {
public:
public:
virtual ~DbcStatementHandler() = default;
virtual void Handle (const std::string &line, DbcParseContext &context) const = 0;
virtual void Handle(const std::string &line, DbcParseContext &context) const = 0;
};
class DbcFrameHandler : public DbcStatementHandler {
public:
void Handle (const std::string &line, DbcParseContext &context) const override;
private:
static FrameInfo ParseFrameLine (const std::string &line);
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
private:
static FrameInfo ParseFrameLine(const std::string &line);
};
class DbcSignalHandler : public DbcStatementHandler {
public:
void Handle (const std::string &line, DbcParseContext &context) const override;
private:
static SignalInfo ParseSignalLine (const std::string &line);
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
private:
static SignalInfo ParseSignalLine(const std::string &line);
};
class DbcCommentHandler : public DbcStatementHandler {
public:
void Handle (const std::string &line, DbcParseContext &context) const override;
private:
static void ParseCommentLine (const std::string &line, DbcDatabase &database);
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
private:
static void ParseCommentLine(const std::string &line, DbcDatabase &database);
};
/** Exact-key dispatch to handlers created once per registration via TypeFactory. */
class DbcStatementDispatcher {
public:
public:
DbcStatementDispatcher();
template <class Handler>
void RegisterType (const std::string &keyword) {
m_factory.registerType<Handler> (keyword);
m_handlers.insert_or_assign (keyword, m_factory.create (keyword));
template<class Handler>
void RegisterType(const std::string &keyword) {
m_factory.registerType<Handler>(keyword);
m_handlers.insert_or_assign(keyword, m_factory.create(keyword));
}
/** Returns false for an unregistered keyword; input must be trimmed. */
bool Dispatch (const std::string &line, DbcParseContext &context) const;
bool Dispatch(const std::string &line, DbcParseContext &context) const;
private:
private:
TypeFactory<std::string, DbcStatementHandler> m_factory;
std::unordered_map<std::string, std::shared_ptr<DbcStatementHandler> > m_handlers;
std::unordered_map<std::string, std::shared_ptr<DbcStatementHandler>> m_handlers;
};
#endif /* DBC_STATEMENT_HANDLER_H */
+13 -13
View File
@@ -11,21 +11,21 @@
#include "dbc_tree_builder.h"
std::unique_ptr<TreeNode> DbcTreeBuilder::Build (const DbcDatabase &database) const {
std::unique_ptr<TreeNode> root (new TreeNode());
std::unique_ptr<TreeNode> DbcTreeBuilder::Build(const DbcDatabase &database) const {
std::unique_ptr<TreeNode> root(new TreeNode());
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
const FrameInfo &frame = database.frames[frameIndex];
std::unique_ptr<TreeNode> frameNode (new TreeNode (frame));
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
const FrameInfo &frame = database.frames[frameIndex];
std::unique_ptr<TreeNode> frameNode(new TreeNode(frame));
for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) {
const SignalInfo &signal = frame.signals[signalIndex];
std::unique_ptr<TreeNode> signalNode (new TreeNode (signal));
frameNode->AddChild (std::move (signalNode));
for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) {
const SignalInfo &signal = frame.signals[signalIndex];
std::unique_ptr<TreeNode> signalNode(new TreeNode(signal));
frameNode->AddChild(std::move(signalNode));
}
root->AddChild(std::move(frameNode));
}
root->AddChild (std::move (frameNode));
}
return root;
return root;
}
+2 -2
View File
@@ -21,13 +21,13 @@
* @brief Builds a simple tree from parsed DBC database.
*/
class DbcTreeBuilder {
public:
public:
/**
* @brief Build tree representation of parsed DBC data.
* @param database Parsed database.
* @return Root node of the tree.
*/
std::unique_ptr<TreeNode> Build (const DbcDatabase &database) const;
std::unique_ptr<TreeNode> Build(const DbcDatabase &database) const;
};
#endif /* DBC_TREE_BUILDER_H */
+52 -73
View File
@@ -12,126 +12,105 @@
#ifndef DECODE_DATABASE_H
#define DECODE_DATABASE_H
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
/**
* @brief Signal byte order used for runtime decoding.
*/
enum class ByteOrder {
Intel,
Motorola
Intel,
Motorola
};
/**
* @brief Signal numeric type.
*/
enum class ValueType {
Unsigned,
Signed
Unsigned,
Signed
};
/**
* @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. */
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() {
}
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() {
}
};
/**
* @brief Runtime-ready frame definition.
*/
struct DecodeFrame {
std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */
std::uint8_t dlc; /**< Payload length. */
std::uint32_t pgn; /**< PGN if available. */
bool hasPgn; /**< true if PGN is valid. */
std::string transmitter; /**< Transmitter ECU. */
std::string comment; /**< Frame comment. */
std::vector<DecodeSignal> signals; /**< Signal definitions. */
std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */
std::uint8_t dlc; /**< Payload length. */
std::uint32_t pgn; /**< PGN if available. */
bool hasPgn; /**< true if PGN is valid. */
std::string transmitter; /**< Transmitter ECU. */
std::string comment; /**< Frame comment. */
std::vector<DecodeSignal> signals; /**< Signal definitions. */
DecodeFrame()
: name()
, canId (0U)
, isExtended (false)
, dlc (0U)
, pgn (0U)
, hasPgn (false)
, transmitter()
, comment()
, signals() {
}
DecodeFrame()
: name(), canId(0U), isExtended(false), dlc(0U), pgn(0U), hasPgn(false), transmitter(), comment(), signals() {
}
};
/**
* @brief Key for fast frame lookup.
*/
struct FrameKey {
std::uint32_t canId;
bool isExtended;
std::uint32_t canId;
bool isExtended;
FrameKey()
: canId (0U)
, isExtended (false) {
}
FrameKey()
: canId(0U), isExtended(false) {
}
FrameKey (std::uint32_t id, bool extended)
: canId (id)
, isExtended (extended) {
}
FrameKey(std::uint32_t id, bool extended)
: canId(id), isExtended(extended) {
}
bool operator== (const FrameKey &other) const {
return (canId == other.canId) && (isExtended == other.isExtended);
}
bool operator==(const FrameKey &other) const {
return (canId == other.canId) && (isExtended == other.isExtended);
}
};
/**
* @brief Hasher for frame key.
*/
struct FrameKeyHasher {
std::size_t operator() (const FrameKey &key) const {
const std::size_t a = static_cast<std::size_t> (key.canId);
const std::size_t b = key.isExtended ? 1U : 0U;
return (a * 1315423911U) ^ b;
}
std::size_t operator()(const FrameKey &key) const {
const std::size_t a = static_cast<std::size_t>(key.canId);
const std::size_t b = key.isExtended ? 1U : 0U;
return (a * 1315423911U) ^ b;
}
};
/**
* @brief Runtime decode database with fast lookup by CAN ID.
*/
struct DecodeDatabase {
std::vector<DecodeFrame> frames;
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
std::vector<DecodeFrame> frames;
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
};
#endif /* DECODE_DATABASE_H */
+13 -21
View File
@@ -12,9 +12,9 @@
#ifndef FRAME_INFO_H
#define FRAME_INFO_H
#include <cstdint>
#include <string>
#include <vector>
#include <cstdint>
#include "signal_info.h"
@@ -22,27 +22,19 @@
* @brief Describes one CAN frame from a DBC file.
*/
struct FrameInfo {
std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN identifier. */
bool isExtended; /**< true for extended frame. */
std::uint32_t pgn; /**< J1939 PGN if applicable. */
bool hasPgn; /**< true if PGN was derived. */
std::uint8_t dlc; /**< Frame payload length. */
std::string transmitter; /**< Transmitter ECU name. */
std::string comment; /**< Optional frame comment. */
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN identifier. */
bool isExtended; /**< true for extended frame. */
std::uint32_t pgn; /**< J1939 PGN if applicable. */
bool hasPgn; /**< true if PGN was derived. */
std::uint8_t dlc; /**< Frame payload length. */
std::string transmitter; /**< Transmitter ECU name. */
std::string comment; /**< Optional frame comment. */
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
FrameInfo()
: name()
, canId (0U)
, isExtended (false)
, pgn (0U)
, hasPgn (false)
, dlc (0U)
, transmitter()
, comment()
, signals() {
}
FrameInfo()
: name(), canId(0U), isExtended(false), pgn(0U), hasPgn(false), dlc(0U), transmitter(), comment(), signals() {
}
};
#endif /* FRAME_INFO_H */
+40
View File
@@ -0,0 +1,40 @@
# Validate `SG_` signal definitions and report malformed or unsupported syntax
## Description
Following strict validation of `BO_` frame definitions, add equivalent validation for `SG_` signal definitions. Currently, some malformed statements are accepted silently, and unsupported syntax can produce incorrect decoding.
Examples of current behavior:
- `@2+` is interpreted as Motorola instead of being rejected.
- Numeric fields containing suffixes, such as `0junk` or `1junk`, are partially parsed and accepted.
- `SG_ Value m1 : ...` is treated as an ordinary signal named `Value m1`, without multiplexing conditions.
- A statement containing a tab after `SG_` is silently skipped.
## Scope
- Validate statement structure and require a nonempty signal name without whitespace.
- Accept spaces and tabs between syntax elements.
- Parse numeric fields completely, rejecting overflow and trailing characters; preserve support for fractional values and scientific notation in physical parameters.
- Accept only byte order `@0` or `@1` and signedness `+` or `-`.
- Require signal lengths within `1..64` bits.
- Validate that the signal fits within the frame's declared payload length, using the appropriate Intel or Motorola bit traversal.
- Reject NaN and infinite values for factor, offset, minimum, and maximum; require `minimum <= maximum`.
- Explicitly reject multiplexing markers `M`, `mN`, and their combinations until multiplexing is implemented.
- Preserve the error format `path:line: description`.
## Acceptance criteria
- [ ] Existing valid signal definitions continue to parse successfully.
- [ ] Malformed and unsupported definitions stop parsing with a clear error.
- [ ] Regression tests cover the cases above, numeric boundaries, and Intel/Motorola payload boundaries.
- [ ] Existing parser and decoder tests pass.
- [ ] README documents the new validation rules and limitations.
## Out of scope
Duplicate detection, multiplexing support, and `VAL_` support remain separate tasks.
## Suggested branch
`codex/validate-sg-definitions`
+93 -93
View File
@@ -9,129 +9,129 @@
* See LICENSE file in the project root for full license text.
*/
#include <iostream>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <memory>
#include <vector>
#include <cstddef>
#include "dbc_parser.h"
#include "dbc_tree_builder.h"
#include "dbc_decode_builder.h"
#include "dbc_decoder.h"
#include "dbc_parser.h"
#include "dbc_tree_builder.h"
static void PrintTree (const TreeNode *node, int indent) {
if (node == nullptr)
return;
static void PrintTree(const TreeNode *node, int indent) {
if (node == nullptr)
return;
for (int i = 0; i < indent; ++i)
std::cout << " ";
for (int i = 0; i < indent; ++i)
std::cout << " ";
switch (node->GetType()) {
case NodeType::Root:
std::cout << "[root] " << node->GetName() << "\n";
break;
switch (node->GetType()) {
case NodeType::Root:
std::cout << "[root] " << node->GetName() << "\n";
break;
case NodeType::Frame: {
const FrameInfo *frame = node->GetFrame();
std::cout << "[frame] " << node->GetName();
case NodeType::Frame: {
const FrameInfo *frame = node->GetFrame();
std::cout << "[frame] " << node->GetName();
if (frame != nullptr) {
std::cout << " id=0x" << std::hex << frame->canId << std::dec
<< " ext=" << (frame->isExtended ? "yes" : "no")
<< " dlc=" << static_cast<unsigned int> (frame->dlc);
if (frame != nullptr) {
std::cout << " id=0x" << std::hex << frame->canId << std::dec
<< " ext=" << (frame->isExtended ? "yes" : "no")
<< " dlc=" << static_cast<unsigned int>(frame->dlc);
if (frame->hasPgn)
std::cout << " pgn=" << frame->pgn;
}
if (frame->hasPgn)
std::cout << " pgn=" << frame->pgn;
}
std::cout << "\n";
break;
std::cout << "\n";
break;
}
case NodeType::Signal: {
const SignalInfo *signal = node->GetSignal();
std::cout << "[signal] " << node->GetName();
if (signal != nullptr) {
std::cout << " start=" << signal->startBit
<< " len=" << signal->length
<< " unit=" << signal->unit;
}
std::cout << "\n";
break;
}
default:
std::cout << "[unknown]\n";
break;
}
case NodeType::Signal: {
const SignalInfo *signal = node->GetSignal();
std::cout << "[signal] " << node->GetName();
for (std::size_t i = 0U; i < node->GetChildCount(); ++i)
PrintTree(node->GetChild(i), indent + 1);
}
if (signal != nullptr) {
std::cout << " start=" << signal->startBit
<< " len=" << signal->length
<< " unit=" << signal->unit;
}
std::cout << "\n";
break;
static void PrintDecodedFrame(const DecodedFrameValue &decoded) {
if (!decoded.valid || (decoded.definition == nullptr)) {
std::cout << "No frame definition found.\n";
return;
}
default:
std::cout << "[unknown]\n";
break;
}
std::cout << "Decoded frame: " << decoded.definition->name << "\n";
for (std::size_t i = 0U; i < node->GetChildCount(); ++i)
PrintTree (node->GetChild (i), indent + 1);
for (std::size_t index = 0U; index < decoded.signals.size(); ++index) {
const DecodedSignalValue &signal = decoded.signals[index];
if ((signal.definition == nullptr) || !signal.valid)
continue;
std::cout << " " << signal.definition->name
<< " raw=";
std::visit([](auto rawValue) { std::cout << rawValue; }, signal.rawValue);
std::cout << " physical=" << signal.physicalValue
<< " " << signal.definition->unit
<< "\n";
}
}
static void PrintDecodedFrame (const DecodedFrameValue &decoded) {
if (!decoded.valid || (decoded.definition == nullptr)) {
std::cout << "No frame definition found.\n";
return;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
std::cerr << "Usage: dbc_demo <file.dbc>\n";
return 1;
}
std::cout << "Decoded frame: " << decoded.definition->name << "\n";
try {
DbcParser parser;
DbcDatabase database = parser.ParseFile(argv[1]);
for (std::size_t index = 0U; index < decoded.signals.size(); ++index) {
const DecodedSignalValue &signal = decoded.signals[index];
DbcTreeBuilder treeBuilder;
std::unique_ptr<TreeNode> root = treeBuilder.Build(database);
if ((signal.definition == nullptr) || !signal.valid)
continue;
std::cout << "=== Parsed tree ===\n";
PrintTree(root.get(), 0);
std::cout << " " << signal.definition->name
<< " raw=";
std::visit ([] (auto rawValue) { std::cout << rawValue; }, signal.rawValue);
std::cout << " physical=" << signal.physicalValue
<< " " << signal.definition->unit
<< "\n";
}
}
DbcDecodeBuilder decodeBuilder;
DecodeDatabase decodeDatabase = decodeBuilder.Build(database);
int main (int argc, char *argv[]) {
if (argc < 2) {
std::cerr << "Usage: dbc_demo <file.dbc>\n";
return 1;
}
try {
DbcParser parser;
DbcDatabase database = parser.ParseFile (argv[1]);
DbcTreeBuilder treeBuilder;
std::unique_ptr<TreeNode> root = treeBuilder.Build (database);
std::cout << "=== Parsed tree ===\n";
PrintTree (root.get(), 0);
DbcDecodeBuilder decodeBuilder;
DecodeDatabase decodeDatabase = decodeBuilder.Build (database);
/*
/*
* Example raw frame.
* Replace with live CAN frame or trace record later.
*/
RawCanFrame rawFrame;
rawFrame.canId = decodeDatabase.frames.empty() ? 0U : decodeDatabase.frames[0].canId;
rawFrame.isExtended = decodeDatabase.frames.empty() ? false : decodeDatabase.frames[0].isExtended;
rawFrame.data.resize (8U, 0U);
RawCanFrame rawFrame;
rawFrame.canId = decodeDatabase.frames.empty() ? 0U : decodeDatabase.frames[0].canId;
rawFrame.isExtended = decodeDatabase.frames.empty() ? false : decodeDatabase.frames[0].isExtended;
rawFrame.data.resize(8U, 0U);
DbcDecoder decoder;
DecodedFrameValue decoded = decoder.Decode (decodeDatabase, rawFrame);
DbcDecoder decoder;
DecodedFrameValue decoded = decoder.Decode(decodeDatabase, rawFrame);
std::cout << "\n=== Decoded frame ===\n";
PrintDecodedFrame (decoded);
} catch (const std::exception &ex) {
std::cerr << "Error: " << ex.what() << "\n";
return 2;
}
std::cout << "\n=== Decoded frame ===\n";
PrintDecodedFrame(decoded);
} catch (const std::exception &ex) {
std::cerr << "Error: " << ex.what() << "\n";
return 2;
}
return 0;
return 0;
}
+16 -27
View File
@@ -12,41 +12,30 @@
#ifndef SIGNAL_INFO_H
#define SIGNAL_INFO_H
#include <cstdint>
#include <string>
#include <vector>
#include <cstdint>
/**
* @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. */
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() {
}
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() {
}
};
#endif /* SIGNAL_INFO_H */
+3
View File
@@ -43,6 +43,9 @@ following the decoder instructions above, then run `./dbc_parser_test`.
Parser tests cover standard and extended CAN IDs, whitespace, payload storage
boundaries, missing fields, malformed and overflowing numbers, reserved ID bits,
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.
Temporary DBC fixtures are created in the system temporary directory and removed
automatically.
+123 -138
View File
@@ -5,150 +5,135 @@
#include <sstream>
struct DecodeCase {
const char *name;
ByteOrder byteOrder;
std::uint32_t startBit;
std::uint32_t length;
ValueType valueType;
std::vector<std::uint8_t> data;
bool valid;
std::int64_t rawValue;
double factor;
double offset;
double physicalValue;
const char *name;
ByteOrder byteOrder;
std::uint32_t startBit;
std::uint32_t length;
ValueType valueType;
std::vector<std::uint8_t> data;
bool valid;
std::int64_t rawValue;
double factor;
double offset;
double physicalValue;
};
int main() {
const DecodeCase cases[] = {
{"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned,
{0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned,
{0xAC}, true, 11, 1.0, 0.0, 11.0},
{"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned,
{0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0},
{"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned,
{0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
{"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned,
{0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed,
{0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0},
{"Motorola 64 bits", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned,
{0, 0, 0, 0, 0, 0, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola 64 bits high bit", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned,
{0x40, 0, 0, 0, 0, 0, 0, 0}, true, 0x4000000000000000LL,
1.0, 0.0, 4611686018427387904.0},
{"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned,
{0x12}, false, 0, 1.0, 0.0, 0.0},
{"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned,
{}, false, 0, 1.0, 0.0, 0.0},
{"Motorola large start", ByteOrder::Motorola,
std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned,
{0xFF}, false, 0, 1.0, 0.0, 0.0},
{"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned,
{0xFF}, false, 0, 1.0, 0.0, 0.0},
{"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
std::vector<std::uint8_t> (9U, 0xFF), false, 0, 1.0, 0.0, 0.0},
{"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned,
{0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
{"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed,
{0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0},
{"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned,
{0x12}, false, 0, 1.0, 0.0, 0.0}
};
const DecodeCase cases[] = {
{"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC}, true, 11, 1.0, 0.0, 11.0},
{"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0},
{"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
{"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0},
{"Motorola 64 bits", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned, {0, 0, 0, 0, 0, 0, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola 64 bits high bit", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned, {0x40, 0, 0, 0, 0, 0, 0, 0}, true, 0x4000000000000000LL, 1.0, 0.0, 4611686018427387904.0},
{"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0},
{"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0},
{"Motorola large start", ByteOrder::Motorola, std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
{"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
{"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
std::vector<std::uint8_t>(9U, 0xFF), false, 0, 1.0, 0.0, 0.0},
{"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
{"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0},
{"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}};
int failures = 0;
for (const DecodeCase &test : cases) {
DecodeSignal signal;
signal.name = test.name;
signal.byteOrder = test.byteOrder;
signal.startBit = test.startBit;
signal.length = test.length;
signal.valueType = test.valueType;
signal.factor = test.factor;
signal.offset = test.offset;
DecodeFrame definition;
definition.canId = 0x123U;
definition.dlc = static_cast<std::uint8_t> (test.data.size());
definition.signals.push_back (signal);
DecodeDatabase database;
database.frames.push_back (definition);
database.frameIndexByKey.emplace (FrameKey (definition.canId, false), 0U);
RawCanFrame frame;
frame.canId = definition.canId;
frame.data = test.data;
const DecodedFrameValue result = DbcDecoder().Decode (database, frame);
const bool passed = result.valid &&
(result.definition == &database.frames[0]) &&
(result.signals.size() == 1U) &&
(result.signals[0].definition == &database.frames[0].signals[0]) &&
(result.signals[0].valid == test.valid) &&
(!test.valid || (((test.valueType == ValueType::Signed)
? (std::get_if<std::int64_t> (&result.signals[0].rawValue) &&
std::get<std::int64_t> (result.signals[0].rawValue) == test.rawValue)
: (std::get_if<std::uint64_t> (&result.signals[0].rawValue) &&
std::get<std::uint64_t> (result.signals[0].rawValue) ==
static_cast<std::uint64_t> (test.rawValue))) &&
(result.signals[0].physicalValue == test.physicalValue)));
if (!passed) {
std::cerr << "FAIL: " << test.name << '\n';
++failures;
}
}
std::size_t boundaryCases = 0U;
for (ByteOrder order : {ByteOrder::Intel, ByteOrder::Motorola}) {
for (ValueType type : {ValueType::Unsigned, ValueType::Signed}) {
for (std::uint64_t bits : {std::uint64_t (0U), std::uint64_t (0x7FFFFFFFFFFFFFFFULL),
std::uint64_t (0x8000000000000000ULL),
std::uint64_t (0x8000000000000001ULL),
std::uint64_t (0xFFFFFFFFFFFFFFFFULL)}) {
DecodeDatabase database;
DecodeFrame definition;
int failures = 0;
for (const DecodeCase &test: cases) {
DecodeSignal signal;
signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
signal.length = 64U;
signal.byteOrder = order;
signal.valueType = type;
signal.factor = 0.5;
signal.offset = 10.0;
definition.signals.push_back (signal);
database.frames.push_back (definition);
database.frameIndexByKey.emplace (FrameKey (0U, false), 0U);
signal.name = test.name;
signal.byteOrder = test.byteOrder;
signal.startBit = test.startBit;
signal.length = test.length;
signal.valueType = test.valueType;
signal.factor = test.factor;
signal.offset = test.offset;
DecodeFrame definition;
definition.canId = 0x123U;
definition.dlc = static_cast<std::uint8_t>(test.data.size());
definition.signals.push_back(signal);
DecodeDatabase database;
database.frames.push_back(definition);
database.frameIndexByKey.emplace(FrameKey(definition.canId, false), 0U);
RawCanFrame frame;
for (unsigned byte = 0U; byte < 8U; ++byte) {
const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
frame.data.push_back (static_cast<std::uint8_t> (bits >> shift));
frame.canId = definition.canId;
frame.data = test.data;
const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
const bool passed = result.valid &&
(result.definition == &database.frames[0]) &&
(result.signals.size() == 1U) &&
(result.signals[0].definition == &database.frames[0].signals[0]) &&
(result.signals[0].valid == test.valid) &&
(!test.valid || (((test.valueType == ValueType::Signed)
? (std::get_if<std::int64_t>(&result.signals[0].rawValue) &&
std::get<std::int64_t>(result.signals[0].rawValue) == test.rawValue)
: (std::get_if<std::uint64_t>(&result.signals[0].rawValue) &&
std::get<std::uint64_t>(result.signals[0].rawValue) ==
static_cast<std::uint64_t>(test.rawValue))) &&
(result.signals[0].physicalValue == test.physicalValue)));
if (!passed) {
std::cerr << "FAIL: " << test.name << '\n';
++failures;
}
const auto result = DbcDecoder().Decode (database, frame);
const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
? static_cast<std::int64_t> (bits)
: std::numeric_limits<std::int64_t>::min() +
static_cast<std::int64_t> (bits - 0x8000000000000000ULL);
const auto &decoded = result.signals.at (0U);
const auto expected = type == ValueType::Unsigned
? std::variant<std::int64_t, std::uint64_t> (bits)
: std::variant<std::int64_t, std::uint64_t> (signedExpected);
const double physical = (type == ValueType::Unsigned
? static_cast<double> (bits) : static_cast<double> (signedExpected)) * 0.5 + 10.0;
std::ostringstream actualText;
std::ostringstream expectedText;
std::visit ([&actualText] (auto raw) { actualText << raw; }, decoded.rawValue);
if (type == ValueType::Unsigned)
expectedText << bits;
else
expectedText << signedExpected;
if (!decoded.valid || decoded.rawValue != expected ||
decoded.physicalValue != physical || actualText.str() != expectedText.str()) {
std::cerr << "FAIL: 64-bit boundary " << bits << '\n';
++failures;
}
++boundaryCases;
}
}
}
if (failures != 0)
return 1;
std::cout << "All " << sizeof (cases) / sizeof (cases[0]) + boundaryCases << " decoder cases passed.\n";
return 0;
std::size_t boundaryCases = 0U;
for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
for (ValueType type: {ValueType::Unsigned, ValueType::Signed}) {
for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL),
std::uint64_t(0x8000000000000000ULL),
std::uint64_t(0x8000000000000001ULL),
std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) {
DecodeDatabase database;
DecodeFrame definition;
DecodeSignal signal;
signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
signal.length = 64U;
signal.byteOrder = order;
signal.valueType = type;
signal.factor = 0.5;
signal.offset = 10.0;
definition.signals.push_back(signal);
database.frames.push_back(definition);
database.frameIndexByKey.emplace(FrameKey(0U, false), 0U);
RawCanFrame frame;
for (unsigned byte = 0U; byte < 8U; ++byte) {
const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
frame.data.push_back(static_cast<std::uint8_t>(bits >> shift));
}
const auto result = DbcDecoder().Decode(database, frame);
const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
? static_cast<std::int64_t>(bits)
: std::numeric_limits<std::int64_t>::min() +
static_cast<std::int64_t>(bits - 0x8000000000000000ULL);
const auto &decoded = result.signals.at(0U);
const auto expected = type == ValueType::Unsigned
? std::variant<std::int64_t, std::uint64_t>(bits)
: std::variant<std::int64_t, std::uint64_t>(signedExpected);
const double physical = (type == ValueType::Unsigned
? static_cast<double>(bits)
: static_cast<double>(signedExpected)) *
0.5 +
10.0;
std::ostringstream actualText;
std::ostringstream expectedText;
std::visit([&actualText](auto raw) { actualText << raw; }, decoded.rawValue);
if (type == ValueType::Unsigned)
expectedText << bits;
else
expectedText << signedExpected;
if (!decoded.valid || decoded.rawValue != expected ||
decoded.physicalValue != physical || actualText.str() != expectedText.str()) {
std::cerr << "FAIL: 64-bit boundary " << bits << '\n';
++failures;
}
++boundaryCases;
}
}
}
if (failures != 0)
return 1;
std::cout << "All " << sizeof(cases) / sizeof(cases[0]) + boundaryCases << " decoder cases passed.\n";
return 0;
}
+231 -111
View File
@@ -8,126 +8,246 @@
#include <stdexcept>
struct Fixture {
std::filesystem::path path = std::filesystem::temp_directory_path() /
("dbc_parser_test_" + std::to_string (
std::chrono::steady_clock::now().time_since_epoch().count()) + ".dbc");
~Fixture() { std::error_code error; std::filesystem::remove (path, error); }
void Write (const std::string &text) const {
std::ofstream output (path);
output << text;
if (!output)
throw std::runtime_error ("Cannot write test fixture.");
}
std::filesystem::path path = std::filesystem::temp_directory_path() /
("dbc_parser_test_" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".dbc");
~Fixture() {
std::error_code error;
std::filesystem::remove(path, error);
}
void Write(const std::string &text) const {
std::ofstream output(path);
output << text;
if (!output)
throw std::runtime_error("Cannot write test fixture.");
}
};
class CountingHandler : public DbcStatementHandler {
public:
public:
inline static int instances = 0;
inline static int calls = 0;
CountingHandler() { ++instances; }
void Handle (const std::string &, DbcParseContext &context) const override {
++calls;
context.database.frames.emplace_back();
void Handle(const std::string &, DbcParseContext &context) const override {
++calls;
context.database.frames.emplace_back();
}
};
int main() {
Fixture fixture;
int failures = 0;
std::size_t count = 0U;
const auto check = [&] (bool passed, const std::string &name) {
++count;
if (!passed) { ++failures; std::cerr << "FAIL: " << name << '\n'; }
};
struct ValidCase { const char *line; std::uint32_t id; bool extended; unsigned length; };
const ValidCase valid[] = {
{"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
{"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
{"BO_ 2048 Frame: 64 ECU", 2048U, true, 64U},
{"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
{"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
{"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
{" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}
};
for (const auto &test : valid) {
fixture.Write (std::string (test.line) + '\n');
try {
const auto database = DbcParser().ParseFile (fixture.path.string());
check (database.frames.size() == 1U && database.frames[0].name == "Frame" &&
database.frames[0].canId == test.id &&
database.frames[0].isExtended == test.extended &&
database.frames[0].dlc == test.length &&
database.frames[0].transmitter == "ECU", test.line);
} catch (const std::exception &error) { check (false, error.what()); }
}
const char *invalid[] = {
"BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
"BO_ 1 Frame:", "BO_ 1 Frame: 8", "BO_ 1 Frame: 8 ECU extra",
"BO_ 1 Two Names: 8 ECU", "BO_ -1 Frame: 8 ECU", "BO_ +1 Frame: 8 ECU",
"BO_ 1x Frame: 8 ECU", "BO_ 0x123 Frame: 8 ECU", "BO_ 1.5 Frame: 8 ECU",
"BO_ 4294967296 Frame: 8 ECU", "BO_ 999999999999999999999 Frame: 8 ECU",
"BO_ 536870912 Frame: 8 ECU", "BO_ 1073741824 Frame: 8 ECU",
"BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 Frame: 8 ECU",
"BO_ 1 Frame: -1 ECU", "BO_ 1 Frame: +8 ECU", "BO_ 1 Frame: 8x ECU",
"BO_ 1 Frame: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
"BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"
};
for (const auto *line : invalid) {
fixture.Write (std::string ("VERSION \"test\"\n\n") + line + '\n');
try {
DbcParser().ParseFile (fixture.path.string());
check (false, line);
} catch (const std::runtime_error &error) {
const std::string prefix = fixture.path.string() + ":3: ";
check (std::string (error.what()).find (prefix) == 0U &&
std::string (error.what()).size() > prefix.size(), line);
Fixture fixture;
int failures = 0;
std::size_t count = 0U;
const auto check = [&](bool passed, const std::string &name) {
++count;
if (!passed) {
++failures;
std::cerr << "FAIL: " << name << '\n';
}
};
struct ValidCase {
const char *line;
std::uint32_t id;
bool extended;
unsigned length;
};
const ValidCase valid[] = {
{"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
{"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
{"BO_ 2048 Frame: 64 ECU", 2048U, true, 64U},
{"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
{"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
{"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
{" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}};
for (const auto &test: valid) {
fixture.Write(std::string(test.line) + '\n');
try {
const auto database = DbcParser().ParseFile(fixture.path.string());
check(database.frames.size() == 1U && database.frames[0].name == "Frame" &&
database.frames[0].canId == test.id &&
database.frames[0].isExtended == test.extended &&
database.frames[0].dlc == test.length &&
database.frames[0].transmitter == "ECU",
test.line);
} catch (const std::exception &error) { check(false, error.what()); }
}
}
fixture.Write (
"BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
" SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
"BO_ 257 Second: 8 ECU\n");
const auto database = DbcParser().ParseFile (fixture.path.string());
check (database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
database.frames[0].comment == "Frame comment" &&
database.frames[0].signals[0].comment == "Signal comment", "Integration");
fixture.Write ("\nSG_ Speed : invalid\n");
try {
DbcParser().ParseFile (fixture.path.string());
check (false, "Signal error location");
} catch (const std::runtime_error &error) {
check (std::string (error.what()).find (fixture.path.string() + ":2: ") == 0U,
"Signal error location");
}
DbcDatabase dispatchDatabase;
DbcParseContext context {dispatchDatabase, nullptr};
DbcStatementDispatcher dispatcher;
dispatcher.RegisterType<CountingHandler> ("TEST_");
check (dispatcher.Dispatch ("TEST_ first", context) &&
dispatcher.Dispatch ("TEST_ second", context) &&
CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
dispatchDatabase.frames.size() == 2U, "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) &&
CountingHandler::calls == 2, "Exact and unknown keyword dispatch");
const DbcParser parser;
fixture.Write ("BO_ 1 First: 8 ECU\n");
check (parser.ParseFile (fixture.path.string()).frames.size() == 1U, "First parse");
fixture.Write ("SG_ Speed : invalid\n");
try {
parser.ParseFile (fixture.path.string());
check (false, "Independent parse context");
} catch (const std::runtime_error &error) {
check (std::string (error.what()).find ("Signal found before any frame") !=
std::string::npos, "Independent parse context");
}
fixture.Write ("BO_ 2 Second: 8 ECU\n");
const auto recovered = parser.ParseFile (fixture.path.string());
check (recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
"Fresh parse after error");
if (failures != 0) return 1;
std::cout << "All " << count << " parser cases passed.\n";
return 0;
const char *invalid[] = {
"BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
"BO_ 1 Frame:", "BO_ 1 Frame: 8", "BO_ 1 Frame: 8 ECU extra",
"BO_ 1 Two Names: 8 ECU", "BO_ -1 Frame: 8 ECU", "BO_ +1 Frame: 8 ECU",
"BO_ 1x Frame: 8 ECU", "BO_ 0x123 Frame: 8 ECU", "BO_ 1.5 Frame: 8 ECU",
"BO_ 4294967296 Frame: 8 ECU", "BO_ 999999999999999999999 Frame: 8 ECU",
"BO_ 536870912 Frame: 8 ECU", "BO_ 1073741824 Frame: 8 ECU",
"BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 Frame: 8 ECU",
"BO_ 1 Frame: -1 ECU", "BO_ 1 Frame: +8 ECU", "BO_ 1 Frame: 8x ECU",
"BO_ 1 Frame: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
"BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"};
for (const auto *line: invalid) {
fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n');
try {
DbcParser().ParseFile(fixture.path.string());
check(false, line);
} catch (const std::runtime_error &error) {
const std::string prefix = fixture.path.string() + ":3: ";
check(std::string(error.what()).find(prefix) == 0U &&
std::string(error.what()).size() > prefix.size(),
line);
}
}
fixture.Write(
"BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
" SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
"BO_ 257 Second: 8 ECU\n");
const auto database = DbcParser().ParseFile(fixture.path.string());
check(database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
database.frames[0].comment == "Frame comment" &&
database.frames[0].signals[0].comment == "Signal comment",
"Integration");
fixture.Write("\nSG_ Speed : invalid\n");
try {
DbcParser().ParseFile(fixture.path.string());
check(false, "Signal error location");
} catch (const std::runtime_error &error) {
check(std::string(error.what()).find(fixture.path.string() + ":2: ") == 0U,
"Signal error location");
}
const DbcParser signalParser;
const auto signalLine = [](const std::string &start, const std::string &length,
const std::string &order, const std::string &factor,
const std::string &offset, const std::string &minimum,
const std::string &maximum) {
return "SG_ Value : " + start + "|" + length + "@" + order +
" (" + factor + "," + offset + ") [" + minimum + "|" + maximum + "] \"\" ECU";
};
const auto rejectSignal = [&](const std::string &line, unsigned payload = 8U) {
fixture.Write("BO_ 1 Frame: " + std::to_string(payload) + " ECU\n\n" + line + "\n");
try {
signalParser.ParseFile(fixture.path.string());
check(false, "Reject " + line);
} catch (const std::runtime_error &error) {
const std::string prefix = fixture.path.string() + ":3: ";
check(std::string(error.what()).find(prefix) == 0U &&
std::string(error.what()).size() > prefix.size(),
"Reject " + line);
}
};
const std::string ordinary = signalLine("0", "8", "1+", "1", "0", "0", "255");
for (const std::string value: {"-1", "+1", "0junk", "0x10", "1.5",
"4294967296", "999999999999999999999"}) {
rejectSignal(signalLine(value, "8", "1+", "1", "0", "0", "255"));
rejectSignal(signalLine("0", value, "1+", "1", "0", "0", "255"));
}
for (const std::string length: {"0", "65"})
rejectSignal(signalLine("0", length, "1+", "1", "0", "0", "255"));
for (const std::string order: {"2+", "9-", "1", "1x", "1++", "01+"})
rejectSignal(signalLine("0", "8", order, "1", "0", "0", "255"));
for (const std::string value: {"1junk", "nan", "NaN", "inf", "-inf", "1e999", "1e-999", "0x1p2", "", "1e", "1.2.3"}) {
rejectSignal(signalLine("0", "8", "1+", value, "0", "0", "255"));
rejectSignal(signalLine("0", "8", "1+", "1", value, "0", "255"));
rejectSignal(signalLine("0", "8", "1+", "1", "0", value, "255"));
rejectSignal(signalLine("0", "8", "1+", "1", "0", "0", value));
}
rejectSignal(signalLine("0", "8", "1+", "1", "0", "2", "1"));
for (const std::string name: {"", "Two Names", "Value M", "Value m1", "Value m1M"}) {
std::string line = ordinary;
line.replace(4U, 5U, name);
rejectSignal(line);
}
for (const std::string broken: {
"SG_", "SG_ Value", "SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
rejectSignal(broken);
rejectSignal(signalLine("64", "1", "1+", "1", "0", "0", "1"));
rejectSignal(signalLine("63", "2", "1+", "1", "0", "0", "3"));
rejectSignal(signalLine("56", "2", "0+", "1", "0", "0", "3"));
rejectSignal(signalLine("4294967295", "64", "1+", "1", "0", "0", "1"));
rejectSignal(ordinary, 0U);
for (const char delimiter: {'|', '@', '(', ')', '[', ']', '"'}) {
std::string line = ordinary;
line.erase(line.find(delimiter), 1U);
rejectSignal(line);
}
fixture.Write("BO_ 1 Frame: 8 ECU\n" +
signalLine("0", "8", "1+", "0", "0", "0", "0") + "\n" +
signalLine("8", "8", "1-", "-1", "0", "-255", "0") + "\n");
const auto factors = signalParser.ParseFile(fixture.path.string());
check(factors.frames[0].signals.size() == 2U &&
factors.frames[0].signals[0].factor == 0.0 &&
factors.frames[0].signals[1].factor == -1.0,
"Zero and negative factors and equal range boundaries");
struct SignalCase {
const char *start;
const char *length;
const char *order;
unsigned payload;
};
const SignalCase validSignals[] = {
{"0", "1", "1+", 1U},
{"7", "1", "0-", 1U},
{"0", "64", "1+", 8U},
{"7", "64", "0-", 8U},
{"63", "1", "1+", 8U},
{"56", "1", "0+", 8U},
{"3", "12", "0+", 2U},
{"2039", "1", "1+", 255U}};
for (const auto &test: validSignals) {
fixture.Write("BO_ 1 Frame: " + std::to_string(test.payload) + " ECU\n" +
signalLine(test.start, test.length, test.order, ".5e+1", "-2.5E-1", "-1.", "+2e2") + "\n");
try {
const auto parsed = signalParser.ParseFile(fixture.path.string());
const auto &signal = parsed.frames.at(0).signals.at(0);
check(signal.startBit == std::stoul(test.start) &&
signal.length == std::stoul(test.length) &&
signal.isLittleEndian == (test.order[0] == '1') &&
signal.isSigned == (test.order[1] == '-') &&
signal.factor == 5.0 && signal.offset == -0.25 &&
signal.minimum == -1.0 && signal.maximum == 200.0,
"Valid signal boundary and numeric metadata");
} catch (const std::exception &error) { check(false, error.what()); }
}
fixture.Write("BO_ 1 Frame: 8 ECU\n"
"SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
try {
const auto parsed = signalParser.ParseFile(fixture.path.string());
const auto &signal = parsed.frames.at(0).signals.at(0);
check(signal.name == "Value" && signal.unit == "rpm" &&
signal.receivers == std::vector<std::string>({"ECU1", "ECU2"}),
"Signal tabs, spaces, and receiver list");
} catch (const std::exception &error) { check(false, error.what()); }
DbcDatabase dispatchDatabase;
DbcParseContext context{dispatchDatabase, nullptr};
DbcStatementDispatcher dispatcher;
dispatcher.RegisterType<CountingHandler>("TEST_");
check(dispatcher.Dispatch("TEST_ first", context) &&
dispatcher.Dispatch("TEST_ second", context) &&
CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
dispatchDatabase.frames.size() == 2U,
"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) &&
CountingHandler::calls == 2,
"Exact and unknown keyword dispatch");
const DbcParser parser;
fixture.Write("BO_ 1 First: 8 ECU\n");
check(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse");
fixture.Write("SG_ Speed : invalid\n");
try {
parser.ParseFile(fixture.path.string());
check(false, "Independent parse context");
} catch (const std::runtime_error &error) {
check(std::string(error.what()).find("Signal found before any frame") !=
std::string::npos,
"Independent parse context");
}
fixture.Write("BO_ 2 Second: 8 ECU\n");
const auto recovered = parser.ParseFile(fixture.path.string());
check(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
"Fresh parse after error");
if (failures != 0) return 1;
std::cout << "All " << count << " parser cases passed.\n";
return 0;
}
+21 -33
View File
@@ -12,64 +12,52 @@
#include "tree_node.h"
TreeNode::TreeNode()
: m_type (NodeType::Root)
, m_name ("dbc")
, m_children()
, m_frame()
, m_signal() {
: m_type(NodeType::Root), m_name("dbc"), m_children(), m_frame(), m_signal() {
}
TreeNode::TreeNode (const FrameInfo &frame)
: m_type (NodeType::Frame)
, m_name (frame.name)
, m_children()
, m_frame (new FrameInfo (frame))
, m_signal() {
TreeNode::TreeNode(const FrameInfo &frame)
: m_type(NodeType::Frame), m_name(frame.name), m_children(), m_frame(new FrameInfo(frame)), m_signal() {
}
TreeNode::TreeNode (const SignalInfo &signal)
: m_type (NodeType::Signal)
, m_name (signal.name)
, m_children()
, m_frame()
, m_signal (new SignalInfo (signal)) {
TreeNode::TreeNode(const SignalInfo &signal)
: m_type(NodeType::Signal), m_name(signal.name), m_children(), m_frame(), m_signal(new SignalInfo(signal)) {
}
void TreeNode::AddChild (std::unique_ptr<TreeNode> child) {
if (child)
m_children.push_back (std::move (child));
void TreeNode::AddChild(std::unique_ptr<TreeNode> child) {
if (child)
m_children.push_back(std::move(child));
}
std::size_t TreeNode::GetChildCount() const {
return m_children.size();
return m_children.size();
}
const TreeNode *TreeNode::GetChild (std::size_t index) const {
if (index >= m_children.size())
return nullptr;
const TreeNode *TreeNode::GetChild(std::size_t index) const {
if (index >= m_children.size())
return nullptr;
return m_children[index].get();
return m_children[index].get();
}
TreeNode *TreeNode::GetChild (std::size_t index) {
if (index >= m_children.size())
return nullptr;
TreeNode *TreeNode::GetChild(std::size_t index) {
if (index >= m_children.size())
return nullptr;
return m_children[index].get();
return m_children[index].get();
}
NodeType TreeNode::GetType() const {
return m_type;
return m_type;
}
const std::string &TreeNode::GetName() const {
return m_name;
return m_name;
}
const FrameInfo *TreeNode::GetFrame() const {
return m_frame.get();
return m_frame.get();
}
const SignalInfo *TreeNode::GetSignal() const {
return m_signal.get();
return m_signal.get();
}
+17 -17
View File
@@ -12,10 +12,10 @@
#ifndef TREE_NODE_H
#define TREE_NODE_H
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include <memory>
#include <cstddef>
#include "frame_info.h"
#include "signal_info.h"
@@ -24,16 +24,16 @@
* @brief Type of a tree node.
*/
enum class NodeType {
Root,
Frame,
Signal
Root,
Frame,
Signal
};
/**
* @brief Tree node for later use in model/view or other hierarchy consumers.
*/
class TreeNode {
public:
public:
/**
* @brief Create root node.
*/
@@ -43,27 +43,27 @@ class TreeNode {
* @brief Create frame node.
* @param frame Frame payload.
*/
explicit TreeNode (const FrameInfo &frame);
explicit TreeNode(const FrameInfo &frame);
/**
* @brief Create signal node.
* @param signal Signal payload.
*/
explicit TreeNode (const SignalInfo &signal);
explicit TreeNode(const SignalInfo &signal);
~TreeNode() = default;
TreeNode (const TreeNode &) = delete;
TreeNode &operator= (const TreeNode &) = delete;
TreeNode(const TreeNode &) = delete;
TreeNode &operator=(const TreeNode &) = delete;
TreeNode (TreeNode &&) = default;
TreeNode &operator= (TreeNode &&) = default;
TreeNode(TreeNode &&) = default;
TreeNode &operator=(TreeNode &&) = default;
/**
* @brief Add child node.
* @param child Child node to add.
*/
void AddChild (std::unique_ptr<TreeNode> child);
void AddChild(std::unique_ptr<TreeNode> child);
/**
* @brief Get child count.
@@ -76,14 +76,14 @@ class TreeNode {
* @param index Child index.
* @return Child pointer or nullptr if index is invalid.
*/
const TreeNode *GetChild (std::size_t index) const;
const TreeNode *GetChild(std::size_t index) const;
/**
* @brief Get mutable child by index.
* @param index Child index.
* @return Child pointer or nullptr if index is invalid.
*/
TreeNode *GetChild (std::size_t index);
TreeNode *GetChild(std::size_t index);
/**
* @brief Get node type.
@@ -109,10 +109,10 @@ class TreeNode {
*/
const SignalInfo *GetSignal() const;
private:
private:
NodeType m_type;
std::string m_name;
std::vector<std::unique_ptr<TreeNode> > m_children;
std::vector<std::unique_ptr<TreeNode>> m_children;
std::unique_ptr<FrameInfo> m_frame;
std::unique_ptr<SignalInfo> m_signal;
};
+33 -33
View File
@@ -7,7 +7,7 @@
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <utility> // std::move
#include <utility>// std::move
/**
* @brief Registry-based factory for creating objects by runtime identifier.
@@ -55,15 +55,15 @@
* (void)b->get();
* @endcode
*/
template <class ClassId, class BaseClass, class... Args>
template<class ClassId, class BaseClass, class... Args>
class TypeFactory {
public:
using BasePtr = std::shared_ptr<BaseClass>;
using CreatorFn = BasePtr (*)(Args...);
using BasePtr = std::shared_ptr<BaseClass>;
using CreatorFn = BasePtr (*)(Args...);
TypeFactory() = default;
TypeFactory() = default;
/**
/**
* @brief Registers a Derived type under the given id.
*
* Requirements (checked at compile-time):
@@ -72,47 +72,47 @@ public:
*
* Re-registering the same id overwrites the previous entry ("last wins").
*/
template <class Derived>
void registerType(const ClassId& id) {
static_assert(std::is_base_of_v<BaseClass, Derived>,
"Derived must inherit from BaseClass");
static_assert(std::is_constructible_v<Derived, Args...>,
"Derived must be constructible from Args...");
template<class Derived>
void registerType(const ClassId &id) {
static_assert(std::is_base_of_v<BaseClass, Derived>,
"Derived must inherit from BaseClass");
static_assert(std::is_constructible_v<Derived, Args...>,
"Derived must be constructible from Args...");
classes.insert_or_assign(id, &instantiate<Derived>);
}
classes.insert_or_assign(id, &instantiate<Derived>);
}
/**
/**
* @brief Returns creator function for the given id.
* @throws std::out_of_range when id is not registered.
*/
[[nodiscard]] CreatorFn creator(const ClassId& id) const {
return classes.at(id);
}
[[nodiscard]] CreatorFn creator(const ClassId &id) const {
return classes.at(id);
}
/**
/**
* @brief Creates an instance for the given id using provided arguments.
* @throws std::out_of_range when id is not registered.
*/
[[nodiscard]] BasePtr create(const ClassId& id, Args... args) const {
return creator(id)(std::move(args)...);
}
[[nodiscard]] BasePtr create(const ClassId &id, Args... args) const {
return creator(id)(std::move(args)...);
}
/**
/**
* @brief Checks whether an id is registered.
*/
[[nodiscard]] bool contains(const ClassId& id) const {
return classes.find(id) != classes.end();
}
[[nodiscard]] bool contains(const ClassId &id) const {
return classes.find(id) != classes.end();
}
protected:
// Protected by design: derived factories may extend/inspect the registry.
std::unordered_map<ClassId, CreatorFn> classes;
// Protected by design: derived factories may extend/inspect the registry.
std::unordered_map<ClassId, CreatorFn> classes;
private:
template <class Derived>
static BasePtr instantiate(Args... args) {
return std::make_shared<Derived>(std::move(args)...);
}
template<class Derived>
static BasePtr instantiate(Args... args) {
return std::make_shared<Derived>(std::move(args)...);
}
};
#endif // TYPEFACTORY_H
#endif// TYPEFACTORY_H