This commit is contained in:
deeaitch committed 2026-10-07 18:50:11 -04:00
1 parent a0938cb29c
commit 3dd9230fca
21 files changed
+491 -450

No files matched your search

+193 -176
View File
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h"
#include <cctype>
#include "text_utils.h"
#include <charconv>
#include <cmath>
#include <limits>
@@ -13,7 +13,7 @@ namespace {
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);
const std::from_chars_result 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);
@@ -37,27 +37,92 @@ namespace {
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;
Multiplexing ParseMultiplexingMarker(const std::string &marker) {
Multiplexing result;
if (marker == "M")
result.role = MultiplexRole::Selector;
else if (!marker.empty()) {
if (marker[0] != 'm')
throw std::runtime_error("Unsupported multiplexing marker.");
const std::string value = marker.substr(1U);
const std::from_chars_result parsed =
std::from_chars(value.data(), value.data() + value.size(), result.value);
if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
throw std::runtime_error("Invalid or unsupported multiplexing marker.");
result.role = MultiplexRole::Branch;
}
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);
return result;
}
RawSignalValue ParseValueKey(const std::string &number) {
RawSignalValue key;
if (!number.empty() && number[0] == '-') {
std::int64_t value = 0;
const std::from_chars_result parsed =
std::from_chars(number.data(), number.data() + number.size(), value);
if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = NormalizeValueKey(RawSignalValue(value));
} else {
std::uint64_t value = 0;
const std::from_chars_result parsed =
std::from_chars(number.data(), number.data() + number.size(), value);
if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = value;
}
return key;
}
std::string UnescapeDescription(const std::string &quoted) {
std::string description;
for (std::size_t i = 0; i < quoted.size(); ++i) {
if (quoted[i] == '\\')
++i;
description += quoted[i];
}
return description;
}
FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended) {
FrameInfo *frame = nullptr;
for (FrameInfo &candidate : database.frames) {
if (candidate.canId == canId && candidate.isExtended == isExtended) {
if (frame)
throw std::runtime_error("Ambiguous VAL_ frame reference.");
frame = &candidate;
}
}
if (!frame)
throw std::runtime_error("Unresolved VAL_ frame reference.");
return frame;
}
SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName) {
SignalInfo *signal = nullptr;
for (SignalInfo &candidate : frame.signals) {
if (candidate.name == signalName) {
if (signal)
throw std::runtime_error("Ambiguous VAL_ signal reference: " + signalName);
signal = &candidate;
}
}
if (!signal)
throw std::runtime_error("Unresolved VAL_ signal reference: " + signalName);
return signal;
}
std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
if (isLittleEndian)
return bit + 1U;
// From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
return bit % 8U == 0U ? bit + 15U : bit - 1U;
}
FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
}// namespace
} // namespace
namespace {
std::vector<std::string> SplitReceivers(const std::string &text) {
@@ -66,7 +131,7 @@ namespace {
std::istringstream stream(text);
while (std::getline(stream, token, ',')) {
token = TrimText(token);
token = dbc_detail::TrimText(token);
if (!token.empty())
receivers.push_back(token);
}
@@ -74,14 +139,12 @@ namespace {
return receivers;
}
void NormalizeCanId(std::uint32_t rawCanId,
std::uint32_t &normalizedCanId,
bool &isExtended) {
void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
/*
* DBC commonly stores extended identifiers with bit 31 set.
* Example:
* raw id = 0x80000000 | actual_29_bit_id
*/
* 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);
@@ -115,7 +178,7 @@ namespace {
return pgn;
}
}// namespace
} // namespace
FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
/*
@@ -162,48 +225,50 @@ FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
// Match the complete statement before converting any numeric fields.
enum Field {
NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness,
Factor, Offset, Minimum, Maximum, Unit, Receivers
};
static const std::regex syntax(
R"dbc(^SG_[ \t]+([^:]+):[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*\([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*"([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
// Name and optional multiplexing marker.
R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc"
// Bit position, length, byte order and signedness.
R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc"
// Scaling factor and offset.
R"dbc(\([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc"
// Physical range.
R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc"
// Unit and receivers.
R"dbc("([^"\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;
std::istringstream nameFields(fields[1].str());
std::istringstream nameFields(fields[NameAndMarker].str());
std::string marker;
std::string extra;
nameFields >> signal.name >> marker;
if (signal.name.empty() || (nameFields >> extra))
throw std::runtime_error("Invalid signal name or multiplexing marker.");
if (marker == "M")
signal.multiplexing.role = MultiplexRole::Selector;
else if (!marker.empty()) {
if (marker[0] != 'm')
throw std::runtime_error("Unsupported multiplexing marker.");
const std::string value = marker.substr(1U);
const std::from_chars_result parsed = std::from_chars(
value.data(), value.data() + value.size(), signal.multiplexing.value);
if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
throw std::runtime_error("Invalid or unsupported multiplexing marker.");
signal.multiplexing.role = MultiplexRole::Branch;
}
signal.multiplexing = ParseMultiplexingMarker(marker);
signal.startBit = ParseDecimal(fields[2].str(),
signal.startBit = ParseDecimal(fields[StartBit].str(),
std::numeric_limits<std::uint32_t>::max(), "signal start bit");
signal.length = ParseDecimal(fields[3].str(), 64U, "signal length");
signal.length = ParseDecimal(fields[BitLength].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() == "-";
signal.isLittleEndian = fields[ByteOrder].str() == "1";
signal.isSigned = fields[Signedness].str() == "-";
signal.factor = ParsePhysicalValue(fields[6].str(), "signal factor");
signal.offset = ParsePhysicalValue(fields[7].str(), "signal offset");
signal.minimum = ParsePhysicalValue(fields[8].str(), "signal minimum");
signal.maximum = ParsePhysicalValue(fields[9].str(), "signal maximum");
signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor");
signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset");
signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum");
signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum");
if (signal.minimum > signal.maximum)
throw std::runtime_error("Signal minimum exceeds maximum.");
signal.unit = fields[10].str();
signal.receivers = SplitReceivers(fields[11].str());
signal.unit = fields[Unit].str();
signal.receivers = SplitReceivers(fields[Receivers].str());
return signal;
}
@@ -222,71 +287,52 @@ void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &d
return;
stream >> token;
if (token != "BO_" && token != "SG_")
return;
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;
std::uint32_t rawCanId = 0U;
std::uint32_t canId = 0U;
bool isExtended = false;
std::string signalName;
stream >> rawCanId;
if (token == "SG_")
stream >> signalName;
NormalizeCanId(rawCanId, canId, isExtended);
NormalizeCanId(rawCanId, canId, isExtended);
const std::string::size_type quoteBegin = line.find('"');
const std::string::size_type quoteEnd = line.rfind('"');
if (quoteBegin == std::string::npos || quoteEnd == std::string::npos || quoteEnd <= quoteBegin)
return;
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 *frame = FindFrameById(database, canId, isExtended);
if (frame == nullptr)
return;
const std::string comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
if (token == "BO_") {
frame->comment = comment;
return;
}
SignalInfo *signal = FindSignalByName(*frame, signalName);
if (signal != nullptr)
signal->comment = comment;
}
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];
for (FrameInfo &frame : database.frames) {
if (frame.canId == canId && frame.isExtended == isExtended)
return &frame;
}
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];
for (SignalInfo &signal : frame.signals) {
if (signal.name == signalName)
return &signal;
}
return nullptr;
@@ -308,10 +354,7 @@ void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context)
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;
bit = NextSignalBit(bit, signal.isLittleEndian);
}
context.currentFrame->signals.push_back(std::move(signal));
context.signalLines.back().push_back(context.lineNumber);
@@ -335,7 +378,7 @@ bool DbcStatementDispatcher::Dispatch(const std::string &line,
std::istringstream stream(line);
std::string keyword;
stream >> keyword;
const auto handler = m_handlers.find(keyword);
const HandlerMap::const_iterator handler = m_handlers.find(keyword);
if (handler == m_handlers.end())
return false;
handler->second->Handle(line, context);
@@ -348,7 +391,7 @@ void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext
std::smatch fields;
if (!std::regex_match(line, fields, header))
throw std::runtime_error("Malformed or unsupported VAL_ statement.");
const auto rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
if ((rawId & 0x60000000U) != 0U)
throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
PendingValueDescriptions pending;
@@ -360,34 +403,15 @@ void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext
if (!std::regex_search(rest, fields, pair))
throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
const std::string number = fields[1].str();
RawSignalValue key;
if (!number.empty() && number[0] == '-') {
std::int64_t value = 0;
const auto parsed = std::from_chars(number.data(), number.data() + number.size(), value);
if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = NormalizeValueKey(RawSignalValue(value));
} else {
std::uint64_t value = 0;
const auto parsed = std::from_chars(number.data(), number.data() + number.size(), value);
if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = value;
}
std::string description;
const std::string quoted = fields[2].str();
for (std::size_t i = 0; i < quoted.size(); ++i) {
if (quoted[i] == '\\')
++i;
description += quoted[i];
}
const RawSignalValue key = ParseValueKey(number);
const std::string description = UnescapeDescription(fields[2].str());
if (!pending.descriptions.emplace(key, description).second)
throw std::runtime_error("Duplicate VAL_ value key: " + number);
const auto consumed = static_cast<std::size_t>(fields.length());
const std::size_t consumed = static_cast<std::size_t>(fields.length());
rest.erase(0, consumed);
if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
throw std::runtime_error("Missing whitespace between VAL_ pairs.");
rest = TrimText(rest);
rest = dbc_detail::TrimText(rest);
}
if (pending.descriptions.empty())
throw std::runtime_error("VAL_ requires at least one value/description pair.");
@@ -395,29 +419,11 @@ void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext
}
void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
for (const auto &pending: pendingValues) {
for (const PendingValueDescriptions &pending: pendingValues) {
try {
FrameInfo *frame = nullptr;
for (auto &candidate: database.frames) {
if (candidate.canId == pending.canId && candidate.isExtended == pending.isExtended) {
if (frame)
throw std::runtime_error("Ambiguous VAL_ frame reference.");
frame = &candidate;
}
}
if (!frame)
throw std::runtime_error("Unresolved VAL_ frame reference.");
SignalInfo *signal = nullptr;
for (auto &candidate: frame->signals) {
if (candidate.name == pending.signalName) {
if (signal)
throw std::runtime_error("Ambiguous VAL_ signal reference: " + pending.signalName);
signal = &candidate;
}
}
if (!signal)
throw std::runtime_error("Unresolved VAL_ signal reference: " + pending.signalName);
for (const auto &entry: pending.descriptions) {
FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
for (const ValueDescriptions::value_type &entry: pending.descriptions) {
if (!signal->valueDescriptions.insert(entry).second)
throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
}
@@ -437,14 +443,43 @@ namespace {
std::uint64_t bit = signal.startBit;
for (std::uint32_t index = 0U; index < signal.length; ++index) {
bits.push_back(bit);
if (signal.isLittleEndian)
++bit;
else
bit = bit % 8U == 0U ? bit + 15U : bit - 1U;
bit = NextSignalBit(bit, signal.isLittleEndian);
}
return bits;
}
bool AreMutuallyExclusive(const SignalInfo &first, const SignalInfo &second) {
return first.multiplexing.role == MultiplexRole::Branch &&
second.multiplexing.role == MultiplexRole::Branch &&
first.multiplexing.value != second.multiplexing.value;
}
std::string ValidateBranch(const SignalInfo &signal, const SignalInfo *selector) {
if (signal.multiplexing.role != MultiplexRole::Branch)
return {};
if (selector == nullptr)
return "Multiplexed branch has no selector.";
if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))
return "Branch value exceeds selector raw range.";
return {};
}
const SignalInfo *FindSelector(const FrameInfo &frame,
const std::vector<std::size_t> &lines,
const std::string &filePath) {
const SignalInfo *selector = nullptr;
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
const SignalInfo &signal = frame.signals[index];
if (signal.multiplexing.role != MultiplexRole::Selector)
continue;
if (selector != nullptr || signal.isSigned)
throw std::runtime_error(filePath + ":" + std::to_string(lines[index]) +
": Multiplexing requires one unsigned selector per frame.");
selector = &signal;
}
return selector;
}
bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
const std::vector<std::uint64_t> firstBits = SignalBits(first);
const std::vector<std::uint64_t> secondBits = SignalBits(second);
@@ -461,31 +496,13 @@ namespace {
void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
const FrameInfo &frame = database.frames[frameIndex];
const SignalInfo *selector = nullptr;
const SignalInfo *selector = FindSelector(frame, signalLines[frameIndex], filePath);
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
const SignalInfo &signal = frame.signals[index];
if (signal.multiplexing.role != MultiplexRole::Selector)
continue;
if (selector != nullptr || signal.isSigned)
throw std::runtime_error(filePath + ":" + std::to_string(signalLines[frameIndex][index]) +
": Multiplexing requires one unsigned selector per frame.");
selector = &signal;
}
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
const SignalInfo &signal = frame.signals[index];
std::string error;
if (signal.multiplexing.role == MultiplexRole::Branch) {
if (selector == nullptr)
error = "Multiplexed branch has no selector.";
else if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))
error = "Branch value exceeds selector raw range.";
}
std::string error = ValidateBranch(signal, selector);
for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) {
const SignalInfo &other = frame.signals[previous];
const bool exclusive = signal.multiplexing.role == MultiplexRole::Branch &&
other.multiplexing.role == MultiplexRole::Branch &&
signal.multiplexing.value != other.multiplexing.value;
if (!exclusive && SignalsOverlap(signal, other))
if (!AreMutuallyExclusive(signal, other) && SignalsOverlap(signal, other))
error = "Signals overlap without mutually exclusive multiplexing conditions.";
}
if (!error.empty())