2#include "dbc_statement_handler.h"
14 bool IsWhitespace(
char character) {
15 return character ==
' ' || character ==
'\t' || character ==
'\n' || character ==
'\r';
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))
26 if (character ==
'"') {
29 while (index < text.size()) {
30 char next = text[index++];
36 if (index == text.size())
37 throw std::runtime_error(
"Incomplete attribute escape.");
39 if (next !=
'"' && next !=
'\\')
40 throw std::runtime_error(
"Invalid attribute escape.");
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;
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++];
60 tokens.push_back(token);
62 if (tokens.empty() || tokens.back().quoted || tokens.back().text !=
";")
63 throw std::runtime_error(
"Attribute statement requires a semicolon.");
68 class AttributeTokens {
70 explicit AttributeTokens(
const std::string &line) : tokens(Tokenize(line)) {}
72 if (position == tokens.size())
73 throw std::runtime_error(
"Incomplete attribute statement.");
74 return tokens[position++];
77 const Token token = Take();
78 if (token.quoted || token.text ==
"," || token.text ==
";")
79 throw std::runtime_error(
"Expected an attribute keyword or number.");
82 std::string Quoted() {
83 const Token token = Take();
85 throw std::runtime_error(
"Expected a quoted attribute name or string.");
88 bool Empty()
const {
return position == tokens.size(); }
91 throw std::runtime_error(
"Unexpected trailing attribute fields.");
95 std::vector<Token> tokens;
96 std::size_t position = 0U;
99 bool IsScope(
const std::string &text) {
100 return text ==
"BO_" || text ==
"SG_" || text ==
"BU_" || text ==
"EV_";
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);
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 ==
'+') {
117 if (begin != end && (*begin ==
'-' || *begin ==
'+'))
118 throw std::runtime_error(
"Invalid attribute number: " + text);
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);
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.");
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);
137 throw std::runtime_error(
"Unknown attribute ENUM default label.");
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.");
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.");
152 void Define(AttributeTokens &tokens,
DbcDatabase &database) {
154 const Token first = tokens.Take();
155 std::string name = first.text;
157 definition.
scope = ParseScope(first.text);
158 name = tokens.Quoted();
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());
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());
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;
180 const std::string label = tokens.Quoted();
181 if (!labels.insert(label).second)
182 throw std::runtime_error(
"Duplicate attribute ENUM 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.");
190 throw std::runtime_error(
"Unsupported attribute definition type: " + type);
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.");
199 throw std::runtime_error(
"Duplicate attribute definition: " + name);
203 const std::map<std::string, AttributeDefinition>::iterator found = database.
attributeDefinitions.find(name);
205 throw std::runtime_error(
"Undefined attribute: " + name);
206 return found->second;
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;
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;
224 if (signal.name == assignment.
targetName)
return signal.attributes;
226 throw std::runtime_error(
"Unresolved attribute signal: " + assignment.
targetName);
228 throw std::runtime_error(
"Unresolved attribute frame.");
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.");
240 if (definition.
scope == AttributeScope::Database) {
244 if (definition.
scope != AttributeScope::Frame && definition.
scope != AttributeScope::Signal)
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;
260 std::map<std::string, std::size_t> defaultLines;
261 std::vector<std::size_t> assignmentLines;
263 const char *keywords[] = {
"BA_DEF_",
"BA_DEF_DEF_",
"BA_"};
264 for (
const char *phase: keywords) {
266 const std::string keyword = pending.statement.substr(0U, pending.statement.find_first_of(
" \t\r\n"));
267 if (keyword != phase)
continue;
269 AttributeTokens tokens(pending.statement);
271 if (keyword ==
"BA_DEF_") {
275 const std::string name = tokens.Quoted();
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);
282 defaultLines[name] = pending.lineNumber;
283 ApplyDefault(
database, name, definition);
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)
295 value = tokens.Take();
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);
302 if (assignment.
scope != AttributeScope::Node && assignment.
scope != AttributeScope::Environment)
305 assignmentLines.push_back(pending.lineNumber);
306 }
catch (
const std::exception &error) {
307 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidAttribute, filePath, pending.lineNumber, error.what());
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];
317 if (assignment.
name == attribute.first && assignment.
scope == AttributeScope::Frame &&
318 assignment.
canId == frame.canId && assignment.
isExtended == frame.isExtended)
319 line = assignmentLines[index];
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);
327 const std::string &format = *frame.frameFormat;
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});
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.
Attribute type, permitted range, and optional default.
std::vector< std::string > enumValues
std::int64_t integerMaximum
std::int64_t integerMinimum
std::optional< AttributeValue > defaultValue
Parsed DBC content stored in a simple internal form.
std::map< std::string, AttributeDefinition > attributeDefinitions
std::vector< FrameInfo > frames
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.
Attribute statement retained for ordered resolution.
Describes one signal inside a DBC frame.