// SPDX-License-Identifier: MIT #include "dbc_statement_handler.h" #include #include #include #include namespace { struct Token { std::string text; bool quoted = false; }; std::vector Tokenize(const std::string &text) { std::vector tokens; std::size_t index = 0U; while (index < text.size()) { const char character = text[index++]; if (character == ' ' || character == '\t') continue; Token token; if (character == '"') { token.quoted = true; bool closed = false; while (index < text.size()) { char next = text[index++]; if (next == '"') { closed = true; break; } if (next == '\\') { if (index == text.size()) throw std::runtime_error("Incomplete attribute escape."); next = text[index++]; if (next != '"' && next != '\\') throw std::runtime_error("Invalid attribute escape."); } token.text += next; } if (!closed) throw std::runtime_error("Unterminated attribute string."); if (index < text.size() && text[index] != ' ' && text[index] != '\t' && text[index] != ',' && text[index] != ';') throw std::runtime_error("Missing separator after attribute string."); } else if (character == ';' || character == ',') { token.text = character; } else { token.text = character; while (index < text.size() && text[index] != ' ' && text[index] != '\t' && text[index] != ';' && text[index] != ',') { if (text[index] == '"') throw std::runtime_error("Missing separator before attribute string."); token.text += text[index++]; } } tokens.push_back(token); } if (tokens.empty() || tokens.back().quoted || tokens.back().text != ";") throw std::runtime_error("Attribute statement requires a semicolon."); tokens.pop_back(); return tokens; } class AttributeTokens { public: explicit AttributeTokens(const std::string &line) : tokens(Tokenize(line)) {} Token Take() { if (position == tokens.size()) throw std::runtime_error("Incomplete attribute statement."); return tokens[position++]; } std::string Word() { const Token token = Take(); if (token.quoted || token.text == "," || token.text == ";") throw std::runtime_error("Expected an attribute keyword or number."); return token.text; } std::string Quoted() { const Token token = Take(); if (!token.quoted) throw std::runtime_error("Expected a quoted attribute name or string."); return token.text; } bool Empty() const { return position == tokens.size(); } void End() const { if (!Empty()) throw std::runtime_error("Unexpected trailing attribute fields."); } private: std::vector tokens; std::size_t position = 0U; }; bool IsScope(const std::string &text) { return text == "BO_" || text == "SG_" || text == "BU_" || text == "EV_"; } AttributeScope ParseScope(const std::string &text) { if (text == "BO_") return AttributeScope::Frame; if (text == "SG_") return AttributeScope::Signal; if (text == "BU_") return AttributeScope::Node; if (text == "EV_") return AttributeScope::Environment; throw std::runtime_error("Unsupported attribute scope: " + text); } std::int64_t Integer(const std::string &text) { const char *begin = text.data(); const char *end = begin + text.size(); if (begin != end && *begin == '+') { ++begin; if (begin != end && (*begin == '-' || *begin == '+')) throw std::runtime_error("Invalid attribute number: " + text); } std::int64_t value = 0; const std::from_chars_result parsed = std::from_chars(begin, end, value); if (begin == end || parsed.ec != std::errc() || parsed.ptr != end) throw std::runtime_error("Invalid attribute integer: " + text); return value; } double Number(const std::string &text) { const char *begin = text.data(); const char *end = begin + text.size(); if (begin != end && *begin == '+') { ++begin; if (begin != end && (*begin == '-' || *begin == '+')) throw std::runtime_error("Invalid attribute number: " + text); } double value = 0.0; const std::from_chars_result parsed = std::from_chars(begin, end, value); if (begin == end || parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value)) throw std::runtime_error("Invalid attribute number: " + text); return value; } AttributeValue Value(const Token &token, const AttributeDefinition &definition, bool isDefault) { if (definition.type == AttributeType::String) { if (!token.quoted) throw std::runtime_error("Attribute STRING requires quoted text."); return token.text; } if (definition.type == AttributeType::Enum && isDefault && token.quoted) { for (std::size_t index = 0U; index < definition.enumValues.size(); ++index) { if (definition.enumValues[index] == token.text) return static_cast(index); } throw std::runtime_error("Unknown attribute ENUM default label."); } if (token.quoted) throw std::runtime_error("Numeric attribute requires an unquoted number."); if (definition.type == AttributeType::Float) { const double value = Number(token.text); if (value < definition.minimum || value > definition.maximum) throw std::runtime_error("Attribute FLOAT value outside definition range."); return value; } const std::int64_t value = Integer(token.text); if (value < definition.integerMinimum || value > definition.integerMaximum) throw std::runtime_error("Attribute integer/ENUM value outside definition range."); return value; } void Define(AttributeTokens &tokens, DbcDatabase &database) { AttributeDefinition definition; const Token first = tokens.Take(); std::string name = first.text; if (!first.quoted) { definition.scope = ParseScope(first.text); name = tokens.Quoted(); } if (name.empty()) throw std::runtime_error("Empty attribute name."); const std::string type = tokens.Word(); if (type == "INT" || type == "HEX") { definition.type = type == "INT" ? AttributeType::Integer : AttributeType::Hex; definition.integerMinimum = Integer(tokens.Word()); definition.integerMaximum = Integer(tokens.Word()); if (definition.integerMinimum > definition.integerMaximum) throw std::runtime_error("Reversed attribute integer range."); } else if (type == "FLOAT") { definition.type = AttributeType::Float; definition.minimum = Number(tokens.Word()); definition.maximum = Number(tokens.Word()); if (definition.minimum > definition.maximum) throw std::runtime_error("Reversed attribute FLOAT range."); } else if (type == "STRING") { definition.type = AttributeType::String; } else if (type == "ENUM") { definition.type = AttributeType::Enum; std::set labels; for (;;) { const std::string label = tokens.Quoted(); if (!labels.insert(label).second) throw std::runtime_error("Duplicate attribute ENUM label."); definition.enumValues.push_back(label); if (tokens.Empty()) break; const Token separator = tokens.Take(); if (separator.quoted || separator.text != ",") throw std::runtime_error("Expected ENUM comma."); } definition.integerMaximum = static_cast(definition.enumValues.size() - 1U); } else { throw std::runtime_error("Unsupported attribute definition type: " + type); } tokens.End(); if (name == "VFrameFormat" && (definition.scope != AttributeScope::Frame || definition.type != AttributeType::Enum)) throw std::runtime_error("VFrameFormat requires a BO_ ENUM definition."); if (name == "GenMsgCycleTime" && (definition.scope != AttributeScope::Frame || (definition.type != AttributeType::Integer && definition.type != AttributeType::Hex))) throw std::runtime_error("GenMsgCycleTime requires a BO_ integer definition."); if (!database.attributeDefinitions.emplace(name, definition).second) throw std::runtime_error("Duplicate attribute definition: " + name); } AttributeDefinition &FindDefinition(DbcDatabase &database, const std::string &name) { const std::map::iterator found = database.attributeDefinitions.find(name); if (found == database.attributeDefinitions.end()) throw std::runtime_error("Undefined attribute: " + name); return found->second; } void ParseId(const std::string &text, AttributeAssignment &assignment) { const std::int64_t raw = Integer(text); if (raw < 0 || raw > std::numeric_limits::max() || (raw & 0x60000000U) != 0) throw std::runtime_error("Invalid attribute CAN ID."); const std::uint32_t id = static_cast(raw); assignment.canId = id & 0x1fffffffU; assignment.isExtended = (id & 0x80000000U) != 0U || id > 0x7ffU; } AttributeMap &Target(DbcDatabase &database, const AttributeAssignment &assignment) { if (assignment.scope == AttributeScope::Database) return database.attributes; for (FrameInfo &frame: database.frames) { if (frame.canId != assignment.canId || frame.isExtended != assignment.isExtended) continue; if (assignment.scope == AttributeScope::Frame) return frame.attributes; for (SignalInfo &signal: frame.signals) { if (signal.name == assignment.targetName) return signal.attributes; } throw std::runtime_error("Unresolved attribute signal: " + assignment.targetName); } throw std::runtime_error("Unresolved attribute frame."); } void ValidateCycleTime(const std::string &name, const AttributeValue &value) { if (name != "GenMsgCycleTime") return; const std::int64_t milliseconds = std::get(value); if (milliseconds < 0 || milliseconds > std::numeric_limits::max()) throw std::runtime_error("GenMsgCycleTime must fit unsigned 32-bit milliseconds."); } void ApplyDefault(DbcDatabase &database, const std::string &name, const AttributeDefinition &definition) { if (!definition.defaultValue) return; if (definition.scope == AttributeScope::Database) database.attributes[name] = *definition.defaultValue; for (FrameInfo &frame: database.frames) { if (definition.scope == AttributeScope::Frame) frame.attributes[name] = *definition.defaultValue; if (definition.scope == AttributeScope::Signal) { for (SignalInfo &signal: frame.signals) signal.attributes[name] = *definition.defaultValue; } } } }// namespace void DbcAttributeHandler::Handle(const std::string &line, DbcParseContext &context) const { context.pendingAttributes.push_back({line, context.lineNumber}); } void DbcParseContext::ResolveAttributes(const std::string &filePath, std::vector &diagnostics) { std::map defaultLines; std::vector assignmentLines; // Resolve each phase independently of source ordering. const char *keywords[] = {"BA_DEF_", "BA_DEF_DEF_", "BA_"}; for (int phase = 0; phase < 3; ++phase) { for (const PendingAttribute &pending: pendingAttributes) { const std::string keyword = pending.statement.substr(0U, pending.statement.find_first_of(" \t")); if (keyword != keywords[phase]) continue; try { AttributeTokens tokens(pending.statement); tokens.Word(); if (phase == 0) { Define(tokens, database); continue; } const std::string name = tokens.Quoted(); AttributeDefinition &definition = FindDefinition(database, name); if (phase == 1) { if (definition.defaultValue) throw std::runtime_error("Duplicate attribute default: " + name); definition.defaultValue = Value(tokens.Take(), definition, true); ValidateCycleTime(name, *definition.defaultValue); tokens.End(); defaultLines[name] = pending.lineNumber; ApplyDefault(database, name, definition); continue; } AttributeAssignment assignment; assignment.name = name; Token value = tokens.Take(); if (!value.quoted && IsScope(value.text)) { assignment.scope = ParseScope(value.text); if (assignment.scope == AttributeScope::Frame || assignment.scope == AttributeScope::Signal) ParseId(tokens.Word(), assignment); if (assignment.scope != AttributeScope::Frame) assignment.targetName = tokens.Word(); value = tokens.Take(); } if (assignment.scope != definition.scope) throw std::runtime_error("Attribute assignment scope differs from definition."); assignment.value = Value(value, definition, false); ValidateCycleTime(name, assignment.value); tokens.End(); // Repeated explicit assignments use the last value, matching comments. if (assignment.scope != AttributeScope::Node && assignment.scope != AttributeScope::Environment) Target(database, assignment)[name] = assignment.value; database.attributeAssignments.push_back(assignment); assignmentLines.push_back(pending.lineNumber); } catch (const std::exception &error) { throw DbcDiagnosticError(DbcDiagnosticCode::InvalidAttribute, filePath, pending.lineNumber, error.what()); } } } for (FrameInfo &frame: database.frames) { for (const AttributeMap::value_type &attribute: frame.attributes) { if (attribute.first != "VFrameFormat" && attribute.first != "GenMsgCycleTime") continue; std::size_t line = defaultLines[attribute.first]; for (std::size_t index = 0U; index < database.attributeAssignments.size(); ++index) { const AttributeAssignment &assignment = database.attributeAssignments[index]; if (assignment.name == attribute.first && assignment.scope == AttributeScope::Frame && assignment.canId == frame.canId && assignment.isExtended == frame.isExtended) line = assignmentLines[index]; } const std::int64_t value = std::get(attribute.second); if (attribute.first == "GenMsgCycleTime") { frame.cycleTimeMs = static_cast(value); continue; } frame.frameFormat = database.attributeDefinitions.at(attribute.first).enumValues[static_cast(value)]; const std::string &format = *frame.frameFormat; std::string warning; if (format != "StandardCAN" && format != "ExtendedCAN") warning = "Unsupported VFrameFormat '" + format + "' is preserved; decoding may be incomplete."; else if ((format == "ExtendedCAN") != frame.isExtended) warning = "VFrameFormat '" + format + "' conflicts with the BO_ CAN ID; ID interpretation is unchanged."; if (!warning.empty()) diagnostics.push_back({DbcDiagnosticSeverity::Warning, DbcDiagnosticCode::UnsupportedAttribute, filePath, line, warning}); } } }