Compare commits

..
1 Commits
Author SHA1 Message Date
deeaitch d7e72e1d12 Merge pull request 'Main' (#5) from main into doxygen
Reviewed-on: #5
2026-10-02 18:19:47 +00:00
23 changed files with 497 additions and 649 deletions

No files matched your search

-65
View File
@@ -1,65 +0,0 @@
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
+5 -33
View File
@@ -532,8 +532,9 @@ one-based physical line number, including blank lines:
example.dbc:3: Invalid CAN ID: invalid example.dbc:3: Invalid CAN ID: invalid
``` ```
This change validates frame definitions. Signal definitions are also validated as described below; comprehensive This change validates frame definitions. Signal syntax and unsupported DBC
DBC grammar coverage is still planned. File-open errors contain the path but have no line number. constructs retain their existing behavior; comprehensive validation is still
planned. File-open errors contain the path but have no line number.
--- ---
@@ -561,35 +562,6 @@ 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 ## Comments
Frame comment example: Frame comment example:
@@ -833,8 +805,8 @@ RegisterType<DbcValueHandler>("VAL_"); // after implementing DbcValueHandler
Registration creates one handler; replacing a registration replaces that instance. Registration creates one handler; replacing a registration replaces that instance.
Register during initialization before dispatching. A line is routed using its Register during initialization before dispatching. A line is routed using its
full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`. full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`.
Unregistered keywords remain ignored. `SG_` accepts spaces and tabs after its keyword; `CM_` retains its existing Unregistered keywords remain ignored. Existing `SG_` and `CM_` syntax policies
policy requiring a literal space. are preserved, including requiring a literal space after those keywords.
This factory handles statements inside DBC files. A separate format-level This factory handles statements inside DBC files. A separate format-level
factory can be introduced when another file format is implemented. The decoder factory can be introduced when another file format is implemented. The decoder
+4 -4
View File
@@ -11,7 +11,7 @@
#include "dbc_decode_builder.h" #include "dbc_decode_builder.h"
DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const { DecodeDatabase DbcDecodeBuilder::Build (const DbcDatabase &source) const {
DecodeDatabase result; DecodeDatabase result;
for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) { for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) {
@@ -44,11 +44,11 @@ DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
targetSignal.receivers = sourceSignal.receivers; targetSignal.receivers = sourceSignal.receivers;
targetSignal.comment = sourceSignal.comment; targetSignal.comment = sourceSignal.comment;
targetFrame.signals.push_back(targetSignal); targetFrame.signals.push_back (targetSignal);
} }
result.frames.push_back(targetFrame); result.frames.push_back (targetFrame);
result.frameIndexByKey[FrameKey(targetFrame.canId, targetFrame.isExtended)] = result.frameIndexByKey[FrameKey (targetFrame.canId, targetFrame.isExtended)] =
result.frames.size() - 1U; result.frames.size() - 1U;
} }
+2 -2
View File
@@ -19,13 +19,13 @@
* @brief Converts parsed DBC data into runtime decode database. * @brief Converts parsed DBC data into runtime decode database.
*/ */
class DbcDecodeBuilder { class DbcDecodeBuilder {
public: public:
/** /**
* @brief Build runtime decode database. * @brief Build runtime decode database.
* @param source Parsed DBC database. * @param source Parsed DBC database.
* @return Runtime-ready decode database. * @return Runtime-ready decode database.
*/ */
DecodeDatabase Build(const DbcDatabase &source) const; DecodeDatabase Build (const DbcDatabase &source) const;
}; };
#endif /* DBC_DECODE_BUILDER_H */ #endif /* DBC_DECODE_BUILDER_H */
+24 -26
View File
@@ -11,12 +11,12 @@
#include "dbc_decoder.h" #include "dbc_decoder.h"
const DecodeFrame *DbcDecoder::FindFrame(const DecodeDatabase &database, const DecodeFrame *DbcDecoder::FindFrame (const DecodeDatabase &database,
std::uint32_t canId, std::uint32_t canId,
bool isExtended) const { bool isExtended) const {
const FrameKey key(canId, isExtended); const FrameKey key (canId, isExtended);
const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it = const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
database.frameIndexByKey.find(key); database.frameIndexByKey.find (key);
if (it == database.frameIndexByKey.end()) if (it == database.frameIndexByKey.end())
return nullptr; return nullptr;
@@ -28,10 +28,10 @@ const DecodeFrame *DbcDecoder::FindFrame(const DecodeDatabase &database,
return &database.frames[index]; return &database.frames[index];
} }
DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database, DecodedFrameValue DbcDecoder::Decode (const DecodeDatabase &database,
const RawCanFrame &frame) const { const RawCanFrame &frame) const {
DecodedFrameValue result; DecodedFrameValue result;
const DecodeFrame *definition = FindFrame(database, frame.canId, frame.isExtended); const DecodeFrame *definition = FindFrame (database, frame.canId, frame.isExtended);
if (definition == nullptr) if (definition == nullptr)
return result; return result;
@@ -46,43 +46,42 @@ DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database,
decoded.definition = &signal; decoded.definition = &signal;
if (!ExtractUnsigned(frame.data, signal, unsignedValue)) { if (!ExtractUnsigned (frame.data, signal, unsignedValue)) {
decoded.valid = false; decoded.valid = false;
result.signals.push_back(decoded); result.signals.push_back (decoded);
continue; continue;
} }
if (signal.valueType == ValueType::Signed) if (signal.valueType == ValueType::Signed)
decoded.rawValue = SignExtend(unsignedValue, signal.length); decoded.rawValue = SignExtend (unsignedValue, signal.length);
else else
decoded.rawValue = unsignedValue; decoded.rawValue = unsignedValue;
decoded.physicalValue = decoded.physicalValue =
std::visit([&signal](auto rawValue) { std::visit ([&signal] (auto rawValue) {
return (static_cast<double>(rawValue) * signal.factor) + signal.offset; return (static_cast<double> (rawValue) * signal.factor) + signal.offset;
}, }, decoded.rawValue);
decoded.rawValue);
decoded.valid = true; decoded.valid = true;
result.signals.push_back(decoded); result.signals.push_back (decoded);
} }
return result; return result;
} }
bool DbcDecoder::ExtractUnsigned(const std::vector<std::uint8_t> &data, bool DbcDecoder::ExtractUnsigned (const std::vector<std::uint8_t> &data,
const DecodeSignal &signal, const DecodeSignal &signal,
std::uint64_t &value) { std::uint64_t &value) {
if ((signal.length == 0U) || (signal.length > 64U)) if ((signal.length == 0U) || (signal.length > 64U))
return false; return false;
if (signal.byteOrder == ByteOrder::Intel) if (signal.byteOrder == ByteOrder::Intel)
return ExtractIntel(data, signal.startBit, signal.length, value); 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, bool DbcDecoder::ExtractIntel (const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value) { std::uint64_t &value) {
@@ -97,7 +96,7 @@ bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
return false; return false;
const std::uint64_t bitValue = const std::uint64_t bitValue =
(static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex); (static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
value |= bitValue; value |= bitValue;
} }
@@ -105,7 +104,7 @@ bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
return true; return true;
} }
bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data, bool DbcDecoder::ExtractMotorola (const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value) { std::uint64_t &value) {
@@ -127,7 +126,7 @@ bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data,
return false; return false;
value <<= 1U; value <<= 1U;
value |= static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U); value |= static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U);
if (bitInByte == 0U) { if (bitInByte == 0U) {
++byteIndex; ++byteIndex;
@@ -139,19 +138,18 @@ bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data,
return true; return true;
} }
std::int64_t DbcDecoder::SignExtend(std::uint64_t value, std::uint32_t bitLength) { std::int64_t DbcDecoder::SignExtend (std::uint64_t value, std::uint32_t bitLength) {
if ((bitLength == 0U) || (bitLength > 64U)) if ((bitLength == 0U) || (bitLength > 64U))
return 0; return 0;
const std::uint64_t signMask = std::uint64_t(1U) << (bitLength - 1U); const std::uint64_t signMask = std::uint64_t (1U) << (bitLength - 1U);
const std::uint64_t valueMask = (bitLength == 64U) const std::uint64_t valueMask = (bitLength == 64U)
? ~std::uint64_t(0U) ? ~std::uint64_t (0U) : (std::uint64_t (1U) << bitLength) - 1U;
: (std::uint64_t(1U) << bitLength) - 1U;
value &= valueMask; value &= valueMask;
if ((value & signMask) == 0U) if ((value & signMask) == 0U)
return static_cast<std::int64_t>(value); return static_cast<std::int64_t> (value);
// The complemented magnitude fits int64_t, including for INT64_MIN. // The complemented magnitude fits int64_t, including for INT64_MIN.
return -1 - static_cast<std::int64_t>((~value) & valueMask); return -1 - static_cast<std::int64_t> ((~value) & valueMask);
} }
+19 -12
View File
@@ -12,9 +12,9 @@
#ifndef DBC_DECODER_H #ifndef DBC_DECODER_H
#define DBC_DECODER_H #define DBC_DECODER_H
#include <vector>
#include <cstdint> #include <cstdint>
#include <variant> #include <variant>
#include <vector>
#include "decode_database.h" #include "decode_database.h"
@@ -27,7 +27,9 @@ struct RawCanFrame {
std::vector<std::uint8_t> data; /**< Payload bytes. */ std::vector<std::uint8_t> data; /**< Payload bytes. */
RawCanFrame() RawCanFrame()
: canId(0U), isExtended(false), data() { : canId (0U)
, isExtended (false)
, data() {
} }
}; };
@@ -41,7 +43,10 @@ struct DecodedSignalValue {
bool valid; /**< true if decoding succeeded. */ bool valid; /**< true if decoding succeeded. */
DecodedSignalValue() DecodedSignalValue()
: definition(nullptr), rawValue(std::uint64_t(0U)), physicalValue(0.0), valid(false) { : definition (nullptr)
, rawValue (std::uint64_t (0U))
, physicalValue (0.0)
, valid (false) {
} }
}; };
@@ -54,7 +59,9 @@ struct DecodedFrameValue {
bool valid; /**< true if frame was matched. */ bool valid; /**< true if frame was matched. */
DecodedFrameValue() DecodedFrameValue()
: definition(nullptr), signals(), valid(false) { : definition (nullptr)
, signals()
, valid (false) {
} }
}; };
@@ -62,7 +69,7 @@ struct DecodedFrameValue {
* @brief Runtime CAN decoder using prebuilt decode database. * @brief Runtime CAN decoder using prebuilt decode database.
*/ */
class DbcDecoder { class DbcDecoder {
public: public:
/** /**
* @brief Find frame definition by CAN ID. * @brief Find frame definition by CAN ID.
* @param database Runtime decode database. * @param database Runtime decode database.
@@ -70,7 +77,7 @@ public:
* @param isExtended true for extended frame. * @param isExtended true for extended frame.
* @return Pointer to frame definition or nullptr. * @return Pointer to frame definition or nullptr.
*/ */
const DecodeFrame *FindFrame(const DecodeDatabase &database, const DecodeFrame *FindFrame (const DecodeDatabase &database,
std::uint32_t canId, std::uint32_t canId,
bool isExtended) const; bool isExtended) const;
@@ -80,25 +87,25 @@ public:
* @param frame Raw CAN frame. * @param frame Raw CAN frame.
* @return Decoded frame value. * @return Decoded frame value.
*/ */
DecodedFrameValue Decode(const DecodeDatabase &database, DecodedFrameValue Decode (const DecodeDatabase &database,
const RawCanFrame &frame) const; const RawCanFrame &frame) const;
private: private:
static bool ExtractUnsigned(const std::vector<std::uint8_t> &data, static bool ExtractUnsigned (const std::vector<std::uint8_t> &data,
const DecodeSignal &signal, const DecodeSignal &signal,
std::uint64_t &value); std::uint64_t &value);
static bool ExtractIntel(const std::vector<std::uint8_t> &data, static bool ExtractIntel (const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value); std::uint64_t &value);
static bool ExtractMotorola(const std::vector<std::uint8_t> &data, static bool ExtractMotorola (const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value); 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 */ #endif /* DBC_DECODER_H */
+16 -16
View File
@@ -12,9 +12,9 @@
#include "dbc_parser.h" #include "dbc_parser.h"
#include "dbc_statement_handler.h" #include "dbc_statement_handler.h"
#include <cctype>
#include <fstream> #include <fstream>
#include <stdexcept> #include <stdexcept>
#include <cctype>
namespace { namespace {
/** /**
@@ -22,42 +22,42 @@ namespace {
* @param text Input text. * @param text Input text.
* @return Trimmed text. * @return Trimmed text.
*/ */
std::string TrimText(const std::string &text) { std::string TrimText (const std::string &text) {
std::string::size_type begin = 0U; std::string::size_type begin = 0U;
while ((begin < text.size()) && while ((begin < text.size()) &&
std::isspace(static_cast<unsigned char>(text[begin]))) std::isspace (static_cast<unsigned char> (text[begin])))
++begin; ++begin;
std::string::size_type end = text.size(); std::string::size_type end = text.size();
while ((end > begin) && while ((end > begin) &&
std::isspace(static_cast<unsigned char>(text[end - 1U]))) std::isspace (static_cast<unsigned char> (text[end - 1U])))
--end; --end;
return text.substr(begin, end - begin); return text.substr (begin, end - begin);
} }
}// namespace }
DbcDatabase DbcParser::ParseFile(const std::string &filePath) const { DbcDatabase DbcParser::ParseFile (const std::string &filePath) const {
std::ifstream input(filePath.c_str()); std::ifstream input (filePath.c_str());
if (!input.is_open()) if (!input.is_open())
throw std::runtime_error("Failed to open DBC file: " + filePath); throw std::runtime_error ("Failed to open DBC file: " + filePath);
DbcDatabase database; DbcDatabase database;
DbcParseContext context{database, nullptr}; DbcParseContext context {database, nullptr};
const DbcStatementDispatcher dispatcher; const DbcStatementDispatcher dispatcher;
std::size_t lineNumber = 0U; std::size_t lineNumber = 0U;
std::string line; std::string line;
while (std::getline(input, line)) { while (std::getline (input, line)) {
++lineNumber; ++lineNumber;
line = Trim(line); line = Trim (line);
if (line.empty()) if (line.empty())
continue; continue;
try { try {
dispatcher.Dispatch(line, context); dispatcher.Dispatch (line, context);
} catch (const std::exception &error) { } catch (const std::exception &error) {
throw std::runtime_error(filePath + ":" + std::to_string(lineNumber) + throw std::runtime_error (filePath + ":" + std::to_string (lineNumber) +
": " + error.what()); ": " + error.what());
} }
} }
@@ -65,6 +65,6 @@ DbcDatabase DbcParser::ParseFile(const std::string &filePath) const {
return database; return database;
} }
std::string DbcParser::Trim(const std::string &text) { std::string DbcParser::Trim (const std::string &text) {
return TrimText(text); return TrimText (text);
} }
+4 -4
View File
@@ -31,7 +31,7 @@
* - value tables * - value tables
*/ */
class DbcParser { class DbcParser {
public: public:
/** /**
* @brief Parse DBC file. * @brief Parse DBC file.
* @param filePath Path to input file. * @param filePath Path to input file.
@@ -39,10 +39,10 @@ public:
* @throws std::runtime_error on file or parse errors. * @throws std::runtime_error on file or parse errors.
* Line parse errors include the file path and one-based line number. * 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: private:
static std::string Trim(const std::string &text); static std::string Trim (const std::string &text);
}; };
#endif /* DBC_PARSER_H */ #endif /* DBC_PARSER_H */
+160 -125
View File
@@ -1,22 +1,20 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h" #include "dbc_statement_handler.h"
#include <cctype>
#include <charconv>
#include <cmath>
#include <limits>
#include <regex>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <cctype>
#include <charconv>
#include <limits>
namespace { namespace {
std::uint32_t ParseDecimal(const std::string &text, std::uint32_t ParseDecimal (const std::string &text,
std::uint32_t maximum, std::uint32_t maximum,
const char *field) { const char *field) {
std::uint32_t value = 0U; std::uint32_t value = 0U;
const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); const auto parsed = std::from_chars (text.data(), text.data() + text.size(), value);
if (text.empty() || parsed.ec != std::errc() || if (text.empty() || parsed.ec != std::errc() ||
parsed.ptr != text.data() + text.size() || value > maximum) parsed.ptr != text.data() + text.size() || value > maximum)
throw std::runtime_error(std::string("Invalid ") + field + ": " + text); throw std::runtime_error (std::string ("Invalid ") + field + ": " + text);
return value; return value;
} }
@@ -25,39 +23,39 @@ namespace {
* @param text Input text. * @param text Input text.
* @return Trimmed text. * @return Trimmed text.
*/ */
std::string TrimText(const std::string &text) { std::string TrimText (const std::string &text) {
std::string::size_type begin = 0U; std::string::size_type begin = 0U;
while ((begin < text.size()) && while ((begin < text.size()) &&
std::isspace(static_cast<unsigned char>(text[begin]))) std::isspace (static_cast<unsigned char> (text[begin])))
++begin; ++begin;
std::string::size_type end = text.size(); std::string::size_type end = text.size();
while ((end > begin) && while ((end > begin) &&
std::isspace(static_cast<unsigned char>(text[end - 1U]))) std::isspace (static_cast<unsigned char> (text[end - 1U])))
--end; --end;
return text.substr(begin, end - begin); return text.substr (begin, end - begin);
} }
FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool); FrameInfo *FindFrameById (DbcDatabase &, std::uint32_t, bool);
SignalInfo *FindSignalByName(FrameInfo &, const std::string &); SignalInfo *FindSignalByName (FrameInfo &, const std::string &);
}// namespace }
namespace { namespace {
std::vector<std::string> SplitReceivers(const std::string &text) { std::vector<std::string> SplitReceivers (const std::string &text) {
std::vector<std::string> receivers; std::vector<std::string> receivers;
std::string token; std::string token;
std::istringstream stream(text); std::istringstream stream (text);
while (std::getline(stream, token, ',')) { while (std::getline (stream, token, ',')) {
token = TrimText(token); token = TrimText (token);
if (!token.empty()) if (!token.empty())
receivers.push_back(token); receivers.push_back (token);
} }
return receivers; return receivers;
} }
void NormalizeCanId(std::uint32_t rawCanId, void NormalizeCanId (std::uint32_t rawCanId,
std::uint32_t &normalizedCanId, std::uint32_t &normalizedCanId,
bool &isExtended) { bool &isExtended) {
/* /*
@@ -72,9 +70,9 @@ namespace {
isExtended = (rawCanId > 0x7FFU); isExtended = (rawCanId > 0x7FFU);
normalizedCanId = rawCanId; normalizedCanId = rawCanId;
} }
} }
std::uint32_t TryExtractPgn(std::uint32_t canId, bool isExtended, bool &hasPgn) { std::uint32_t TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn) {
hasPgn = false; hasPgn = false;
if (!isExtended) if (!isExtended)
@@ -96,109 +94,154 @@ namespace {
hasPgn = true; hasPgn = true;
return pgn; return pgn;
} }
}// namespace }
FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) { FrameInfo DbcFrameHandler::ParseFrameLine (const std::string &line) {
/* /*
* Example: * Example:
* BO_ 256 EngineData: 8 EEC1 * BO_ 256 EngineData: 8 EEC1
*/ */
std::istringstream stream(line); std::istringstream stream (line);
std::string keyword; std::string keyword;
std::string idText; std::string idText;
if (!(stream >> keyword >> idText) || keyword != "BO_") if (!(stream >> keyword >> idText) || keyword != "BO_")
throw std::runtime_error("Missing CAN ID in frame definition."); throw std::runtime_error ("Missing CAN ID in frame definition.");
const std::uint32_t rawCanId = ParseDecimal( const std::uint32_t rawCanId = ParseDecimal (
idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID"); idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
// Only bit 31 may accompany the actual 29-bit identifier. // Only bit 31 may accompany the actual 29-bit identifier.
if ((rawCanId & 0x60000000U) != 0U) if ((rawCanId & 0x60000000U) != 0U)
throw std::runtime_error("CAN ID exceeds the 29-bit identifier range."); throw std::runtime_error ("CAN ID exceeds the 29-bit identifier range.");
std::string nameText; std::string nameText;
if (!std::getline(stream, nameText, ':') || stream.eof()) if (!std::getline (stream, nameText, ':') || stream.eof())
throw std::runtime_error("Missing ':' in frame definition."); throw std::runtime_error ("Missing ':' in frame definition.");
std::istringstream nameStream(nameText); std::istringstream nameStream (nameText);
FrameInfo frame; FrameInfo frame;
std::string extra; std::string extra;
if (!(nameStream >> frame.name) || (nameStream >> extra)) if (!(nameStream >> frame.name) || (nameStream >> extra))
throw std::runtime_error("Missing or invalid frame name."); throw std::runtime_error ("Missing or invalid frame name.");
std::string lengthText; std::string lengthText;
if (!(stream >> lengthText)) if (!(stream >> lengthText))
throw std::runtime_error("Missing payload length in frame definition."); throw std::runtime_error ("Missing payload length in frame definition.");
frame.dlc = static_cast<std::uint8_t>(ParseDecimal( frame.dlc = static_cast<std::uint8_t> (ParseDecimal (
lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length")); lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
if (!(stream >> frame.transmitter)) if (!(stream >> frame.transmitter))
throw std::runtime_error("Missing transmitter in frame definition."); throw std::runtime_error ("Missing transmitter in frame definition.");
if (stream >> extra) if (stream >> extra)
throw std::runtime_error("Unexpected trailing field in frame definition: " + extra); throw std::runtime_error ("Unexpected trailing field in frame definition: " + extra);
NormalizeCanId(rawCanId, frame.canId, frame.isExtended); NormalizeCanId (rawCanId, frame.canId, frame.isExtended);
frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn); frame.pgn = TryExtractPgn (frame.canId, frame.isExtended, frame.hasPgn);
return frame; return frame;
} }
SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) { SignalInfo DbcSignalHandler::ParseSignalLine (const std::string &line) {
// Match the complete statement before converting any numeric fields. /*
static const std::regex syntax( * Example:
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"); * SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
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.");
signal.startBit = ParseDecimal(fields[2].str(), std::string work = TrimText (line);
std::numeric_limits<std::uint32_t>::max(), "signal start bit"); if (work.compare (0U, 4U, "SG_ ") != 0)
signal.length = ParseDecimal(fields[3].str(), 64U, "signal length"); throw std::runtime_error ("Invalid signal line: " + line);
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() == "-";
const auto parsePhysical = [](const std::string &text, const char *field) { work.erase (0U, 4U);
static const std::regex number(
R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)"); const std::string::size_type colonPos = work.find (':');
if (!std::regex_match(text, number)) if (colonPos == std::string::npos)
throw std::runtime_error(std::string("Invalid ") + field + ": " + text); throw std::runtime_error ("Signal line missing ':' : " + line);
std::size_t consumed = 0U;
double value; signal.name = TrimText (work.substr (0U, colonPos));
try { std::string rest = TrimText (work.substr (colonPos + 1U));
value = std::stod(text, &consumed);
} catch (const std::exception &) { const std::string::size_type pipePos = rest.find ('|');
throw std::runtime_error(std::string("Invalid ") + field + ": " + text); 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');
} }
if (consumed != text.size() || !std::isfinite(value))
throw std::runtime_error(std::string("Invalid ") + field + ": " + text); {
return value; const char signChar = rest[signPos];
}; signal.isSigned = (signChar == '-');
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.factor = std::stod (
signal.maximum = parsePhysical(fields[9].str(), "signal maximum"); TrimText (rest.substr (factorBegin + 1U, factorComma - factorBegin - 1U))
if (signal.minimum > signal.maximum) );
throw std::runtime_error("Signal minimum exceeds maximum.");
signal.unit = fields[10].str(); signal.offset = std::stod (
signal.receivers = SplitReceivers(fields[11].str()); 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; return signal;
} }
void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &database) { void DbcCommentHandler::ParseCommentLine (const std::string &line, DbcDatabase &database) {
/* /*
* Examples: * Examples:
* CM_ BO_ 256 "Frame comment"; * CM_ BO_ 256 "Frame comment";
* CM_ SG_ 256 EngineSpeed "Signal comment"; * CM_ SG_ 256 EngineSpeed "Signal comment";
*/ */
std::istringstream stream(line); std::istringstream stream (line);
std::string token; std::string token;
stream >> token; stream >> token;
@@ -213,19 +256,19 @@ void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &d
bool isExtended = false; bool isExtended = false;
stream >> rawCanId; stream >> rawCanId;
NormalizeCanId(rawCanId, canId, isExtended); NormalizeCanId (rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find('"'); const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind('"'); const std::string::size_type quoteEnd = line.rfind ('"');
if ((quoteBegin == std::string::npos) || if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) || (quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin)) (quoteEnd <= quoteBegin))
return; return;
FrameInfo *frame = FindFrameById(database, canId, isExtended); FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr) if (frame != nullptr)
frame->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U); frame->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
} else if (token == "SG_") { } else if (token == "SG_") {
std::uint32_t rawCanId = 0U; std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U; std::uint32_t canId = 0U;
@@ -235,27 +278,27 @@ void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &d
stream >> rawCanId; stream >> rawCanId;
stream >> signalName; stream >> signalName;
NormalizeCanId(rawCanId, canId, isExtended); NormalizeCanId (rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find('"'); const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind('"'); const std::string::size_type quoteEnd = line.rfind ('"');
if ((quoteBegin == std::string::npos) || if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) || (quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin)) (quoteEnd <= quoteBegin))
return; return;
FrameInfo *frame = FindFrameById(database, canId, isExtended); FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr) { if (frame != nullptr) {
SignalInfo *signal = FindSignalByName(*frame, signalName); SignalInfo *signal = FindSignalByName (*frame, signalName);
if (signal != nullptr) if (signal != nullptr)
signal->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U); signal->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
} }
} }
} }
namespace { namespace {
FrameInfo *FindFrameById(DbcDatabase &database, FrameInfo *FindFrameById (DbcDatabase &database,
std::uint32_t canId, std::uint32_t canId,
bool isExtended) { bool isExtended) {
for (std::size_t index = 0U; index < database.frames.size(); ++index) { for (std::size_t index = 0U; index < database.frames.size(); ++index) {
@@ -265,59 +308,51 @@ namespace {
} }
return nullptr; return nullptr;
} }
SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) { SignalInfo *FindSignalByName (FrameInfo &frame, const std::string &signalName) {
for (std::size_t index = 0U; index < frame.signals.size(); ++index) { for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
if (frame.signals[index].name == signalName) if (frame.signals[index].name == signalName)
return &frame.signals[index]; return &frame.signals[index];
} }
return nullptr; return nullptr;
} }
}// namespace }
void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const { void DbcFrameHandler::Handle (const std::string &line, DbcParseContext &context) const {
context.database.frames.push_back(ParseFrameLine(line)); context.database.frames.push_back (ParseFrameLine (line));
context.currentFrame = &context.database.frames.back(); context.currentFrame = &context.database.frames.back();
} }
void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const { 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) if (context.currentFrame == nullptr)
throw std::runtime_error("Signal found before any frame definition."); throw std::runtime_error ("Signal found before any frame definition.");
SignalInfo signal = ParseSignalLine(line); context.currentFrame->signals.push_back (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 DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const { void DbcCommentHandler::Handle (const std::string &line, DbcParseContext &context) const {
if (line.compare(0U, 4U, "CM_ ") == 0) if (line.compare (0U, 4U, "CM_ ") == 0)
ParseCommentLine(line, context.database); ParseCommentLine (line, context.database);
} }
DbcStatementDispatcher::DbcStatementDispatcher() { DbcStatementDispatcher::DbcStatementDispatcher() {
RegisterType<DbcFrameHandler>("BO_"); RegisterType<DbcFrameHandler> ("BO_");
RegisterType<DbcSignalHandler>("SG_"); RegisterType<DbcSignalHandler> ("SG_");
RegisterType<DbcCommentHandler>("CM_"); RegisterType<DbcCommentHandler> ("CM_");
} }
bool DbcStatementDispatcher::Dispatch(const std::string &line, bool DbcStatementDispatcher::Dispatch (const std::string &line,
DbcParseContext &context) const { DbcParseContext &context) const {
std::istringstream stream(line); std::istringstream stream (line);
std::string keyword; std::string keyword;
stream >> keyword; stream >> keyword;
const auto handler = m_handlers.find(keyword); const auto handler = m_handlers.find (keyword);
if (handler == m_handlers.end()) if (handler == m_handlers.end())
return false; return false;
handler->second->Handle(line, context); handler->second->Handle (line, context);
return true; return true;
} }
+22 -25
View File
@@ -14,52 +14,49 @@ struct DbcParseContext {
}; };
class DbcStatementHandler { class DbcStatementHandler {
public: public:
virtual ~DbcStatementHandler() = default; 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 { class DbcFrameHandler : public DbcStatementHandler {
public: public:
void Handle(const std::string &line, DbcParseContext &context) const override; void Handle (const std::string &line, DbcParseContext &context) const override;
private:
private: static FrameInfo ParseFrameLine (const std::string &line);
static FrameInfo ParseFrameLine(const std::string &line);
}; };
class DbcSignalHandler : public DbcStatementHandler { class DbcSignalHandler : public DbcStatementHandler {
public: public:
void Handle(const std::string &line, DbcParseContext &context) const override; void Handle (const std::string &line, DbcParseContext &context) const override;
private:
private: static SignalInfo ParseSignalLine (const std::string &line);
static SignalInfo ParseSignalLine(const std::string &line);
}; };
class DbcCommentHandler : public DbcStatementHandler { class DbcCommentHandler : public DbcStatementHandler {
public: public:
void Handle(const std::string &line, DbcParseContext &context) const override; void Handle (const std::string &line, DbcParseContext &context) const override;
private:
private: static void ParseCommentLine (const std::string &line, DbcDatabase &database);
static void ParseCommentLine(const std::string &line, DbcDatabase &database);
}; };
/** Exact-key dispatch to handlers created once per registration via TypeFactory. */ /** Exact-key dispatch to handlers created once per registration via TypeFactory. */
class DbcStatementDispatcher { class DbcStatementDispatcher {
public: public:
DbcStatementDispatcher(); DbcStatementDispatcher();
template<class Handler> template <class Handler>
void RegisterType(const std::string &keyword) { void RegisterType (const std::string &keyword) {
m_factory.registerType<Handler>(keyword); m_factory.registerType<Handler> (keyword);
m_handlers.insert_or_assign(keyword, m_factory.create(keyword)); m_handlers.insert_or_assign (keyword, m_factory.create (keyword));
} }
/** Returns false for an unregistered keyword; input must be trimmed. */ /** 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; 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 */ #endif /* DBC_STATEMENT_HANDLER_H */
+6 -6
View File
@@ -11,20 +11,20 @@
#include "dbc_tree_builder.h" #include "dbc_tree_builder.h"
std::unique_ptr<TreeNode> DbcTreeBuilder::Build(const DbcDatabase &database) const { std::unique_ptr<TreeNode> DbcTreeBuilder::Build (const DbcDatabase &database) const {
std::unique_ptr<TreeNode> root(new TreeNode()); std::unique_ptr<TreeNode> root (new TreeNode());
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) { for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
const FrameInfo &frame = database.frames[frameIndex]; const FrameInfo &frame = database.frames[frameIndex];
std::unique_ptr<TreeNode> frameNode(new TreeNode(frame)); std::unique_ptr<TreeNode> frameNode (new TreeNode (frame));
for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) { for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) {
const SignalInfo &signal = frame.signals[signalIndex]; const SignalInfo &signal = frame.signals[signalIndex];
std::unique_ptr<TreeNode> signalNode(new TreeNode(signal)); std::unique_ptr<TreeNode> signalNode (new TreeNode (signal));
frameNode->AddChild(std::move(signalNode)); 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. * @brief Builds a simple tree from parsed DBC database.
*/ */
class DbcTreeBuilder { class DbcTreeBuilder {
public: public:
/** /**
* @brief Build tree representation of parsed DBC data. * @brief Build tree representation of parsed DBC data.
* @param database Parsed database. * @param database Parsed database.
* @return Root node of the tree. * @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 */ #endif /* DBC_TREE_BUILDER_H */
+31 -10
View File
@@ -12,10 +12,10 @@
#ifndef DECODE_DATABASE_H #ifndef DECODE_DATABASE_H
#define DECODE_DATABASE_H #define DECODE_DATABASE_H
#include <cstdint>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
#include <unordered_map>
#include <cstdint>
/** /**
* @brief Signal byte order used for runtime decoding. * @brief Signal byte order used for runtime decoding.
@@ -51,7 +51,18 @@ struct DecodeSignal {
std::string comment; /**< Comment. */ std::string comment; /**< Comment. */
DecodeSignal() 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() { : 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() {
} }
}; };
@@ -70,7 +81,15 @@ struct DecodeFrame {
std::vector<DecodeSignal> signals; /**< Signal definitions. */ std::vector<DecodeSignal> signals; /**< Signal definitions. */
DecodeFrame() DecodeFrame()
: name(), canId(0U), isExtended(false), dlc(0U), pgn(0U), hasPgn(false), transmitter(), comment(), signals() { : name()
, canId (0U)
, isExtended (false)
, dlc (0U)
, pgn (0U)
, hasPgn (false)
, transmitter()
, comment()
, signals() {
} }
}; };
@@ -82,14 +101,16 @@ struct FrameKey {
bool isExtended; bool isExtended;
FrameKey() FrameKey()
: canId(0U), isExtended(false) { : canId (0U)
, isExtended (false) {
} }
FrameKey(std::uint32_t id, bool extended) FrameKey (std::uint32_t id, bool extended)
: canId(id), isExtended(extended) { : canId (id)
, isExtended (extended) {
} }
bool operator==(const FrameKey &other) const { bool operator== (const FrameKey &other) const {
return (canId == other.canId) && (isExtended == other.isExtended); return (canId == other.canId) && (isExtended == other.isExtended);
} }
}; };
@@ -98,8 +119,8 @@ struct FrameKey {
* @brief Hasher for frame key. * @brief Hasher for frame key.
*/ */
struct FrameKeyHasher { struct FrameKeyHasher {
std::size_t operator()(const FrameKey &key) const { std::size_t operator() (const FrameKey &key) const {
const std::size_t a = static_cast<std::size_t>(key.canId); const std::size_t a = static_cast<std::size_t> (key.canId);
const std::size_t b = key.isExtended ? 1U : 0U; const std::size_t b = key.isExtended ? 1U : 0U;
return (a * 1315423911U) ^ b; return (a * 1315423911U) ^ b;
} }
+10 -2
View File
@@ -12,9 +12,9 @@
#ifndef FRAME_INFO_H #ifndef FRAME_INFO_H
#define FRAME_INFO_H #define FRAME_INFO_H
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstdint>
#include "signal_info.h" #include "signal_info.h"
@@ -33,7 +33,15 @@ struct FrameInfo {
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */ std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
FrameInfo() FrameInfo()
: name(), canId(0U), isExtended(false), pgn(0U), hasPgn(false), dlc(0U), transmitter(), comment(), signals() { : name()
, canId (0U)
, isExtended (false)
, pgn (0U)
, hasPgn (false)
, dlc (0U)
, transmitter()
, comment()
, signals() {
} }
}; };
-40
View File
@@ -1,40 +0,0 @@
# 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`
+17 -17
View File
@@ -9,18 +9,18 @@
* See LICENSE file in the project root for full license text. * See LICENSE file in the project root for full license text.
*/ */
#include <cstddef>
#include <iomanip>
#include <iostream> #include <iostream>
#include <iomanip>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <cstddef>
#include "dbc_decode_builder.h"
#include "dbc_decoder.h"
#include "dbc_parser.h" #include "dbc_parser.h"
#include "dbc_tree_builder.h" #include "dbc_tree_builder.h"
#include "dbc_decode_builder.h"
#include "dbc_decoder.h"
static void PrintTree(const TreeNode *node, int indent) { static void PrintTree (const TreeNode *node, int indent) {
if (node == nullptr) if (node == nullptr)
return; return;
@@ -39,7 +39,7 @@ static void PrintTree(const TreeNode *node, int indent) {
if (frame != nullptr) { if (frame != nullptr) {
std::cout << " id=0x" << std::hex << frame->canId << std::dec std::cout << " id=0x" << std::hex << frame->canId << std::dec
<< " ext=" << (frame->isExtended ? "yes" : "no") << " ext=" << (frame->isExtended ? "yes" : "no")
<< " dlc=" << static_cast<unsigned int>(frame->dlc); << " dlc=" << static_cast<unsigned int> (frame->dlc);
if (frame->hasPgn) if (frame->hasPgn)
std::cout << " pgn=" << frame->pgn; std::cout << " pgn=" << frame->pgn;
@@ -69,10 +69,10 @@ static void PrintTree(const TreeNode *node, int indent) {
} }
for (std::size_t i = 0U; i < node->GetChildCount(); ++i) for (std::size_t i = 0U; i < node->GetChildCount(); ++i)
PrintTree(node->GetChild(i), indent + 1); PrintTree (node->GetChild (i), indent + 1);
} }
static void PrintDecodedFrame(const DecodedFrameValue &decoded) { static void PrintDecodedFrame (const DecodedFrameValue &decoded) {
if (!decoded.valid || (decoded.definition == nullptr)) { if (!decoded.valid || (decoded.definition == nullptr)) {
std::cout << "No frame definition found.\n"; std::cout << "No frame definition found.\n";
return; return;
@@ -88,14 +88,14 @@ static void PrintDecodedFrame(const DecodedFrameValue &decoded) {
std::cout << " " << signal.definition->name std::cout << " " << signal.definition->name
<< " raw="; << " raw=";
std::visit([](auto rawValue) { std::cout << rawValue; }, signal.rawValue); std::visit ([] (auto rawValue) { std::cout << rawValue; }, signal.rawValue);
std::cout << " physical=" << signal.physicalValue std::cout << " physical=" << signal.physicalValue
<< " " << signal.definition->unit << " " << signal.definition->unit
<< "\n"; << "\n";
} }
} }
int main(int argc, char *argv[]) { int main (int argc, char *argv[]) {
if (argc < 2) { if (argc < 2) {
std::cerr << "Usage: dbc_demo <file.dbc>\n"; std::cerr << "Usage: dbc_demo <file.dbc>\n";
return 1; return 1;
@@ -103,16 +103,16 @@ int main(int argc, char *argv[]) {
try { try {
DbcParser parser; DbcParser parser;
DbcDatabase database = parser.ParseFile(argv[1]); DbcDatabase database = parser.ParseFile (argv[1]);
DbcTreeBuilder treeBuilder; DbcTreeBuilder treeBuilder;
std::unique_ptr<TreeNode> root = treeBuilder.Build(database); std::unique_ptr<TreeNode> root = treeBuilder.Build (database);
std::cout << "=== Parsed tree ===\n"; std::cout << "=== Parsed tree ===\n";
PrintTree(root.get(), 0); PrintTree (root.get(), 0);
DbcDecodeBuilder decodeBuilder; DbcDecodeBuilder decodeBuilder;
DecodeDatabase decodeDatabase = decodeBuilder.Build(database); DecodeDatabase decodeDatabase = decodeBuilder.Build (database);
/* /*
* Example raw frame. * Example raw frame.
@@ -121,13 +121,13 @@ int main(int argc, char *argv[]) {
RawCanFrame rawFrame; RawCanFrame rawFrame;
rawFrame.canId = decodeDatabase.frames.empty() ? 0U : decodeDatabase.frames[0].canId; rawFrame.canId = decodeDatabase.frames.empty() ? 0U : decodeDatabase.frames[0].canId;
rawFrame.isExtended = decodeDatabase.frames.empty() ? false : decodeDatabase.frames[0].isExtended; rawFrame.isExtended = decodeDatabase.frames.empty() ? false : decodeDatabase.frames[0].isExtended;
rawFrame.data.resize(8U, 0U); rawFrame.data.resize (8U, 0U);
DbcDecoder decoder; DbcDecoder decoder;
DecodedFrameValue decoded = decoder.Decode(decodeDatabase, rawFrame); DecodedFrameValue decoded = decoder.Decode (decodeDatabase, rawFrame);
std::cout << "\n=== Decoded frame ===\n"; std::cout << "\n=== Decoded frame ===\n";
PrintDecodedFrame(decoded); PrintDecodedFrame (decoded);
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
std::cerr << "Error: " << ex.what() << "\n"; std::cerr << "Error: " << ex.what() << "\n";
return 2; return 2;
+13 -2
View File
@@ -12,9 +12,9 @@
#ifndef SIGNAL_INFO_H #ifndef SIGNAL_INFO_H
#define SIGNAL_INFO_H #define SIGNAL_INFO_H
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstdint>
/** /**
* @brief Describes one signal inside a DBC frame. * @brief Describes one signal inside a DBC frame.
@@ -34,7 +34,18 @@ struct SignalInfo {
std::string comment; /**< Optional signal comment. */ std::string comment; /**< Optional signal comment. */
SignalInfo() 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() { : name()
, startBit (0U)
, length (0U)
, isLittleEndian (true)
, isSigned (false)
, factor (1.0)
, offset (0.0)
, minimum (0.0)
, maximum (0.0)
, unit()
, receivers()
, comment() {
} }
}; };
-3
View File
@@ -43,9 +43,6 @@ following the decoder instructions above, then run `./dbc_parser_test`.
Parser tests cover standard and extended CAN IDs, whitespace, payload storage Parser tests cover standard and extended CAN IDs, whitespace, payload storage
boundaries, missing fields, malformed and overflowing numbers, reserved ID bits, boundaries, missing fields, malformed and overflowing numbers, reserved ID bits,
error paths and line numbers, and integration with signals and comments. 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 Temporary DBC fixtures are created in the system temporary directory and removed
automatically. automatically.
+64 -49
View File
@@ -20,26 +20,44 @@ struct DecodeCase {
int main() { int main() {
const DecodeCase cases[] = { const DecodeCase cases[] = {
{"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0}, {"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned,
{"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC}, true, 11, 1.0, 0.0, 11.0}, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0}, {"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned,
{"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0}, {0xAC}, true, 11, 1.0, 0.0, 11.0},
{"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0}, {"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned,
{"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0}, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.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 starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned,
{"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}, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
{"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}, {"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned,
{"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0}, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.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 signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed,
{"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0}, {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, {"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
std::vector<std::uint8_t>(9U, 0xFF), false, 0, 1.0, 0.0, 0.0}, 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 regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned,
{"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0}, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
{"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.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; int failures = 0;
for (const DecodeCase &test: cases) { for (const DecodeCase &test : cases) {
DecodeSignal signal; DecodeSignal signal;
signal.name = test.name; signal.name = test.name;
signal.byteOrder = test.byteOrder; signal.byteOrder = test.byteOrder;
@@ -51,27 +69,27 @@ int main() {
DecodeFrame definition; DecodeFrame definition;
definition.canId = 0x123U; definition.canId = 0x123U;
definition.dlc = static_cast<std::uint8_t>(test.data.size()); definition.dlc = static_cast<std::uint8_t> (test.data.size());
definition.signals.push_back(signal); definition.signals.push_back (signal);
DecodeDatabase database; DecodeDatabase database;
database.frames.push_back(definition); database.frames.push_back (definition);
database.frameIndexByKey.emplace(FrameKey(definition.canId, false), 0U); database.frameIndexByKey.emplace (FrameKey (definition.canId, false), 0U);
RawCanFrame frame; RawCanFrame frame;
frame.canId = definition.canId; frame.canId = definition.canId;
frame.data = test.data; frame.data = test.data;
const DecodedFrameValue result = DbcDecoder().Decode(database, frame); const DecodedFrameValue result = DbcDecoder().Decode (database, frame);
const bool passed = result.valid && const bool passed = result.valid &&
(result.definition == &database.frames[0]) && (result.definition == &database.frames[0]) &&
(result.signals.size() == 1U) && (result.signals.size() == 1U) &&
(result.signals[0].definition == &database.frames[0].signals[0]) && (result.signals[0].definition == &database.frames[0].signals[0]) &&
(result.signals[0].valid == test.valid) && (result.signals[0].valid == test.valid) &&
(!test.valid || (((test.valueType == ValueType::Signed) (!test.valid || (((test.valueType == ValueType::Signed)
? (std::get_if<std::int64_t>(&result.signals[0].rawValue) && ? (std::get_if<std::int64_t> (&result.signals[0].rawValue) &&
std::get<std::int64_t>(result.signals[0].rawValue) == test.rawValue) std::get<std::int64_t> (result.signals[0].rawValue) == test.rawValue)
: (std::get_if<std::uint64_t>(&result.signals[0].rawValue) && : (std::get_if<std::uint64_t> (&result.signals[0].rawValue) &&
std::get<std::uint64_t>(result.signals[0].rawValue) == std::get<std::uint64_t> (result.signals[0].rawValue) ==
static_cast<std::uint64_t>(test.rawValue))) && static_cast<std::uint64_t> (test.rawValue))) &&
(result.signals[0].physicalValue == test.physicalValue))); (result.signals[0].physicalValue == test.physicalValue)));
if (!passed) { if (!passed) {
std::cerr << "FAIL: " << test.name << '\n'; std::cerr << "FAIL: " << test.name << '\n';
@@ -79,12 +97,12 @@ int main() {
} }
} }
std::size_t boundaryCases = 0U; std::size_t boundaryCases = 0U;
for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) { for (ByteOrder order : {ByteOrder::Intel, ByteOrder::Motorola}) {
for (ValueType type: {ValueType::Unsigned, ValueType::Signed}) { for (ValueType type : {ValueType::Unsigned, ValueType::Signed}) {
for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL), for (std::uint64_t bits : {std::uint64_t (0U), std::uint64_t (0x7FFFFFFFFFFFFFFFULL),
std::uint64_t(0x8000000000000000ULL), std::uint64_t (0x8000000000000000ULL),
std::uint64_t(0x8000000000000001ULL), std::uint64_t (0x8000000000000001ULL),
std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) { std::uint64_t (0xFFFFFFFFFFFFFFFFULL)}) {
DecodeDatabase database; DecodeDatabase database;
DecodeFrame definition; DecodeFrame definition;
DecodeSignal signal; DecodeSignal signal;
@@ -94,31 +112,28 @@ int main() {
signal.valueType = type; signal.valueType = type;
signal.factor = 0.5; signal.factor = 0.5;
signal.offset = 10.0; signal.offset = 10.0;
definition.signals.push_back(signal); definition.signals.push_back (signal);
database.frames.push_back(definition); database.frames.push_back (definition);
database.frameIndexByKey.emplace(FrameKey(0U, false), 0U); database.frameIndexByKey.emplace (FrameKey (0U, false), 0U);
RawCanFrame frame; RawCanFrame frame;
for (unsigned byte = 0U; byte < 8U; ++byte) { for (unsigned byte = 0U; byte < 8U; ++byte) {
const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U; const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
frame.data.push_back(static_cast<std::uint8_t>(bits >> shift)); frame.data.push_back (static_cast<std::uint8_t> (bits >> shift));
} }
const auto result = DbcDecoder().Decode(database, frame); const auto result = DbcDecoder().Decode (database, frame);
const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
? static_cast<std::int64_t>(bits) ? static_cast<std::int64_t> (bits)
: std::numeric_limits<std::int64_t>::min() + : std::numeric_limits<std::int64_t>::min() +
static_cast<std::int64_t>(bits - 0x8000000000000000ULL); static_cast<std::int64_t> (bits - 0x8000000000000000ULL);
const auto &decoded = result.signals.at(0U); const auto &decoded = result.signals.at (0U);
const auto expected = type == ValueType::Unsigned const auto expected = type == ValueType::Unsigned
? std::variant<std::int64_t, std::uint64_t>(bits) ? std::variant<std::int64_t, std::uint64_t> (bits)
: std::variant<std::int64_t, std::uint64_t>(signedExpected); : std::variant<std::int64_t, std::uint64_t> (signedExpected);
const double physical = (type == ValueType::Unsigned const double physical = (type == ValueType::Unsigned
? static_cast<double>(bits) ? static_cast<double> (bits) : static_cast<double> (signedExpected)) * 0.5 + 10.0;
: static_cast<double>(signedExpected)) *
0.5 +
10.0;
std::ostringstream actualText; std::ostringstream actualText;
std::ostringstream expectedText; std::ostringstream expectedText;
std::visit([&actualText](auto raw) { actualText << raw; }, decoded.rawValue); std::visit ([&actualText] (auto raw) { actualText << raw; }, decoded.rawValue);
if (type == ValueType::Unsigned) if (type == ValueType::Unsigned)
expectedText << bits; expectedText << bits;
else else
@@ -134,6 +149,6 @@ int main() {
} }
if (failures != 0) if (failures != 0)
return 1; return 1;
std::cout << "All " << sizeof(cases) / sizeof(cases[0]) + boundaryCases << " decoder cases passed.\n"; std::cout << "All " << sizeof (cases) / sizeof (cases[0]) + boundaryCases << " decoder cases passed.\n";
return 0; return 0;
} }
+54 -174
View File
@@ -9,25 +9,23 @@
struct Fixture { struct Fixture {
std::filesystem::path path = std::filesystem::temp_directory_path() / 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"); ("dbc_parser_test_" + std::to_string (
~Fixture() { std::chrono::steady_clock::now().time_since_epoch().count()) + ".dbc");
std::error_code error; ~Fixture() { std::error_code error; std::filesystem::remove (path, error); }
std::filesystem::remove(path, error); void Write (const std::string &text) const {
} std::ofstream output (path);
void Write(const std::string &text) const {
std::ofstream output(path);
output << text; output << text;
if (!output) if (!output)
throw std::runtime_error("Cannot write test fixture."); throw std::runtime_error ("Cannot write test fixture.");
} }
}; };
class CountingHandler : public DbcStatementHandler { class CountingHandler : public DbcStatementHandler {
public: public:
inline static int instances = 0; inline static int instances = 0;
inline static int calls = 0; inline static int calls = 0;
CountingHandler() { ++instances; } CountingHandler() { ++instances; }
void Handle(const std::string &, DbcParseContext &context) const override { void Handle (const std::string &, DbcParseContext &context) const override {
++calls; ++calls;
context.database.frames.emplace_back(); context.database.frames.emplace_back();
} }
@@ -37,19 +35,11 @@ int main() {
Fixture fixture; Fixture fixture;
int failures = 0; int failures = 0;
std::size_t count = 0U; std::size_t count = 0U;
const auto check = [&](bool passed, const std::string &name) { const auto check = [&] (bool passed, const std::string &name) {
++count; ++count;
if (!passed) { if (!passed) { ++failures; std::cerr << "FAIL: " << name << '\n'; }
++failures;
std::cerr << "FAIL: " << name << '\n';
}
};
struct ValidCase {
const char *line;
std::uint32_t id;
bool extended;
unsigned length;
}; };
struct ValidCase { const char *line; std::uint32_t id; bool extended; unsigned length; };
const ValidCase valid[] = { const ValidCase valid[] = {
{"BO_ 0 Frame: 0 ECU", 0U, false, 0U}, {"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
{"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U}, {"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
@@ -57,18 +47,18 @@ int main() {
{"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U}, {"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
{"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U}, {"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
{"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U}, {"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
{" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}}; {" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}
for (const auto &test: valid) { };
fixture.Write(std::string(test.line) + '\n'); for (const auto &test : valid) {
fixture.Write (std::string (test.line) + '\n');
try { try {
const auto database = DbcParser().ParseFile(fixture.path.string()); const auto database = DbcParser().ParseFile (fixture.path.string());
check(database.frames.size() == 1U && database.frames[0].name == "Frame" && check (database.frames.size() == 1U && database.frames[0].name == "Frame" &&
database.frames[0].canId == test.id && database.frames[0].canId == test.id &&
database.frames[0].isExtended == test.extended && database.frames[0].isExtended == test.extended &&
database.frames[0].dlc == test.length && database.frames[0].dlc == test.length &&
database.frames[0].transmitter == "ECU", database.frames[0].transmitter == "ECU", test.line);
test.line); } catch (const std::exception &error) { check (false, error.what()); }
} catch (const std::exception &error) { check(false, error.what()); }
} }
const char *invalid[] = { const char *invalid[] = {
"BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU", "BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
@@ -80,172 +70,62 @@ int main() {
"BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 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: -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: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
"BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"}; "BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"
for (const auto *line: invalid) { };
fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n'); for (const auto *line : invalid) {
fixture.Write (std::string ("VERSION \"test\"\n\n") + line + '\n');
try { try {
DbcParser().ParseFile(fixture.path.string()); DbcParser().ParseFile (fixture.path.string());
check(false, line); check (false, line);
} catch (const std::runtime_error &error) { } catch (const std::runtime_error &error) {
const std::string prefix = fixture.path.string() + ":3: "; const std::string prefix = fixture.path.string() + ":3: ";
check(std::string(error.what()).find(prefix) == 0U && check (std::string (error.what()).find (prefix) == 0U &&
std::string(error.what()).size() > prefix.size(), std::string (error.what()).size() > prefix.size(), line);
line);
} }
} }
fixture.Write( fixture.Write (
"BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n" "BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
" SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" 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" "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
"BO_ 257 Second: 8 ECU\n"); "BO_ 257 Second: 8 ECU\n");
const auto database = DbcParser().ParseFile(fixture.path.string()); const auto database = DbcParser().ParseFile (fixture.path.string());
check(database.frames.size() == 2U && database.frames[0].signals.size() == 1U && check (database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
database.frames[0].comment == "Frame comment" && database.frames[0].comment == "Frame comment" &&
database.frames[0].signals[0].comment == "Signal comment", database.frames[0].signals[0].comment == "Signal comment", "Integration");
"Integration"); fixture.Write ("\nSG_ Speed : invalid\n");
fixture.Write("\nSG_ Speed : invalid\n");
try { try {
DbcParser().ParseFile(fixture.path.string()); DbcParser().ParseFile (fixture.path.string());
check(false, "Signal error location"); check (false, "Signal error location");
} catch (const std::runtime_error &error) { } catch (const std::runtime_error &error) {
check(std::string(error.what()).find(fixture.path.string() + ":2: ") == 0U, check (std::string (error.what()).find (fixture.path.string() + ":2: ") == 0U,
"Signal error location"); "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; DbcDatabase dispatchDatabase;
DbcParseContext context{dispatchDatabase, nullptr}; DbcParseContext context {dispatchDatabase, nullptr};
DbcStatementDispatcher dispatcher; DbcStatementDispatcher dispatcher;
dispatcher.RegisterType<CountingHandler>("TEST_"); dispatcher.RegisterType<CountingHandler> ("TEST_");
check(dispatcher.Dispatch("TEST_ first", context) && check (dispatcher.Dispatch ("TEST_ first", context) &&
dispatcher.Dispatch("TEST_ second", context) && dispatcher.Dispatch ("TEST_ second", context) &&
CountingHandler::instances == 1 && CountingHandler::calls == 2 && CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
dispatchDatabase.frames.size() == 2U, dispatchDatabase.frames.size() == 2U, "Factory dispatch and handler reuse");
"Factory dispatch and handler reuse"); check (!dispatcher.Dispatch ("TEST_OTHER data", context) &&
check(!dispatcher.Dispatch("TEST_OTHER data", context) && !dispatcher.Dispatch ("BO_TX_BU_ 1 : ECU;", context) &&
!dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) && !dispatcher.Dispatch ("VAL_ 1 Speed 0 \"Stopped\";", context) &&
!dispatcher.Dispatch("VAL_ 1 Speed 0 \"Stopped\";", context) && CountingHandler::calls == 2, "Exact and unknown keyword dispatch");
CountingHandler::calls == 2,
"Exact and unknown keyword dispatch");
const DbcParser parser; const DbcParser parser;
fixture.Write("BO_ 1 First: 8 ECU\n"); fixture.Write ("BO_ 1 First: 8 ECU\n");
check(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse"); check (parser.ParseFile (fixture.path.string()).frames.size() == 1U, "First parse");
fixture.Write("SG_ Speed : invalid\n"); fixture.Write ("SG_ Speed : invalid\n");
try { try {
parser.ParseFile(fixture.path.string()); parser.ParseFile (fixture.path.string());
check(false, "Independent parse context"); check (false, "Independent parse context");
} catch (const std::runtime_error &error) { } catch (const std::runtime_error &error) {
check(std::string(error.what()).find("Signal found before any frame") != check (std::string (error.what()).find ("Signal found before any frame") !=
std::string::npos, std::string::npos, "Independent parse context");
"Independent parse context");
} }
fixture.Write("BO_ 2 Second: 8 ECU\n"); fixture.Write ("BO_ 2 Second: 8 ECU\n");
const auto recovered = parser.ParseFile(fixture.path.string()); const auto recovered = parser.ParseFile (fixture.path.string());
check(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U, check (recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
"Fresh parse after error"); "Fresh parse after error");
if (failures != 0) return 1; if (failures != 0) return 1;
std::cout << "All " << count << " parser cases passed.\n"; std::cout << "All " << count << " parser cases passed.\n";
+21 -9
View File
@@ -12,34 +12,46 @@
#include "tree_node.h" #include "tree_node.h"
TreeNode::TreeNode() 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) TreeNode::TreeNode (const FrameInfo &frame)
: m_type(NodeType::Frame), m_name(frame.name), m_children(), m_frame(new FrameInfo(frame)), m_signal() { : m_type (NodeType::Frame)
, m_name (frame.name)
, m_children()
, m_frame (new FrameInfo (frame))
, m_signal() {
} }
TreeNode::TreeNode(const SignalInfo &signal) TreeNode::TreeNode (const SignalInfo &signal)
: m_type(NodeType::Signal), m_name(signal.name), m_children(), m_frame(), m_signal(new 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) { void TreeNode::AddChild (std::unique_ptr<TreeNode> child) {
if (child) if (child)
m_children.push_back(std::move(child)); m_children.push_back (std::move (child));
} }
std::size_t TreeNode::GetChildCount() const { std::size_t TreeNode::GetChildCount() const {
return m_children.size(); return m_children.size();
} }
const TreeNode *TreeNode::GetChild(std::size_t index) const { const TreeNode *TreeNode::GetChild (std::size_t index) const {
if (index >= m_children.size()) if (index >= m_children.size())
return nullptr; return nullptr;
return m_children[index].get(); return m_children[index].get();
} }
TreeNode *TreeNode::GetChild(std::size_t index) { TreeNode *TreeNode::GetChild (std::size_t index) {
if (index >= m_children.size()) if (index >= m_children.size())
return nullptr; return nullptr;
+14 -14
View File
@@ -12,10 +12,10 @@
#ifndef TREE_NODE_H #ifndef TREE_NODE_H
#define TREE_NODE_H #define TREE_NODE_H
#include <cstddef>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
#include <cstddef>
#include "frame_info.h" #include "frame_info.h"
#include "signal_info.h" #include "signal_info.h"
@@ -33,7 +33,7 @@ enum class NodeType {
* @brief Tree node for later use in model/view or other hierarchy consumers. * @brief Tree node for later use in model/view or other hierarchy consumers.
*/ */
class TreeNode { class TreeNode {
public: public:
/** /**
* @brief Create root node. * @brief Create root node.
*/ */
@@ -43,27 +43,27 @@ public:
* @brief Create frame node. * @brief Create frame node.
* @param frame Frame payload. * @param frame Frame payload.
*/ */
explicit TreeNode(const FrameInfo &frame); explicit TreeNode (const FrameInfo &frame);
/** /**
* @brief Create signal node. * @brief Create signal node.
* @param signal Signal payload. * @param signal Signal payload.
*/ */
explicit TreeNode(const SignalInfo &signal); explicit TreeNode (const SignalInfo &signal);
~TreeNode() = default; ~TreeNode() = default;
TreeNode(const TreeNode &) = delete; TreeNode (const TreeNode &) = delete;
TreeNode &operator=(const TreeNode &) = delete; TreeNode &operator= (const TreeNode &) = delete;
TreeNode(TreeNode &&) = default; TreeNode (TreeNode &&) = default;
TreeNode &operator=(TreeNode &&) = default; TreeNode &operator= (TreeNode &&) = default;
/** /**
* @brief Add child node. * @brief Add child node.
* @param child Child node to add. * @param child Child node to add.
*/ */
void AddChild(std::unique_ptr<TreeNode> child); void AddChild (std::unique_ptr<TreeNode> child);
/** /**
* @brief Get child count. * @brief Get child count.
@@ -76,14 +76,14 @@ public:
* @param index Child index. * @param index Child index.
* @return Child pointer or nullptr if index is invalid. * @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. * @brief Get mutable child by index.
* @param index Child index. * @param index Child index.
* @return Child pointer or nullptr if index is invalid. * @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. * @brief Get node type.
@@ -109,10 +109,10 @@ public:
*/ */
const SignalInfo *GetSignal() const; const SignalInfo *GetSignal() const;
private: private:
NodeType m_type; NodeType m_type;
std::string m_name; 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<FrameInfo> m_frame;
std::unique_ptr<SignalInfo> m_signal; std::unique_ptr<SignalInfo> m_signal;
}; };
+9 -9
View File
@@ -7,7 +7,7 @@
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility>// std::move #include <utility> // std::move
/** /**
* @brief Registry-based factory for creating objects by runtime identifier. * @brief Registry-based factory for creating objects by runtime identifier.
@@ -55,7 +55,7 @@
* (void)b->get(); * (void)b->get();
* @endcode * @endcode
*/ */
template<class ClassId, class BaseClass, class... Args> template <class ClassId, class BaseClass, class... Args>
class TypeFactory { class TypeFactory {
public: public:
using BasePtr = std::shared_ptr<BaseClass>; using BasePtr = std::shared_ptr<BaseClass>;
@@ -72,8 +72,8 @@ public:
* *
* Re-registering the same id overwrites the previous entry ("last wins"). * Re-registering the same id overwrites the previous entry ("last wins").
*/ */
template<class Derived> template <class Derived>
void registerType(const ClassId &id) { void registerType(const ClassId& id) {
static_assert(std::is_base_of_v<BaseClass, Derived>, static_assert(std::is_base_of_v<BaseClass, Derived>,
"Derived must inherit from BaseClass"); "Derived must inherit from BaseClass");
static_assert(std::is_constructible_v<Derived, Args...>, static_assert(std::is_constructible_v<Derived, Args...>,
@@ -86,7 +86,7 @@ public:
* @brief Returns creator function for the given id. * @brief Returns creator function for the given id.
* @throws std::out_of_range when id is not registered. * @throws std::out_of_range when id is not registered.
*/ */
[[nodiscard]] CreatorFn creator(const ClassId &id) const { [[nodiscard]] CreatorFn creator(const ClassId& id) const {
return classes.at(id); return classes.at(id);
} }
@@ -94,14 +94,14 @@ public:
* @brief Creates an instance for the given id using provided arguments. * @brief Creates an instance for the given id using provided arguments.
* @throws std::out_of_range when id is not registered. * @throws std::out_of_range when id is not registered.
*/ */
[[nodiscard]] BasePtr create(const ClassId &id, Args... args) const { [[nodiscard]] BasePtr create(const ClassId& id, Args... args) const {
return creator(id)(std::move(args)...); return creator(id)(std::move(args)...);
} }
/** /**
* @brief Checks whether an id is registered. * @brief Checks whether an id is registered.
*/ */
[[nodiscard]] bool contains(const ClassId &id) const { [[nodiscard]] bool contains(const ClassId& id) const {
return classes.find(id) != classes.end(); return classes.find(id) != classes.end();
} }
@@ -110,9 +110,9 @@ protected:
std::unordered_map<ClassId, CreatorFn> classes; std::unordered_map<ClassId, CreatorFn> classes;
private: private:
template<class Derived> template <class Derived>
static BasePtr instantiate(Args... args) { static BasePtr instantiate(Args... args) {
return std::make_shared<Derived>(std::move(args)...); return std::make_shared<Derived>(std::move(args)...);
} }
}; };
#endif// TYPEFACTORY_H #endif // TYPEFACTORY_H