DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_attributes.cpp
1// SPDX-License-Identifier: MIT
2#include "dbc_statement_handler.h"
3#include <charconv>
4#include <cmath>
5#include <limits>
6#include <set>
7
8namespace {
9 struct Token {
10 std::string text;
11 bool quoted = false;
12 };
13
14 bool IsWhitespace(char character) {
15 return character == ' ' || character == '\t' || character == '\n' || character == '\r';
16 }
17
18 std::vector<Token> Tokenize(const std::string &text) {
19 std::vector<Token> tokens;
20 std::size_t index = 0U;
21 while (index < text.size()) {
22 const char character = text[index++];
23 if (IsWhitespace(character))
24 continue;
25 Token token;
26 if (character == '"') {
27 token.quoted = true;
28 bool closed = false;
29 while (index < text.size()) {
30 char next = text[index++];
31 if (next == '"') {
32 closed = true;
33 break;
34 }
35 if (next == '\\') {
36 if (index == text.size())
37 throw std::runtime_error("Incomplete attribute escape.");
38 next = text[index++];
39 if (next != '"' && next != '\\')
40 throw std::runtime_error("Invalid attribute escape.");
41 }
42 token.text += next;
43 }
44 if (!closed)
45 throw std::runtime_error("Unterminated attribute string.");
46 if (index < text.size() && !IsWhitespace(text[index]) &&
47 text[index] != ',' && text[index] != ';')
48 throw std::runtime_error("Missing separator after attribute string.");
49 } else if (character == ';' || character == ',') {
50 token.text = character;
51 } else {
52 token.text = character;
53 while (index < text.size() && !IsWhitespace(text[index]) &&
54 text[index] != ';' && text[index] != ',') {
55 if (text[index] == '"')
56 throw std::runtime_error("Missing separator before attribute string.");
57 token.text += text[index++];
58 }
59 }
60 tokens.push_back(token);
61 }
62 if (tokens.empty() || tokens.back().quoted || tokens.back().text != ";")
63 throw std::runtime_error("Attribute statement requires a semicolon.");
64 tokens.pop_back();
65 return tokens;
66 }
67
68 class AttributeTokens {
69 public:
70 explicit AttributeTokens(const std::string &line) : tokens(Tokenize(line)) {}
71 Token Take() {
72 if (position == tokens.size())
73 throw std::runtime_error("Incomplete attribute statement.");
74 return tokens[position++];
75 }
76 std::string Word() {
77 const Token token = Take();
78 if (token.quoted || token.text == "," || token.text == ";")
79 throw std::runtime_error("Expected an attribute keyword or number.");
80 return token.text;
81 }
82 std::string Quoted() {
83 const Token token = Take();
84 if (!token.quoted)
85 throw std::runtime_error("Expected a quoted attribute name or string.");
86 return token.text;
87 }
88 bool Empty() const { return position == tokens.size(); }
89 void End() const {
90 if (!Empty())
91 throw std::runtime_error("Unexpected trailing attribute fields.");
92 }
93
94 private:
95 std::vector<Token> tokens;
96 std::size_t position = 0U;
97 };
98
99 bool IsScope(const std::string &text) {
100 return text == "BO_" || text == "SG_" || text == "BU_" || text == "EV_";
101 }
102
103 AttributeScope ParseScope(const std::string &text) {
104 if (text == "BO_") return AttributeScope::Frame;
105 if (text == "SG_") return AttributeScope::Signal;
106 if (text == "BU_") return AttributeScope::Node;
107 if (text == "EV_") return AttributeScope::Environment;
108 throw std::runtime_error("Unsupported attribute scope: " + text);
109 }
110
111 template<class Numeric>
112 Numeric ParseNumber(const std::string &text) {
113 const char *begin = text.data();
114 const char *end = begin + text.size();
115 if (begin != end && *begin == '+') {
116 ++begin;
117 if (begin != end && (*begin == '-' || *begin == '+'))
118 throw std::runtime_error("Invalid attribute number: " + text);
119 }
120 Numeric value = 0;
121 const std::from_chars_result parsed = std::from_chars(begin, end, value);
122 if (begin == end || parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value))
123 throw std::runtime_error("Invalid attribute number: " + text);
124 return value;
125 }
126
127 AttributeValue Value(const Token &token, const AttributeDefinition &definition, bool isDefault) {
128 if (definition.type == AttributeType::String) {
129 if (!token.quoted) throw std::runtime_error("Attribute STRING requires quoted text.");
130 return token.text;
131 }
132 if (definition.type == AttributeType::Enum && isDefault && token.quoted) {
133 for (std::size_t index = 0U; index < definition.enumValues.size(); ++index) {
134 if (definition.enumValues[index] == token.text)
135 return static_cast<std::int64_t>(index);
136 }
137 throw std::runtime_error("Unknown attribute ENUM default label.");
138 }
139 if (token.quoted) throw std::runtime_error("Numeric attribute requires an unquoted number.");
140 if (definition.type == AttributeType::Float) {
141 const double value = ParseNumber<double>(token.text);
142 if (value < definition.minimum || value > definition.maximum)
143 throw std::runtime_error("Attribute FLOAT value outside definition range.");
144 return value;
145 }
146 const std::int64_t value = ParseNumber<std::int64_t>(token.text);
147 if (value < definition.integerMinimum || value > definition.integerMaximum)
148 throw std::runtime_error("Attribute integer/ENUM value outside definition range.");
149 return value;
150 }
151
152 void Define(AttributeTokens &tokens, DbcDatabase &database) {
153 AttributeDefinition definition;
154 const Token first = tokens.Take();
155 std::string name = first.text;
156 if (!first.quoted) {
157 definition.scope = ParseScope(first.text);
158 name = tokens.Quoted();
159 }
160 if (name.empty()) throw std::runtime_error("Empty attribute name.");
161 const std::string type = tokens.Word();
162 if (type == "INT" || type == "HEX") {
163 definition.type = type == "INT" ? AttributeType::Integer : AttributeType::Hex;
164 definition.integerMinimum = ParseNumber<std::int64_t>(tokens.Word());
165 definition.integerMaximum = ParseNumber<std::int64_t>(tokens.Word());
166 if (definition.integerMinimum > definition.integerMaximum)
167 throw std::runtime_error("Reversed attribute integer range.");
168 } else if (type == "FLOAT") {
169 definition.type = AttributeType::Float;
170 definition.minimum = ParseNumber<double>(tokens.Word());
171 definition.maximum = ParseNumber<double>(tokens.Word());
172 if (definition.minimum > definition.maximum)
173 throw std::runtime_error("Reversed attribute FLOAT range.");
174 } else if (type == "STRING") {
175 definition.type = AttributeType::String;
176 } else if (type == "ENUM") {
177 definition.type = AttributeType::Enum;
178 std::set<std::string> labels;
179 for (;;) {
180 const std::string label = tokens.Quoted();
181 if (!labels.insert(label).second)
182 throw std::runtime_error("Duplicate attribute ENUM label.");
183 definition.enumValues.push_back(label);
184 if (tokens.Empty()) break;
185 const Token separator = tokens.Take();
186 if (separator.quoted || separator.text != ",") throw std::runtime_error("Expected ENUM comma.");
187 }
188 definition.integerMaximum = static_cast<std::int64_t>(definition.enumValues.size() - 1U);
189 } else {
190 throw std::runtime_error("Unsupported attribute definition type: " + type);
191 }
192 tokens.End();
193 if (name == "VFrameFormat" && (definition.scope != AttributeScope::Frame || definition.type != AttributeType::Enum))
194 throw std::runtime_error("VFrameFormat requires a BO_ ENUM definition.");
195 if (name == "GenMsgCycleTime" && (definition.scope != AttributeScope::Frame ||
196 (definition.type != AttributeType::Integer && definition.type != AttributeType::Hex)))
197 throw std::runtime_error("GenMsgCycleTime requires a BO_ integer definition.");
198 if (!database.attributeDefinitions.emplace(name, definition).second)
199 throw std::runtime_error("Duplicate attribute definition: " + name);
200 }
201
202 AttributeDefinition &FindDefinition(DbcDatabase &database, const std::string &name) {
203 const std::map<std::string, AttributeDefinition>::iterator found = database.attributeDefinitions.find(name);
204 if (found == database.attributeDefinitions.end())
205 throw std::runtime_error("Undefined attribute: " + name);
206 return found->second;
207 }
208
209 void ParseId(const std::string &text, AttributeAssignment &assignment) {
210 const std::int64_t raw = ParseNumber<std::int64_t>(text);
211 if (raw < 0 || raw > std::numeric_limits<std::uint32_t>::max() || (raw & 0x60000000U) != 0)
212 throw std::runtime_error("Invalid attribute CAN ID.");
213 const std::uint32_t id = static_cast<std::uint32_t>(raw);
214 assignment.canId = id & 0x1fffffffU;
215 assignment.isExtended = (id & 0x80000000U) != 0U || id > 0x7ffU;
216 }
217
218 AttributeMap &Target(DbcDatabase &database, const AttributeAssignment &assignment) {
219 if (assignment.scope == AttributeScope::Database) return database.attributes;
220 for (FrameInfo &frame: database.frames) {
221 if (frame.canId != assignment.canId || frame.isExtended != assignment.isExtended) continue;
222 if (assignment.scope == AttributeScope::Frame) return frame.attributes;
223 for (SignalInfo &signal: frame.signals) {
224 if (signal.name == assignment.targetName) return signal.attributes;
225 }
226 throw std::runtime_error("Unresolved attribute signal: " + assignment.targetName);
227 }
228 throw std::runtime_error("Unresolved attribute frame.");
229 }
230
231 void ValidateCycleTime(const std::string &name, const AttributeValue &value) {
232 if (name != "GenMsgCycleTime") return;
233 const std::int64_t milliseconds = std::get<std::int64_t>(value);
234 if (milliseconds < 0 || milliseconds > std::numeric_limits<std::uint32_t>::max())
235 throw std::runtime_error("GenMsgCycleTime must fit unsigned 32-bit milliseconds.");
236 }
237
238 void ApplyDefault(DbcDatabase &database, const std::string &name, const AttributeDefinition &definition) {
239 if (!definition.defaultValue) return;
240 if (definition.scope == AttributeScope::Database) {
241 database.attributes[name] = *definition.defaultValue;
242 return;
243 }
244 if (definition.scope != AttributeScope::Frame && definition.scope != AttributeScope::Signal)
245 return;
246 for (FrameInfo &frame: database.frames) {
247 if (definition.scope == AttributeScope::Frame) frame.attributes[name] = *definition.defaultValue;
248 if (definition.scope == AttributeScope::Signal) {
249 for (SignalInfo &signal: frame.signals) signal.attributes[name] = *definition.defaultValue;
250 }
251 }
252 }
253}// namespace
254
255void DbcAttributeHandler::Handle(const std::string &line, DbcParseContext &context) const {
256 context.pendingAttributes.push_back({line, context.lineNumber});
257}
258
259void DbcParseContext::ResolveAttributes(const std::string &filePath, std::vector<DbcDiagnostic> &diagnostics) {
260 std::map<std::string, std::size_t> defaultLines;
261 std::vector<std::size_t> assignmentLines;
262 // Resolve each phase independently of source ordering.
263 const char *keywords[] = {"BA_DEF_", "BA_DEF_DEF_", "BA_"};
264 for (const char *phase: keywords) {
265 for (const PendingAttribute &pending: pendingAttributes) {
266 const std::string keyword = pending.statement.substr(0U, pending.statement.find_first_of(" \t\r\n"));
267 if (keyword != phase) continue;
268 try {
269 AttributeTokens tokens(pending.statement);
270 tokens.Word();
271 if (keyword == "BA_DEF_") {
272 Define(tokens, database);
273 continue;
274 }
275 const std::string name = tokens.Quoted();
276 AttributeDefinition &definition = FindDefinition(database, name);
277 if (keyword == "BA_DEF_DEF_") {
278 if (definition.defaultValue) throw std::runtime_error("Duplicate attribute default: " + name);
279 definition.defaultValue = Value(tokens.Take(), definition, true);
280 ValidateCycleTime(name, *definition.defaultValue);
281 tokens.End();
282 defaultLines[name] = pending.lineNumber;
283 ApplyDefault(database, name, definition);
284 continue;
285 }
286 AttributeAssignment assignment;
287 assignment.name = name;
288 Token value = tokens.Take();
289 if (!value.quoted && IsScope(value.text)) {
290 assignment.scope = ParseScope(value.text);
291 if (assignment.scope == AttributeScope::Frame || assignment.scope == AttributeScope::Signal)
292 ParseId(tokens.Word(), assignment);
293 if (assignment.scope != AttributeScope::Frame)
294 assignment.targetName = tokens.Word();
295 value = tokens.Take();
296 }
297 if (assignment.scope != definition.scope) throw std::runtime_error("Attribute assignment scope differs from definition.");
298 assignment.value = Value(value, definition, false);
299 ValidateCycleTime(name, assignment.value);
300 tokens.End();
301 // Repeated explicit assignments use the last value, matching comments.
302 if (assignment.scope != AttributeScope::Node && assignment.scope != AttributeScope::Environment)
303 Target(database, assignment)[name] = assignment.value;
304 database.attributeAssignments.push_back(assignment);
305 assignmentLines.push_back(pending.lineNumber);
306 } catch (const std::exception &error) {
307 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidAttribute, filePath, pending.lineNumber, error.what());
308 }
309 }
310 }
311 for (FrameInfo &frame: database.frames) {
312 for (const AttributeMap::value_type &attribute: frame.attributes) {
313 if (attribute.first != "VFrameFormat" && attribute.first != "GenMsgCycleTime") continue;
314 std::size_t line = defaultLines[attribute.first];
315 for (std::size_t index = 0U; index < database.attributeAssignments.size(); ++index) {
316 const AttributeAssignment &assignment = database.attributeAssignments[index];
317 if (assignment.name == attribute.first && assignment.scope == AttributeScope::Frame &&
318 assignment.canId == frame.canId && assignment.isExtended == frame.isExtended)
319 line = assignmentLines[index];
320 }
321 const std::int64_t value = std::get<std::int64_t>(attribute.second);
322 if (attribute.first == "GenMsgCycleTime") {
323 frame.cycleTimeMs = static_cast<std::uint32_t>(value);
324 continue;
325 }
326 frame.frameFormat = database.attributeDefinitions.at(attribute.first).enumValues[static_cast<std::size_t>(value)];
327 const std::string &format = *frame.frameFormat;
328 std::string warning;
329 if (format != "StandardCAN" && format != "ExtendedCAN")
330 warning = "Unsupported VFrameFormat '" + format + "' is preserved; decoding may be incomplete.";
331 else if ((format == "ExtendedCAN") != frame.isExtended)
332 warning = "VFrameFormat '" + format + "' conflicts with the BO_ CAN ID; ID interpretation is unchanged.";
333 if (!warning.empty())
334 diagnostics.push_back({DbcDiagnosticSeverity::Warning, DbcDiagnosticCode::UnsupportedAttribute, filePath, line, warning});
335 }
336 }
337}
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Explicit assignment; defaults remain on definitions.
Definition attributes.h:59
std::uint32_t canId
Definition attributes.h:62
std::string targetName
Definition attributes.h:64
AttributeScope scope
Definition attributes.h:61
AttributeValue value
Definition attributes.h:65
std::string name
Definition attributes.h:60
Attribute type, permitted range, and optional default.
Definition attributes.h:42
std::vector< std::string > enumValues
Definition attributes.h:49
std::int64_t integerMaximum
Definition attributes.h:46
AttributeType type
Definition attributes.h:44
AttributeScope scope
Definition attributes.h:43
std::int64_t integerMinimum
Definition attributes.h:45
std::optional< AttributeValue > defaultValue
Definition attributes.h:50
Parsed DBC content stored in a simple internal form.
std::map< std::string, AttributeDefinition > attributeDefinitions
std::vector< FrameInfo > frames
AttributeMap attributes
std::vector< AttributeAssignment > attributeAssignments
void ResolveAttributes(const std::string &filePath, std::vector< DbcDiagnostic > &diagnostics)
Resolve definitions, defaults, then assignments; append format warnings.
std::vector< PendingAttribute > pendingAttributes
Describes one CAN frame from a DBC file.
Definition frame_info.h:25
Attribute statement retained for ordered resolution.
Describes one signal inside a DBC frame.
Definition signal_info.h:25