Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7e72e1d12 | ||
|
|
88b5883dda | ||
|
|
3bd9339baf |
No files matched your search
@@ -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
|
||||
|
||||
@@ -505,6 +508,36 @@ Parsed fields:
|
||||
|
||||
---
|
||||
|
||||
### Frame validation and parse errors
|
||||
|
||||
`BO_` definitions require a decimal CAN ID, a nonempty single-token frame name,
|
||||
a colon, a decimal payload length, and a transmitter token. Spaces or tabs may
|
||||
separate fields, and whitespace before the colon is accepted. Additional trailing
|
||||
fields are rejected.
|
||||
|
||||
Numeric fields must contain decimal digits only; signs, hexadecimal notation,
|
||||
fractional values, suffixes, and overflow are rejected. CAN IDs may contain the
|
||||
29-bit identifier plus the optional DBC extended flag (bit 31). Bits 29 and 30
|
||||
are rejected rather than silently discarded. For compatibility, unflagged IDs
|
||||
above `0x7FF` are still treated as extended.
|
||||
|
||||
The payload length must fit `uint8_t` (`0..255`). This is a storage-range check,
|
||||
not CAN or CAN FD protocol validation; no new 8-byte or 64-byte limit is imposed.
|
||||
|
||||
Parsing stops at the first detected error. Exceptions raised while parsing a
|
||||
line are reported as `std::runtime_error` with the supplied file path and the
|
||||
one-based physical line number, including blank lines:
|
||||
|
||||
```text
|
||||
example.dbc:3: Invalid CAN ID: invalid
|
||||
```
|
||||
|
||||
This change validates frame definitions. Signal syntax and unsupported DBC
|
||||
constructs retain their existing behavior; comprehensive validation is still
|
||||
planned. File-open errors contain the path but have no line number.
|
||||
|
||||
---
|
||||
|
||||
## Signal definition
|
||||
|
||||
Example:
|
||||
@@ -737,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.
|
||||
|
||||
---
|
||||
|
||||
@@ -871,5 +931,6 @@ This gives FrameTap a much better foundation for real use, because both browsing
|
||||
## Regression tests
|
||||
|
||||
See [tests/README.md](tests/README.md) for compiler and qmake commands.
|
||||
Tests cover Intel and Motorola extraction, signed and unsigned 64-bit boundaries,
|
||||
Tests cover frame validation and error locations, Intel and Motorola extraction,
|
||||
signed and unsigned 64-bit boundaries,
|
||||
physical conversion, numeric output, and invalid signal payloads.
|
||||
@@ -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 \
|
||||
|
||||
+11
-297
@@ -10,9 +10,9 @@
|
||||
*/
|
||||
|
||||
#include "dbc_parser.h"
|
||||
#include "dbc_statement_handler.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <cctype>
|
||||
|
||||
@@ -43,314 +43,28 @@ 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;
|
||||
while (std::getline (input, line)) {
|
||||
++lineNumber;
|
||||
line = Trim (line);
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
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);
|
||||
try {
|
||||
dispatcher.Dispatch (line, context);
|
||||
} catch (const std::exception &error) {
|
||||
throw std::runtime_error (filePath + ":" + std::to_string (lineNumber) +
|
||||
": " + error.what());
|
||||
}
|
||||
}
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
bool DbcParser::IsFrameLine (const std::string &line) {
|
||||
return (line.size() >= 4U) && (line.compare (0U, 4U, "BO_ ") == 0);
|
||||
}
|
||||
|
||||
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 token;
|
||||
FrameInfo frame;
|
||||
std::uint32_t rawCanId = 0U;
|
||||
|
||||
stream >> token;
|
||||
if (token != "BO_")
|
||||
throw std::runtime_error ("Invalid frame line: " + line);
|
||||
|
||||
stream >> rawCanId;
|
||||
NormalizeCanId (rawCanId, frame.canId, frame.isExtended);
|
||||
|
||||
stream >> token;
|
||||
if (token.empty())
|
||||
throw std::runtime_error ("Missing frame name: " + line);
|
||||
|
||||
if (token[token.size() - 1U] == ':')
|
||||
token.erase (token.size() - 1U, 1U);
|
||||
|
||||
frame.name = token;
|
||||
|
||||
{
|
||||
unsigned int dlcValue = 0U;
|
||||
stream >> dlcValue;
|
||||
frame.dlc = static_cast<std::uint8_t> (dlcValue);
|
||||
}
|
||||
|
||||
stream >> frame.transmitter;
|
||||
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;
|
||||
}
|
||||
+1
-22
@@ -13,8 +13,6 @@
|
||||
#define DBC_PARSER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#include "dbc_database.h"
|
||||
|
||||
@@ -39,31 +37,12 @@ class DbcParser {
|
||||
* @param filePath Path to input file.
|
||||
* @return Parsed database.
|
||||
* @throws std::runtime_error on file or parse errors.
|
||||
* Line parse errors include the file path and one-based line number.
|
||||
*/
|
||||
DbcDatabase ParseFile (const std::string &filePath) const;
|
||||
|
||||
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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -26,3 +26,25 @@ signals, signed values, scaling, 64-bit extraction, signed/unsigned boundary val
|
||||
(including `INT64_MIN`, `INT64_MAX`, and `UINT64_MAX`), numeric output, invalid lengths, insufficient
|
||||
payloads, and Intel regression cases. Frame validity means the definition was
|
||||
found; insufficient payloads are checked through signal validity.
|
||||
|
||||
## Parser regression tests
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```sh
|
||||
g++ -std=c++17 -Wall -Wextra -Wpedantic -Werror -I. \
|
||||
tests/dbc_parser_test.cpp dbc_parser.cpp dbc_statement_handler.cpp -o /tmp/dbc_parser_test
|
||||
/tmp/dbc_parser_test
|
||||
```
|
||||
|
||||
For qmake, use `tests/dbc_parser_test.pro` in a separate build directory,
|
||||
following the decoder instructions above, then run `./dbc_parser_test`.
|
||||
|
||||
Parser tests cover standard and extended CAN IDs, whitespace, payload storage
|
||||
boundaries, missing fields, malformed and overflowing numbers, reserved ID bits,
|
||||
error paths and line numbers, and integration with signals and comments.
|
||||
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.
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "dbc_parser.h"
|
||||
#include "dbc_statement_handler.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
struct Fixture {
|
||||
std::filesystem::path path = std::filesystem::temp_directory_path() /
|
||||
("dbc_parser_test_" + std::to_string (
|
||||
std::chrono::steady_clock::now().time_since_epoch().count()) + ".dbc");
|
||||
~Fixture() { std::error_code error; std::filesystem::remove (path, error); }
|
||||
void Write (const std::string &text) const {
|
||||
std::ofstream output (path);
|
||||
output << text;
|
||||
if (!output)
|
||||
throw std::runtime_error ("Cannot write test fixture.");
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
std::size_t count = 0U;
|
||||
const auto check = [&] (bool passed, const std::string &name) {
|
||||
++count;
|
||||
if (!passed) { ++failures; std::cerr << "FAIL: " << name << '\n'; }
|
||||
};
|
||||
struct ValidCase { const char *line; std::uint32_t id; bool extended; unsigned length; };
|
||||
const ValidCase valid[] = {
|
||||
{"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
|
||||
{"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
|
||||
{"BO_ 2048 Frame: 64 ECU", 2048U, true, 64U},
|
||||
{"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
|
||||
{"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
|
||||
{"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
|
||||
{" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}
|
||||
};
|
||||
for (const auto &test : valid) {
|
||||
fixture.Write (std::string (test.line) + '\n');
|
||||
try {
|
||||
const auto database = DbcParser().ParseFile (fixture.path.string());
|
||||
check (database.frames.size() == 1U && database.frames[0].name == "Frame" &&
|
||||
database.frames[0].canId == test.id &&
|
||||
database.frames[0].isExtended == test.extended &&
|
||||
database.frames[0].dlc == test.length &&
|
||||
database.frames[0].transmitter == "ECU", test.line);
|
||||
} catch (const std::exception &error) { check (false, error.what()); }
|
||||
}
|
||||
const char *invalid[] = {
|
||||
"BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
|
||||
"BO_ 1 Frame:", "BO_ 1 Frame: 8", "BO_ 1 Frame: 8 ECU extra",
|
||||
"BO_ 1 Two Names: 8 ECU", "BO_ -1 Frame: 8 ECU", "BO_ +1 Frame: 8 ECU",
|
||||
"BO_ 1x Frame: 8 ECU", "BO_ 0x123 Frame: 8 ECU", "BO_ 1.5 Frame: 8 ECU",
|
||||
"BO_ 4294967296 Frame: 8 ECU", "BO_ 999999999999999999999 Frame: 8 ECU",
|
||||
"BO_ 536870912 Frame: 8 ECU", "BO_ 1073741824 Frame: 8 ECU",
|
||||
"BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 Frame: 8 ECU",
|
||||
"BO_ 1 Frame: -1 ECU", "BO_ 1 Frame: +8 ECU", "BO_ 1 Frame: 8x ECU",
|
||||
"BO_ 1 Frame: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
|
||||
"BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"
|
||||
};
|
||||
for (const auto *line : invalid) {
|
||||
fixture.Write (std::string ("VERSION \"test\"\n\n") + line + '\n');
|
||||
try {
|
||||
DbcParser().ParseFile (fixture.path.string());
|
||||
check (false, line);
|
||||
} catch (const std::runtime_error &error) {
|
||||
const std::string prefix = fixture.path.string() + ":3: ";
|
||||
check (std::string (error.what()).find (prefix) == 0U &&
|
||||
std::string (error.what()).size() > prefix.size(), line);
|
||||
}
|
||||
}
|
||||
fixture.Write (
|
||||
"BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
|
||||
" SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
|
||||
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
|
||||
"BO_ 257 Second: 8 ECU\n");
|
||||
const auto database = DbcParser().ParseFile (fixture.path.string());
|
||||
check (database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
|
||||
database.frames[0].comment == "Frame comment" &&
|
||||
database.frames[0].signals[0].comment == "Signal comment", "Integration");
|
||||
fixture.Write ("\nSG_ Speed : invalid\n");
|
||||
try {
|
||||
DbcParser().ParseFile (fixture.path.string());
|
||||
check (false, "Signal error location");
|
||||
} catch (const std::runtime_error &error) {
|
||||
check (std::string (error.what()).find (fixture.path.string() + ":2: ") == 0U,
|
||||
"Signal error location");
|
||||
}
|
||||
DbcDatabase dispatchDatabase;
|
||||
DbcParseContext context {dispatchDatabase, nullptr};
|
||||
DbcStatementDispatcher dispatcher;
|
||||
dispatcher.RegisterType<CountingHandler> ("TEST_");
|
||||
check (dispatcher.Dispatch ("TEST_ first", context) &&
|
||||
dispatcher.Dispatch ("TEST_ second", context) &&
|
||||
CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
|
||||
dispatchDatabase.frames.size() == 2U, "Factory dispatch and handler reuse");
|
||||
check (!dispatcher.Dispatch ("TEST_OTHER data", context) &&
|
||||
!dispatcher.Dispatch ("BO_TX_BU_ 1 : ECU;", context) &&
|
||||
!dispatcher.Dispatch ("VAL_ 1 Speed 0 \"Stopped\";", context) &&
|
||||
CountingHandler::calls == 2, "Exact and unknown keyword dispatch");
|
||||
const DbcParser parser;
|
||||
fixture.Write ("BO_ 1 First: 8 ECU\n");
|
||||
check (parser.ParseFile (fixture.path.string()).frames.size() == 1U, "First parse");
|
||||
fixture.Write ("SG_ Speed : invalid\n");
|
||||
try {
|
||||
parser.ParseFile (fixture.path.string());
|
||||
check (false, "Independent parse context");
|
||||
} catch (const std::runtime_error &error) {
|
||||
check (std::string (error.what()).find ("Signal found before any frame") !=
|
||||
std::string::npos, "Independent parse context");
|
||||
}
|
||||
fixture.Write ("BO_ 2 Second: 8 ECU\n");
|
||||
const auto recovered = parser.ParseFile (fixture.path.string());
|
||||
check (recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
|
||||
"Fresh parse after error");
|
||||
if (failures != 0) return 1;
|
||||
std::cout << "All " << count << " parser cases passed.\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
TEMPLATE = app
|
||||
TARGET = dbc_parser_test
|
||||
CONFIG += console c++17
|
||||
CONFIG -= app_bundle qt
|
||||
INCLUDEPATH += ..
|
||||
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
|
||||
Reference in new issue
Block a user