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
17 changed files with 386 additions and 234 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 example.dbc:3: Invalid CAN ID: invalid
``` ```
This change validates frame definitions. Signal syntax and unsupported DBC This change validates frame definitions. Signal definitions are also validated as described below; comprehensive
constructs retain their existing behavior; comprehensive validation is still DBC grammar coverage is still planned. File-open errors contain the path but have no line number.
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 ## Comments
Frame comment example: Frame comment example:
@@ -805,8 +833,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. Existing `SG_` and `CM_` syntax policies Unregistered keywords remain ignored. `SG_` accepts spaces and tabs after its keyword; `CM_` retains its existing
are preserved, including requiring a literal space after those keywords. policy requiring a literal space.
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 -2
View File
@@ -60,7 +60,8 @@ DecodedFrameValue DbcDecoder::Decode (const DecodeDatabase &database,
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);
@@ -144,7 +145,8 @@ std::int64_t DbcDecoder::SignExtend (std::uint64_t value, std::uint32_t bitLengt
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 (1U) << bitLength) - 1U; ? ~std::uint64_t(0U)
: (std::uint64_t(1U) << bitLength) - 1U;
value &= valueMask; value &= valueMask;
if ((value & signMask) == 0U) if ((value & signMask) == 0U)
+4 -11
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,9 +27,7 @@ struct RawCanFrame {
std::vector<std::uint8_t> data; /**< Payload bytes. */ std::vector<std::uint8_t> data; /**< Payload bytes. */
RawCanFrame() RawCanFrame()
: canId (0U) : canId(0U), isExtended(false), data() {
, isExtended (false)
, data() {
} }
}; };
@@ -43,10 +41,7 @@ struct DecodedSignalValue {
bool valid; /**< true if decoding succeeded. */ bool valid; /**< true if decoding succeeded. */
DecodedSignalValue() DecodedSignalValue()
: definition (nullptr) : definition(nullptr), rawValue(std::uint64_t(0U)), physicalValue(0.0), valid(false) {
, rawValue (std::uint64_t (0U))
, physicalValue (0.0)
, valid (false) {
} }
}; };
@@ -59,9 +54,7 @@ struct DecodedFrameValue {
bool valid; /**< true if frame was matched. */ bool valid; /**< true if frame was matched. */
DecodedFrameValue() DecodedFrameValue()
: definition (nullptr) : definition(nullptr), signals(), valid(false) {
, signals()
, valid (false) {
} }
}; };
+2 -2
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 {
/** /**
@@ -35,7 +35,7 @@ namespace {
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());
+58 -93
View File
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h" #include "dbc_statement_handler.h"
#include <sstream>
#include <stdexcept>
#include <cctype> #include <cctype>
#include <charconv> #include <charconv>
#include <cmath>
#include <limits> #include <limits>
#include <regex>
#include <sstream>
#include <stdexcept>
namespace { namespace {
std::uint32_t ParseDecimal(const std::string &text, std::uint32_t ParseDecimal(const std::string &text,
@@ -38,7 +40,7 @@ namespace {
} }
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) {
@@ -96,7 +98,7 @@ std::uint32_t TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn)
return pgn; return pgn;
} }
} }// namespace
FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) { FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
/* /*
@@ -142,95 +144,50 @@ FrameInfo DbcFrameHandler::ParseFrameLine (const std::string &line) {
} }
SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) { SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
/* // Match the complete statement before converting any numeric fields.
* Example: static const std::regex syntax(
* SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2 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); signal.startBit = ParseDecimal(fields[2].str(),
if (work.compare (0U, 4U, "SG_ ") != 0) std::numeric_limits<std::uint32_t>::max(), "signal start bit");
throw std::runtime_error ("Invalid signal line: " + line); 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 auto parsePhysical = [](const std::string &text, const char *field) {
static const std::regex number(
const std::string::size_type colonPos = work.find (':'); R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
if (colonPos == std::string::npos) if (!std::regex_match(text, number))
throw std::runtime_error ("Signal line missing ':' : " + line); throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
std::size_t consumed = 0U;
signal.name = TrimText (work.substr (0U, colonPos)); double value;
std::string rest = TrimText (work.substr (colonPos + 1U)); try {
value = std::stod(text, &consumed);
const std::string::size_type pipePos = rest.find ('|'); } catch (const std::exception &) {
const std::string::size_type atPos = rest.find ('@'); throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
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);
const char signChar = rest[signPos]; return value;
signal.isSigned = (signChar == '-'); };
} signal.factor = parsePhysical(fields[6].str(), "signal factor");
signal.offset = parsePhysical(fields[7].str(), "signal offset");
signal.factor = std::stod ( signal.minimum = parsePhysical(fields[8].str(), "signal minimum");
TrimText (rest.substr (factorBegin + 1U, factorComma - factorBegin - 1U)) signal.maximum = parsePhysical(fields[9].str(), "signal maximum");
); if (signal.minimum > signal.maximum)
throw std::runtime_error("Signal minimum exceeds maximum.");
signal.offset = std::stod ( signal.unit = fields[10].str();
TrimText (rest.substr (factorComma + 1U, factorEnd - factorComma - 1U)) signal.receivers = SplitReceivers(fields[11].str());
);
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;
} }
@@ -318,7 +275,7 @@ SignalInfo *FindSignalByName (FrameInfo &frame, const std::string &signalName) {
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));
@@ -326,12 +283,20 @@ void DbcFrameHandler::Handle (const std::string &line, DbcParseContext &context)
} }
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.");
context.currentFrame->signals.push_back (ParseSignalLine (line)); 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 DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const { void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
+3
View File
@@ -22,6 +22,7 @@ class DbcStatementHandler {
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);
}; };
@@ -29,6 +30,7 @@ class DbcFrameHandler : public DbcStatementHandler {
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);
}; };
@@ -36,6 +38,7 @@ class DbcSignalHandler : public DbcStatementHandler {
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);
}; };
+7 -28
View File
@@ -12,10 +12,10 @@
#ifndef DECODE_DATABASE_H #ifndef DECODE_DATABASE_H
#define DECODE_DATABASE_H #define DECODE_DATABASE_H
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint> #include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
/** /**
* @brief Signal byte order used for runtime decoding. * @brief Signal byte order used for runtime decoding.
@@ -51,18 +51,7 @@ struct DecodeSignal {
std::string comment; /**< Comment. */ std::string comment; /**< Comment. */
DecodeSignal() DecodeSignal()
: name() : 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() {
, 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() {
} }
}; };
@@ -81,15 +70,7 @@ struct DecodeFrame {
std::vector<DecodeSignal> signals; /**< Signal definitions. */ std::vector<DecodeSignal> signals; /**< Signal definitions. */
DecodeFrame() DecodeFrame()
: name() : name(), canId(0U), isExtended(false), dlc(0U), pgn(0U), hasPgn(false), transmitter(), comment(), signals() {
, canId (0U)
, isExtended (false)
, dlc (0U)
, pgn (0U)
, hasPgn (false)
, transmitter()
, comment()
, signals() {
} }
}; };
@@ -101,13 +82,11 @@ struct FrameKey {
bool isExtended; bool isExtended;
FrameKey() FrameKey()
: canId (0U) : canId(0U), isExtended(false) {
, isExtended (false) {
} }
FrameKey(std::uint32_t id, bool extended) FrameKey(std::uint32_t id, bool extended)
: canId (id) : canId(id), isExtended(extended) {
, isExtended (extended) {
} }
bool operator==(const FrameKey &other) const { bool operator==(const FrameKey &other) const {
+2 -10
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,15 +33,7 @@ struct FrameInfo {
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */ std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
FrameInfo() FrameInfo()
: name() : name(), canId(0U), isExtended(false), pgn(0U), hasPgn(false), dlc(0U), transmitter(), comment(), signals() {
, canId (0U)
, isExtended (false)
, pgn (0U)
, hasPgn (false)
, dlc (0U)
, transmitter()
, comment()
, signals() {
} }
}; };
+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`
+4 -4
View File
@@ -9,16 +9,16 @@
* See LICENSE file in the project root for full license text. * See LICENSE file in the project root for full license text.
*/ */
#include <iostream> #include <cstddef>
#include <iomanip> #include <iomanip>
#include <iostream>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <cstddef>
#include "dbc_parser.h"
#include "dbc_tree_builder.h"
#include "dbc_decode_builder.h" #include "dbc_decode_builder.h"
#include "dbc_decoder.h" #include "dbc_decoder.h"
#include "dbc_parser.h"
#include "dbc_tree_builder.h"
static void PrintTree(const TreeNode *node, int indent) { static void PrintTree(const TreeNode *node, int indent) {
if (node == nullptr) if (node == nullptr)
+2 -13
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,18 +34,7 @@ struct SignalInfo {
std::string comment; /**< Optional signal comment. */ std::string comment; /**< Optional signal comment. */
SignalInfo() SignalInfo()
: name() : name(), startBit(0U), length(0U), isLittleEndian(true), isSigned(false), factor(1.0), offset(0.0), minimum(0.0), maximum(0.0), unit(), receivers(), comment() {
, 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,6 +43,9 @@ 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.
+19 -34
View File
@@ -20,41 +20,23 @@ struct DecodeCase {
int main() { int main() {
const DecodeCase cases[] = { const DecodeCase cases[] = {
{"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{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 within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0},
{0xAC}, true, 11, 1.0, 0.0, 11.0}, {"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
{"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0}, {"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0},
{"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {"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},
{0x01, 0x80}, true, 3, 1.0, 0.0, 3.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 second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0},
{0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0}, {"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0},
{"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {"Motorola large start", ByteOrder::Motorola, std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
{0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0}, {"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.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, {"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
{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 signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}};
{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) {
@@ -130,7 +112,10 @@ int main() {
? 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> (signedExpected)) * 0.5 + 10.0; ? static_cast<double>(bits)
: 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);
+135 -15
View File
@@ -9,9 +9,11 @@
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 ( ("dbc_parser_test_" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".dbc");
std::chrono::steady_clock::now().time_since_epoch().count()) + ".dbc"); ~Fixture() {
~Fixture() { std::error_code error; std::filesystem::remove (path, error); } std::error_code error;
std::filesystem::remove(path, error);
}
void Write(const std::string &text) const { void Write(const std::string &text) const {
std::ofstream output(path); std::ofstream output(path);
output << text; output << text;
@@ -37,9 +39,17 @@ int main() {
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) { ++failures; std::cerr << "FAIL: " << name << '\n'; } if (!passed) {
++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},
@@ -47,8 +57,7 @@ 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) { for (const auto &test: valid) {
fixture.Write(std::string(test.line) + '\n'); fixture.Write(std::string(test.line) + '\n');
try { try {
@@ -57,7 +66,8 @@ int main() {
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", test.line); database.frames[0].transmitter == "ECU",
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[] = {
@@ -70,8 +80,7 @@ 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) { for (const auto *line: invalid) {
fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n'); fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n');
try { try {
@@ -80,7 +89,8 @@ int main() {
} 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(), line); std::string(error.what()).size() > prefix.size(),
line);
} }
} }
fixture.Write( fixture.Write(
@@ -91,7 +101,8 @@ int main() {
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", "Integration"); database.frames[0].signals[0].comment == "Signal comment",
"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());
@@ -100,6 +111,112 @@ int main() {
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;
@@ -107,11 +224,13 @@ int main() {
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, "Factory dispatch and handler reuse"); dispatchDatabase.frames.size() == 2U,
"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");
@@ -121,7 +240,8 @@ int main() {
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, "Independent parse context"); std::string::npos,
"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());
+3 -15
View File
@@ -12,27 +12,15 @@
#include "tree_node.h" #include "tree_node.h"
TreeNode::TreeNode() TreeNode::TreeNode()
: m_type (NodeType::Root) : m_type(NodeType::Root), m_name("dbc"), m_children(), m_frame(), m_signal() {
, m_name ("dbc")
, m_children()
, m_frame()
, m_signal() {
} }
TreeNode::TreeNode(const FrameInfo &frame) TreeNode::TreeNode(const FrameInfo &frame)
: m_type (NodeType::Frame) : m_type(NodeType::Frame), m_name(frame.name), m_children(), m_frame(new FrameInfo(frame)), m_signal() {
, 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_type(NodeType::Signal), m_name(signal.name), m_children(), m_frame(), m_signal(new SignalInfo(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) {
+2 -2
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"