closes #4 Refactor DBC parsing into TypeFactory statement handlers

This commit is contained in:
deeaitch committed 2026-10-01 14:01:31 -04:00
1 parent 3bd9339baf
commit 88b5883dda
9 files changed
+511 -349

No files matched your search

+39 -9
View File
@@ -151,8 +151,11 @@ Files:
- `dbc_parser.h`
- `dbc_parser.cpp`
- `dbc_statement_handler.h`
- `dbc_statement_handler.cpp`
- `typefactory.h`
These parse the DBC file itself.
The parser reads files and reports errors; registered statement handlers parse DBC constructs.
### Demo
@@ -767,20 +770,47 @@ That is why `DecodeDatabase` is a separate layer.
---
## Why No Abstract Factory Is Used
## Statement Handler Factory
At the current stage, abstract factory is intentionally avoided.
`DbcParser` owns file reading, trimming, line numbers, and error reporting.
Each `ParseFile()` call creates a fresh `DbcParseContext` and a
`DbcStatementDispatcher`. The context contains the parsed database and current
frame; handlers receive it by reference and retain no parsing state.
The current design is already clean:
The dispatcher uses `TypeFactory<std::string, DbcStatementHandler>` to create
registered handlers once per parsing session, then reuses them for all matching
lines:
```text
parser → parsed database → decode database
↘ tree builder → UI tree
- `BO_` → `DbcFrameHandler`
- `SG_` → `DbcSignalHandler`
- `CM_` → `DbcCommentHandler`
Grammar implementations live in `dbc_statement_handler.cpp`; their interface,
context, and dispatcher live in `dbc_statement_handler.h`. `typefactory.h`
provides the generic registry and object creation.
To add a construct, implement a default-constructible handler deriving from
`DbcStatementHandler` with:
```cpp
void Handle(const std::string &line, DbcParseContext &context) const override;
```
Introducing factory layers now would increase complexity without solving an immediate problem.
Register it in the dispatcher constructor:
If later the project requires multiple output representations or multiple build strategies, that can be added then.
```cpp
RegisterType<DbcValueHandler>("VAL_"); // after implementing DbcValueHandler
```
Registration creates one handler; replacing a registration replaces that instance.
Register during initialization before dispatching. A line is routed using its
full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`.
Unregistered keywords remain ignored. Existing `SG_` and `CM_` syntax policies
are preserved, including requiring a literal space after those keywords.
This factory handles statements inside DBC files. A separate format-level
factory can be introduced when another file format is implemented. The decoder
and database builders remain independent of statement dispatch.
---
+3
View File
@@ -7,6 +7,7 @@ SOURCES += \
dbc_decode_builder.cpp \
dbc_decoder.cpp \
dbc_parser.cpp \
dbc_statement_handler.cpp \
dbc_tree_builder.cpp \
main.cpp \
tree_node.cpp
@@ -16,6 +17,8 @@ HEADERS += \
dbc_decode_builder.h \
dbc_decoder.h \
dbc_parser.h \
dbc_statement_handler.h \
typefactory.h \
dbc_tree_builder.h \
decode_database.h \
frame_info.h \
+4 -315
View File
@@ -10,26 +10,13 @@
*/
#include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <cctype>
#include <charconv>
#include <limits>
namespace {
std::uint32_t ParseDecimal (const std::string &text,
std::uint32_t maximum,
const char *field) {
std::uint32_t value = 0U;
const auto parsed = std::from_chars (text.data(), text.data() + text.size(), value);
if (text.empty() || parsed.ec != std::errc() ||
parsed.ptr != text.data() + text.size() || value > maximum)
throw std::runtime_error (std::string ("Invalid ") + field + ": " + text);
return value;
}
/**
* @brief Remove leading and trailing spaces.
* @param text Input text.
@@ -56,7 +43,8 @@ DbcDatabase DbcParser::ParseFile (const std::string &filePath) const {
throw std::runtime_error ("Failed to open DBC file: " + filePath);
DbcDatabase database;
FrameInfo *currentFrame = nullptr;
DbcParseContext context {database, nullptr};
const DbcStatementDispatcher dispatcher;
std::size_t lineNumber = 0U;
std::string line;
@@ -67,18 +55,7 @@ DbcDatabase DbcParser::ParseFile (const std::string &filePath) const {
continue;
try {
if (IsFrameLine (line)) {
FrameInfo frame = ParseFrameLine (line);
database.frames.push_back (frame);
currentFrame = &database.frames.back();
} else if (IsSignalLine (line)) {
if (currentFrame == nullptr)
throw std::runtime_error ("Signal found before any frame definition.");
SignalInfo signal = ParseSignalLine (line);
currentFrame->signals.push_back (signal);
} else if (IsCommentLine (line))
ParseCommentLine (line, database);
dispatcher.Dispatch (line, context);
} catch (const std::exception &error) {
throw std::runtime_error (filePath + ":" + std::to_string (lineNumber) +
": " + error.what());
@@ -88,294 +65,6 @@ DbcDatabase DbcParser::ParseFile (const std::string &filePath) const {
return database;
}
bool DbcParser::IsFrameLine (const std::string &line) {
return (line.compare (0U, 3U, "BO_") == 0) &&
((line.size() == 3U) || std::isspace (static_cast<unsigned char> (line[3U])));
}
bool DbcParser::IsSignalLine (const std::string &line) {
return (line.size() >= 4U) && (line.compare (0U, 4U, "SG_ ") == 0);
}
bool DbcParser::IsCommentLine (const std::string &line) {
return (line.size() >= 4U) && (line.compare (0U, 4U, "CM_ ") == 0);
}
std::string DbcParser::Trim (const std::string &text) {
return TrimText (text);
}
std::vector<std::string> DbcParser::SplitReceivers (const std::string &text) {
std::vector<std::string> receivers;
std::string token;
std::istringstream stream (text);
while (std::getline (stream, token, ',')) {
token = TrimText (token);
if (!token.empty())
receivers.push_back (token);
}
return receivers;
}
void DbcParser::NormalizeCanId (std::uint32_t rawCanId,
std::uint32_t &normalizedCanId,
bool &isExtended) {
/*
* DBC commonly stores extended identifiers with bit 31 set.
* Example:
* raw id = 0x80000000 | actual_29_bit_id
*/
if ((rawCanId & 0x80000000U) != 0U) {
isExtended = true;
normalizedCanId = (rawCanId & 0x1FFFFFFFU);
} else {
isExtended = (rawCanId > 0x7FFU);
normalizedCanId = rawCanId;
}
}
std::uint32_t DbcParser::TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn) {
hasPgn = false;
if (!isExtended)
return 0U;
if ((canId & 0x1FFFFFFFU) != canId)
return 0U;
const std::uint32_t pf = (canId >> 16U) & 0xFFU;
const std::uint32_t ps = (canId >> 8U) & 0xFFU;
const std::uint32_t dp = (canId >> 24U) & 0x01U;
std::uint32_t pgn = 0U;
if (pf < 240U)
pgn = (dp << 16U) | (pf << 8U);
else
pgn = (dp << 16U) | (pf << 8U) | ps;
hasPgn = true;
return pgn;
}
FrameInfo DbcParser::ParseFrameLine (const std::string &line) {
/*
* Example:
* BO_ 256 EngineData: 8 EEC1
*/
std::istringstream stream (line);
std::string keyword;
std::string idText;
if (!(stream >> keyword >> idText) || keyword != "BO_")
throw std::runtime_error ("Missing CAN ID in frame definition.");
const std::uint32_t rawCanId = ParseDecimal (
idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
// Only bit 31 may accompany the actual 29-bit identifier.
if ((rawCanId & 0x60000000U) != 0U)
throw std::runtime_error ("CAN ID exceeds the 29-bit identifier range.");
std::string nameText;
if (!std::getline (stream, nameText, ':') || stream.eof())
throw std::runtime_error ("Missing ':' in frame definition.");
std::istringstream nameStream (nameText);
FrameInfo frame;
std::string extra;
if (!(nameStream >> frame.name) || (nameStream >> extra))
throw std::runtime_error ("Missing or invalid frame name.");
std::string lengthText;
if (!(stream >> lengthText))
throw std::runtime_error ("Missing payload length in frame definition.");
frame.dlc = static_cast<std::uint8_t> (ParseDecimal (
lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
if (!(stream >> frame.transmitter))
throw std::runtime_error ("Missing transmitter in frame definition.");
if (stream >> extra)
throw std::runtime_error ("Unexpected trailing field in frame definition: " + extra);
NormalizeCanId (rawCanId, frame.canId, frame.isExtended);
frame.pgn = TryExtractPgn (frame.canId, frame.isExtended, frame.hasPgn);
return frame;
}
SignalInfo DbcParser::ParseSignalLine (const std::string &line) {
/*
* Example:
* SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
*/
SignalInfo signal;
std::string work = TrimText (line);
if (work.compare (0U, 4U, "SG_ ") != 0)
throw std::runtime_error ("Invalid signal line: " + line);
work.erase (0U, 4U);
const std::string::size_type colonPos = work.find (':');
if (colonPos == std::string::npos)
throw std::runtime_error ("Signal line missing ':' : " + line);
signal.name = TrimText (work.substr (0U, colonPos));
std::string rest = TrimText (work.substr (colonPos + 1U));
const std::string::size_type pipePos = rest.find ('|');
const std::string::size_type atPos = rest.find ('@');
const std::string::size_type signPos = rest.find_first_of ("+-", atPos);
const std::string::size_type factorBegin = rest.find ('(');
const std::string::size_type factorComma = rest.find (',', factorBegin);
const std::string::size_type factorEnd = rest.find (')', factorComma);
const std::string::size_type rangeBegin = rest.find ('[');
const std::string::size_type rangeSep = rest.find ('|', rangeBegin);
const std::string::size_type rangeEnd = rest.find (']', rangeSep);
const std::string::size_type unitBegin = rest.find ('"', rangeEnd);
const std::string::size_type unitEnd = rest.find ('"', unitBegin + 1U);
if ((pipePos == std::string::npos) ||
(atPos == std::string::npos) ||
(signPos == std::string::npos) ||
(factorBegin == std::string::npos) ||
(factorComma == std::string::npos) ||
(factorEnd == std::string::npos) ||
(rangeBegin == std::string::npos) ||
(rangeSep == std::string::npos) ||
(rangeEnd == std::string::npos) ||
(unitBegin == std::string::npos) ||
(unitEnd == std::string::npos))
throw std::runtime_error ("Unsupported signal syntax: " + line);
signal.startBit = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (0U, pipePos)))
);
signal.length = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (pipePos + 1U, atPos - pipePos - 1U)))
);
{
if ((atPos + 1U) >= rest.size())
throw std::runtime_error ("Invalid endianness in signal: " + line);
const char endianChar = rest[atPos + 1U];
signal.isLittleEndian = (endianChar == '1');
}
{
const char signChar = rest[signPos];
signal.isSigned = (signChar == '-');
}
signal.factor = std::stod (
TrimText (rest.substr (factorBegin + 1U, factorComma - factorBegin - 1U))
);
signal.offset = std::stod (
TrimText (rest.substr (factorComma + 1U, factorEnd - factorComma - 1U))
);
signal.minimum = std::stod (
TrimText (rest.substr (rangeBegin + 1U, rangeSep - rangeBegin - 1U))
);
signal.maximum = std::stod (
TrimText (rest.substr (rangeSep + 1U, rangeEnd - rangeSep - 1U))
);
signal.unit = rest.substr (unitBegin + 1U, unitEnd - unitBegin - 1U);
{
const std::string receiversText = TrimText (rest.substr (unitEnd + 1U));
signal.receivers = SplitReceivers (receiversText);
}
return signal;
}
void DbcParser::ParseCommentLine (const std::string &line, DbcDatabase &database) {
/*
* Examples:
* CM_ BO_ 256 "Frame comment";
* CM_ SG_ 256 EngineSpeed "Signal comment";
*/
std::istringstream stream (line);
std::string token;
stream >> token;
if (token != "CM_")
return;
stream >> token;
if (token == "BO_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
stream >> rawCanId;
NormalizeCanId (rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind ('"');
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr)
frame->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
} else if (token == "SG_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
std::string signalName;
stream >> rawCanId;
stream >> signalName;
NormalizeCanId (rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind ('"');
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr) {
SignalInfo *signal = FindSignalByName (*frame, signalName);
if (signal != nullptr)
signal->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
}
}
}
FrameInfo *DbcParser::FindFrameById (DbcDatabase &database,
std::uint32_t canId,
bool isExtended) {
for (std::size_t index = 0U; index < database.frames.size(); ++index) {
if ((database.frames[index].canId == canId) &&
(database.frames[index].isExtended == isExtended))
return &database.frames[index];
}
return nullptr;
}
SignalInfo *DbcParser::FindSignalByName (FrameInfo &frame, const std::string &signalName) {
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
if (frame.signals[index].name == signalName)
return &frame.signals[index];
}
return nullptr;
}
-22
View File
@@ -13,8 +13,6 @@
#define DBC_PARSER_H
#include <string>
#include <vector>
#include <cstdint>
#include "dbc_database.h"
@@ -44,27 +42,7 @@ class DbcParser {
DbcDatabase ParseFile (const std::string &filePath) const;
private:
static bool IsFrameLine (const std::string &line);
static bool IsSignalLine (const std::string &line);
static bool IsCommentLine (const std::string &line);
static std::string Trim (const std::string &text);
static std::vector<std::string> SplitReceivers (const std::string &text);
static std::uint32_t TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn);
static void NormalizeCanId (std::uint32_t rawCanId,
std::uint32_t &normalizedCanId,
bool &isExtended);
static FrameInfo ParseFrameLine (const std::string &line);
static SignalInfo ParseSignalLine (const std::string &line);
static void ParseCommentLine (const std::string &line, DbcDatabase &database);
static FrameInfo *FindFrameById (DbcDatabase &database,
std::uint32_t canId,
bool isExtended);
static SignalInfo *FindSignalByName (FrameInfo &frame, const std::string &signalName);
};
#endif /* DBC_PARSER_H */
+358
View File
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h"
#include <sstream>
#include <stdexcept>
#include <cctype>
#include <charconv>
#include <limits>
namespace {
std::uint32_t ParseDecimal (const std::string &text,
std::uint32_t maximum,
const char *field) {
std::uint32_t value = 0U;
const auto parsed = std::from_chars (text.data(), text.data() + text.size(), value);
if (text.empty() || parsed.ec != std::errc() ||
parsed.ptr != text.data() + text.size() || value > maximum)
throw std::runtime_error (std::string ("Invalid ") + field + ": " + text);
return value;
}
/**
* @brief Remove leading and trailing spaces.
* @param text Input text.
* @return Trimmed text.
*/
std::string TrimText (const std::string &text) {
std::string::size_type begin = 0U;
while ((begin < text.size()) &&
std::isspace (static_cast<unsigned char> (text[begin])))
++begin;
std::string::size_type end = text.size();
while ((end > begin) &&
std::isspace (static_cast<unsigned char> (text[end - 1U])))
--end;
return text.substr (begin, end - begin);
}
FrameInfo *FindFrameById (DbcDatabase &, std::uint32_t, bool);
SignalInfo *FindSignalByName (FrameInfo &, const std::string &);
}
namespace {
std::vector<std::string> SplitReceivers (const std::string &text) {
std::vector<std::string> receivers;
std::string token;
std::istringstream stream (text);
while (std::getline (stream, token, ',')) {
token = TrimText (token);
if (!token.empty())
receivers.push_back (token);
}
return receivers;
}
void NormalizeCanId (std::uint32_t rawCanId,
std::uint32_t &normalizedCanId,
bool &isExtended) {
/*
* DBC commonly stores extended identifiers with bit 31 set.
* Example:
* raw id = 0x80000000 | actual_29_bit_id
*/
if ((rawCanId & 0x80000000U) != 0U) {
isExtended = true;
normalizedCanId = (rawCanId & 0x1FFFFFFFU);
} else {
isExtended = (rawCanId > 0x7FFU);
normalizedCanId = rawCanId;
}
}
std::uint32_t TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn) {
hasPgn = false;
if (!isExtended)
return 0U;
if ((canId & 0x1FFFFFFFU) != canId)
return 0U;
const std::uint32_t pf = (canId >> 16U) & 0xFFU;
const std::uint32_t ps = (canId >> 8U) & 0xFFU;
const std::uint32_t dp = (canId >> 24U) & 0x01U;
std::uint32_t pgn = 0U;
if (pf < 240U)
pgn = (dp << 16U) | (pf << 8U);
else
pgn = (dp << 16U) | (pf << 8U) | ps;
hasPgn = true;
return pgn;
}
}
FrameInfo DbcFrameHandler::ParseFrameLine (const std::string &line) {
/*
* Example:
* BO_ 256 EngineData: 8 EEC1
*/
std::istringstream stream (line);
std::string keyword;
std::string idText;
if (!(stream >> keyword >> idText) || keyword != "BO_")
throw std::runtime_error ("Missing CAN ID in frame definition.");
const std::uint32_t rawCanId = ParseDecimal (
idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
// Only bit 31 may accompany the actual 29-bit identifier.
if ((rawCanId & 0x60000000U) != 0U)
throw std::runtime_error ("CAN ID exceeds the 29-bit identifier range.");
std::string nameText;
if (!std::getline (stream, nameText, ':') || stream.eof())
throw std::runtime_error ("Missing ':' in frame definition.");
std::istringstream nameStream (nameText);
FrameInfo frame;
std::string extra;
if (!(nameStream >> frame.name) || (nameStream >> extra))
throw std::runtime_error ("Missing or invalid frame name.");
std::string lengthText;
if (!(stream >> lengthText))
throw std::runtime_error ("Missing payload length in frame definition.");
frame.dlc = static_cast<std::uint8_t> (ParseDecimal (
lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
if (!(stream >> frame.transmitter))
throw std::runtime_error ("Missing transmitter in frame definition.");
if (stream >> extra)
throw std::runtime_error ("Unexpected trailing field in frame definition: " + extra);
NormalizeCanId (rawCanId, frame.canId, frame.isExtended);
frame.pgn = TryExtractPgn (frame.canId, frame.isExtended, frame.hasPgn);
return frame;
}
SignalInfo DbcSignalHandler::ParseSignalLine (const std::string &line) {
/*
* Example:
* SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
*/
SignalInfo signal;
std::string work = TrimText (line);
if (work.compare (0U, 4U, "SG_ ") != 0)
throw std::runtime_error ("Invalid signal line: " + line);
work.erase (0U, 4U);
const std::string::size_type colonPos = work.find (':');
if (colonPos == std::string::npos)
throw std::runtime_error ("Signal line missing ':' : " + line);
signal.name = TrimText (work.substr (0U, colonPos));
std::string rest = TrimText (work.substr (colonPos + 1U));
const std::string::size_type pipePos = rest.find ('|');
const std::string::size_type atPos = rest.find ('@');
const std::string::size_type signPos = rest.find_first_of ("+-", atPos);
const std::string::size_type factorBegin = rest.find ('(');
const std::string::size_type factorComma = rest.find (',', factorBegin);
const std::string::size_type factorEnd = rest.find (')', factorComma);
const std::string::size_type rangeBegin = rest.find ('[');
const std::string::size_type rangeSep = rest.find ('|', rangeBegin);
const std::string::size_type rangeEnd = rest.find (']', rangeSep);
const std::string::size_type unitBegin = rest.find ('"', rangeEnd);
const std::string::size_type unitEnd = rest.find ('"', unitBegin + 1U);
if ((pipePos == std::string::npos) ||
(atPos == std::string::npos) ||
(signPos == std::string::npos) ||
(factorBegin == std::string::npos) ||
(factorComma == std::string::npos) ||
(factorEnd == std::string::npos) ||
(rangeBegin == std::string::npos) ||
(rangeSep == std::string::npos) ||
(rangeEnd == std::string::npos) ||
(unitBegin == std::string::npos) ||
(unitEnd == std::string::npos))
throw std::runtime_error ("Unsupported signal syntax: " + line);
signal.startBit = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (0U, pipePos)))
);
signal.length = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (pipePos + 1U, atPos - pipePos - 1U)))
);
{
if ((atPos + 1U) >= rest.size())
throw std::runtime_error ("Invalid endianness in signal: " + line);
const char endianChar = rest[atPos + 1U];
signal.isLittleEndian = (endianChar == '1');
}
{
const char signChar = rest[signPos];
signal.isSigned = (signChar == '-');
}
signal.factor = std::stod (
TrimText (rest.substr (factorBegin + 1U, factorComma - factorBegin - 1U))
);
signal.offset = std::stod (
TrimText (rest.substr (factorComma + 1U, factorEnd - factorComma - 1U))
);
signal.minimum = std::stod (
TrimText (rest.substr (rangeBegin + 1U, rangeSep - rangeBegin - 1U))
);
signal.maximum = std::stod (
TrimText (rest.substr (rangeSep + 1U, rangeEnd - rangeSep - 1U))
);
signal.unit = rest.substr (unitBegin + 1U, unitEnd - unitBegin - 1U);
{
const std::string receiversText = TrimText (rest.substr (unitEnd + 1U));
signal.receivers = SplitReceivers (receiversText);
}
return signal;
}
void DbcCommentHandler::ParseCommentLine (const std::string &line, DbcDatabase &database) {
/*
* Examples:
* CM_ BO_ 256 "Frame comment";
* CM_ SG_ 256 EngineSpeed "Signal comment";
*/
std::istringstream stream (line);
std::string token;
stream >> token;
if (token != "CM_")
return;
stream >> token;
if (token == "BO_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
stream >> rawCanId;
NormalizeCanId (rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind ('"');
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr)
frame->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
} else if (token == "SG_") {
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
std::string signalName;
stream >> rawCanId;
stream >> signalName;
NormalizeCanId (rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find ('"');
const std::string::size_type quoteEnd = line.rfind ('"');
if ((quoteBegin == std::string::npos) ||
(quoteEnd == std::string::npos) ||
(quoteEnd <= quoteBegin))
return;
FrameInfo *frame = FindFrameById (database, canId, isExtended);
if (frame != nullptr) {
SignalInfo *signal = FindSignalByName (*frame, signalName);
if (signal != nullptr)
signal->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
}
}
}
namespace {
FrameInfo *FindFrameById (DbcDatabase &database,
std::uint32_t canId,
bool isExtended) {
for (std::size_t index = 0U; index < database.frames.size(); ++index) {
if ((database.frames[index].canId == canId) &&
(database.frames[index].isExtended == isExtended))
return &database.frames[index];
}
return nullptr;
}
SignalInfo *FindSignalByName (FrameInfo &frame, const std::string &signalName) {
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
if (frame.signals[index].name == signalName)
return &frame.signals[index];
}
return nullptr;
}
}
void DbcFrameHandler::Handle (const std::string &line, DbcParseContext &context) const {
context.database.frames.push_back (ParseFrameLine (line));
context.currentFrame = &context.database.frames.back();
}
void DbcSignalHandler::Handle (const std::string &line, DbcParseContext &context) const {
// Preserve the existing signal syntax policy during this refactor.
if (line.compare (0U, 4U, "SG_ ") != 0)
return;
if (context.currentFrame == nullptr)
throw std::runtime_error ("Signal found before any frame definition.");
context.currentFrame->signals.push_back (ParseSignalLine (line));
}
void DbcCommentHandler::Handle (const std::string &line, DbcParseContext &context) const {
if (line.compare (0U, 4U, "CM_ ") == 0)
ParseCommentLine (line, context.database);
}
DbcStatementDispatcher::DbcStatementDispatcher() {
RegisterType<DbcFrameHandler> ("BO_");
RegisterType<DbcSignalHandler> ("SG_");
RegisterType<DbcCommentHandler> ("CM_");
}
bool DbcStatementDispatcher::Dispatch (const std::string &line,
DbcParseContext &context) const {
std::istringstream stream (line);
std::string keyword;
stream >> keyword;
const auto handler = m_handlers.find (keyword);
if (handler == m_handlers.end())
return false;
handler->second->Handle (line, context);
return true;
}
+62
View File
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
#ifndef DBC_STATEMENT_HANDLER_H
#define DBC_STATEMENT_HANDLER_H
#include "dbc_database.h"
#include "typefactory.h"
#include <string>
#include <unordered_map>
/** Mutable state belonging to one file parse. Handlers do not retain it. */
struct DbcParseContext {
DbcDatabase &database;
FrameInfo *currentFrame = nullptr;
};
class DbcStatementHandler {
public:
virtual ~DbcStatementHandler() = default;
virtual void Handle (const std::string &line, DbcParseContext &context) const = 0;
};
class DbcFrameHandler : public DbcStatementHandler {
public:
void Handle (const std::string &line, DbcParseContext &context) const override;
private:
static FrameInfo ParseFrameLine (const std::string &line);
};
class DbcSignalHandler : public DbcStatementHandler {
public:
void Handle (const std::string &line, DbcParseContext &context) const override;
private:
static SignalInfo ParseSignalLine (const std::string &line);
};
class DbcCommentHandler : public DbcStatementHandler {
public:
void Handle (const std::string &line, DbcParseContext &context) const override;
private:
static void ParseCommentLine (const std::string &line, DbcDatabase &database);
};
/** Exact-key dispatch to handlers created once per registration via TypeFactory. */
class DbcStatementDispatcher {
public:
DbcStatementDispatcher();
template <class Handler>
void RegisterType (const std::string &keyword) {
m_factory.registerType<Handler> (keyword);
m_handlers.insert_or_assign (keyword, m_factory.create (keyword));
}
/** Returns false for an unregistered keyword; input must be trimmed. */
bool Dispatch (const std::string &line, DbcParseContext &context) const;
private:
TypeFactory<std::string, DbcStatementHandler> m_factory;
std::unordered_map<std::string, std::shared_ptr<DbcStatementHandler> > m_handlers;
};
#endif /* DBC_STATEMENT_HANDLER_H */
+4 -1
View File
@@ -33,7 +33,7 @@ Run from the repository root:
```sh
g++ -std=c++17 -Wall -Wextra -Wpedantic -Werror -I. \
tests/dbc_parser_test.cpp dbc_parser.cpp -o /tmp/dbc_parser_test
tests/dbc_parser_test.cpp dbc_parser.cpp dbc_statement_handler.cpp -o /tmp/dbc_parser_test
/tmp/dbc_parser_test
```
@@ -45,3 +45,6 @@ boundaries, missing fields, malformed and overflowing numbers, reserved ID bits,
error paths and line numbers, and integration with signals and comments.
Temporary DBC fixtures are created in the system temporary directory and removed
automatically.
Dispatch tests also verify exact keyword matching, handler construction and reuse,
unknown statements, and independent parsing sessions, including recovery after errors.
+39
View File
@@ -1,4 +1,5 @@
#include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include <chrono>
#include <filesystem>
@@ -19,6 +20,17 @@ struct Fixture {
}
};
class CountingHandler : public DbcStatementHandler {
public:
inline static int instances = 0;
inline static int calls = 0;
CountingHandler() { ++instances; }
void Handle (const std::string &, DbcParseContext &context) const override {
++calls;
context.database.frames.emplace_back();
}
};
int main() {
Fixture fixture;
int failures = 0;
@@ -88,6 +100,33 @@ int main() {
check (std::string (error.what()).find (fixture.path.string() + ":2: ") == 0U,
"Signal error location");
}
DbcDatabase dispatchDatabase;
DbcParseContext context {dispatchDatabase, nullptr};
DbcStatementDispatcher dispatcher;
dispatcher.RegisterType<CountingHandler> ("TEST_");
check (dispatcher.Dispatch ("TEST_ first", context) &&
dispatcher.Dispatch ("TEST_ second", context) &&
CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
dispatchDatabase.frames.size() == 2U, "Factory dispatch and handler reuse");
check (!dispatcher.Dispatch ("TEST_OTHER data", context) &&
!dispatcher.Dispatch ("BO_TX_BU_ 1 : ECU;", context) &&
!dispatcher.Dispatch ("VAL_ 1 Speed 0 \"Stopped\";", context) &&
CountingHandler::calls == 2, "Exact and unknown keyword dispatch");
const DbcParser parser;
fixture.Write ("BO_ 1 First: 8 ECU\n");
check (parser.ParseFile (fixture.path.string()).frames.size() == 1U, "First parse");
fixture.Write ("SG_ Speed : invalid\n");
try {
parser.ParseFile (fixture.path.string());
check (false, "Independent parse context");
} catch (const std::runtime_error &error) {
check (std::string (error.what()).find ("Signal found before any frame") !=
std::string::npos, "Independent parse context");
}
fixture.Write ("BO_ 2 Second: 8 ECU\n");
const auto recovered = parser.ParseFile (fixture.path.string());
check (recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
"Fresh parse after error");
if (failures != 0) return 1;
std::cout << "All " << count << " parser cases passed.\n";
return 0;
+2 -2
View File
@@ -3,5 +3,5 @@ TARGET = dbc_parser_test
CONFIG += console c++17
CONFIG -= app_bundle qt
INCLUDEPATH += ..
SOURCES += dbc_parser_test.cpp ../dbc_parser.cpp
HEADERS += ../dbc_parser.h ../dbc_database.h ../frame_info.h ../signal_info.h
SOURCES += dbc_parser_test.cpp ../dbc_parser.cpp ../dbc_statement_handler.cpp
HEADERS += ../dbc_statement_handler.h ../typefactory.h ../dbc_parser.h ../dbc_database.h ../frame_info.h ../signal_info.h