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