Closes #25
This commit is contained in:
1 parent
e555ebb571
commit
849ce31e2f
17 files changed
+651
-11
No files matched your search
@@ -647,11 +647,52 @@ retain their original source lines. Invalid signal markers use `InvalidStatement
|
||||
`HasErrors()` checks severity; warnings alone do not make a parse fail.
|
||||
|
||||
Unknown statements are reported conservatively, including `SIG_VALTYPE_`,
|
||||
attributes (`BA_`, `BA_DEF_`, defaults and relation forms), signal types/references,
|
||||
attribute relation forms (`BA_REL_`, `BA_DEF_REL_`), signal types/references,
|
||||
environment variables, named `VAL_TABLE_` tables, and vendor extensions. For
|
||||
example, ignoring `SIG_VALTYPE_` may decode a floating-point signal as an integer.
|
||||
The parser does not implement the warned features; consumers should review warnings.
|
||||
|
||||
### Attributes and frame metadata
|
||||
|
||||
`BA_DEF_`, `BA_DEF_DEF_`, and `BA_` support database, `BO_`, `SG_`,
|
||||
`BU_`, and `EV_` scopes with `INT`, `HEX`, `FLOAT`, `STRING`, and `ENUM`
|
||||
definitions. Integer/HEX values use decimal signed 64-bit numbers; floating
|
||||
values are finite and locale independent. ENUM assignments use zero-based indices;
|
||||
defaults accept an index or a quoted label. Strings support escaped quotes and
|
||||
backslashes. Each statement occupies one physical line and ends with `;`.
|
||||
|
||||
```dbc
|
||||
BA_DEF_ BO_ "VFrameFormat" ENUM "StandardCAN","ExtendedCAN","StandardCAN_FD";
|
||||
BA_DEF_DEF_ "VFrameFormat" "StandardCAN";
|
||||
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;
|
||||
BA_DEF_DEF_ "GenMsgCycleTime" 100;
|
||||
BA_ "GenMsgCycleTime" BO_ 256 20;
|
||||
```
|
||||
|
||||
Definitions, defaults, assignments, and frame/signal declarations may appear in
|
||||
any order. Defaults apply to every object of the defined scope; explicit values
|
||||
override them, with the last repeated assignment winning. Duplicate definitions
|
||||
or defaults, undefined names, wrong types/scopes, out-of-range values, and missing
|
||||
frame/signal targets produce `Error / InvalidAttribute` at the source line.
|
||||
|
||||
`DbcDatabase::attributeDefinitions` retains types, ranges, enum labels, and defaults;
|
||||
`attributeAssignments` retains explicit values and targets. Effective values are
|
||||
available in `attributes` on the database, frames and signals. Node/environment
|
||||
assignments are retained by name without checking declarations; their defaults
|
||||
remain on the definitions. Frame/signal attributes are copied into runtime
|
||||
snapshots and tree payloads. Other valid attributes remain available as metadata
|
||||
without changing decoding.
|
||||
|
||||
`FrameInfo` and `DecodeFrame` expose optional `frameFormat` (the enum label) and
|
||||
`cycleTimeMs` (`GenMsgCycleTime`, unsigned 32-bit milliseconds). Missing values
|
||||
remain unset; zero cycle time means unspecified. `StandardCAN` and `ExtendedCAN`
|
||||
are recognized. Other format labels, including CAN FD and `J1939PG`, are preserved
|
||||
with `Warning / UnsupportedAttribute` when effective on a frame. A supported
|
||||
format conflicting with the encoded `BO_` ID also warns. Warnings identify the
|
||||
assignment or default line. Attributes do not change CAN ID normalization or
|
||||
automatically enable CAN FD or J1939 decoding; J1939 still requires parser opt-in.
|
||||
Use `ParseFileWithDiagnostics()` to review these warnings.
|
||||
|
||||
Common `VERSION`, `NS_`, `BS_`, and `BU_` headers produce no diagnostics.
|
||||
Single-token symbols inside the `NS_` declaration list are quiet regardless of
|
||||
indentation, even when they name unsupported syntax. Frame, signal, bus, and
|
||||
@@ -1207,8 +1248,7 @@ Not supported yet:
|
||||
|
||||
- extended and nested multiplexing (`SG_MUL_VAL_`, `mNM`)
|
||||
- named `VAL_TABLE_` tables and environment-variable value descriptions
|
||||
- `BA_` attributes
|
||||
- `BA_DEF_` definitions and attribute defaults
|
||||
- attribute relation forms (`BA_REL_`, `BA_DEF_REL_`)
|
||||
- `SIG_VALTYPE_` floating-point signal types
|
||||
- signal types/references (`SGTYPE_`, `SIG_TYPE_REF_`) and environment variables
|
||||
- advanced comment handling
|
||||
@@ -1264,7 +1304,7 @@ Recommended additions:
|
||||
Recommended additions:
|
||||
|
||||
- basic multiplexing (implemented)
|
||||
- attribute parsing
|
||||
- attribute parsing (implemented for definitions, defaults and values)
|
||||
- richer J1939 support
|
||||
- CSV export of decoded traces
|
||||
- optimized filtering and signal selection
|
||||
|
||||
@@ -8,6 +8,21 @@ existing Git tag; subsequent changes are listed as unreleased.
|
||||
|
||||
### 2026-10-20
|
||||
|
||||
#### Added
|
||||
|
||||
- Parse typed `BA_DEF_`, `BA_DEF_DEF_`, and `BA_` attributes with deferred
|
||||
definitions/references, validated ranges, defaults, and explicit overrides.
|
||||
Preserve definitions and assignments plus effective database/frame/signal values.
|
||||
- Expose optional frame format and cycle time metadata in parsed frames, runtime
|
||||
snapshots, and tree payloads. Preserve unknown frame format labels with source
|
||||
warnings, and report format/ID conflicts without changing ID or J1939 behavior.
|
||||
- Add attribute syntax, scope, reference, numeric, override, diagnostics, and
|
||||
metadata propagation regressions; document supported forms and limitations.
|
||||
Verified the qmake build, all six QtTest suites, the qmake tree target, and
|
||||
the standalone C++17 tree test. The existing named C locale test skipped
|
||||
because the required locale is not installed.
|
||||
|
||||
|
||||
#### Changed
|
||||
|
||||
- J1939 interpretation now requires `DbcParserOptions::enableJ1939` (default off)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#ifndef DBC_ATTRIBUTES_H
|
||||
#define DBC_ATTRIBUTES_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
enum class AttributeScope {
|
||||
Database,
|
||||
Node,
|
||||
Frame,
|
||||
Signal,
|
||||
Environment
|
||||
};
|
||||
|
||||
enum class AttributeType {
|
||||
Integer,
|
||||
Hex,
|
||||
Float,
|
||||
String,
|
||||
Enum
|
||||
};
|
||||
|
||||
using AttributeValue = std::variant<std::int64_t, double, std::string>;
|
||||
using AttributeMap = std::map<std::string, AttributeValue>;
|
||||
|
||||
struct AttributeDefinition {
|
||||
AttributeScope scope = AttributeScope::Database;
|
||||
AttributeType type = AttributeType::String;
|
||||
std::int64_t integerMinimum = 0;
|
||||
std::int64_t integerMaximum = 0;
|
||||
double minimum = 0.0;
|
||||
double maximum = 0.0;
|
||||
std::vector<std::string> enumValues;
|
||||
std::optional<AttributeValue> defaultValue;
|
||||
};
|
||||
|
||||
/** Explicit assignment; defaults remain on definitions. Named nodes/environment
|
||||
* variables are retained without validating declarations, which are not parsed. */
|
||||
struct AttributeAssignment {
|
||||
std::string name;
|
||||
AttributeScope scope = AttributeScope::Database;
|
||||
std::uint32_t canId = 0U;
|
||||
bool isExtended = false;
|
||||
std::string targetName;
|
||||
AttributeValue value;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
SOURCES += \
|
||||
$$PWD/dbc_attributes.cpp \
|
||||
$$PWD/decode_database.cpp \
|
||||
$$PWD/dbc_decode_builder.cpp \
|
||||
$$PWD/dbc_decoder.cpp \
|
||||
@@ -8,6 +9,7 @@ SOURCES += \
|
||||
$$PWD/tree_node.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/attributes.h \
|
||||
$$PWD/j1939.h \
|
||||
$$PWD/signal_layout.h \
|
||||
$$PWD/definition_key.h \
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#include "dbc_statement_handler.h"
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
struct Token {
|
||||
std::string text;
|
||||
bool quoted = false;
|
||||
};
|
||||
|
||||
std::vector<Token> Tokenize(const std::string &text) {
|
||||
std::vector<Token> 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<Token> 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<std::int64_t>(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<std::string> 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<std::int64_t>(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<std::string, AttributeDefinition>::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<std::uint32_t>::max() || (raw & 0x60000000U) != 0)
|
||||
throw std::runtime_error("Invalid attribute CAN ID.");
|
||||
const std::uint32_t id = static_cast<std::uint32_t>(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<std::int64_t>(value);
|
||||
if (milliseconds < 0 || milliseconds > std::numeric_limits<std::uint32_t>::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<DbcDiagnostic> &diagnostics) {
|
||||
std::map<std::string, std::size_t> defaultLines;
|
||||
std::vector<std::size_t> 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<std::int64_t>(attribute.second);
|
||||
if (attribute.first == "GenMsgCycleTime") {
|
||||
frame.cycleTimeMs = static_cast<std::uint32_t>(value);
|
||||
continue;
|
||||
}
|
||||
frame.frameFormat = database.attributeDefinitions.at(attribute.first).enumValues[static_cast<std::size_t>(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});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@
|
||||
*/
|
||||
struct DbcDatabase {
|
||||
std::vector<FrameInfo> frames; /**< All frames found in the DBC file. */
|
||||
std::map<std::string, AttributeDefinition> attributeDefinitions = {};
|
||||
std::vector<AttributeAssignment> attributeAssignments = {};
|
||||
AttributeMap attributes = {}; /**< Effective database attributes. */
|
||||
};
|
||||
|
||||
#endif /* DBC_DATABASE_H */
|
||||
@@ -19,6 +19,9 @@ DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
|
||||
for (const FrameInfo &sourceFrame: source.frames) {
|
||||
DecodeFrame targetFrame;
|
||||
|
||||
targetFrame.attributes = sourceFrame.attributes;
|
||||
targetFrame.frameFormat = sourceFrame.frameFormat;
|
||||
targetFrame.cycleTimeMs = sourceFrame.cycleTimeMs;
|
||||
targetFrame.name = sourceFrame.name;
|
||||
targetFrame.canId = sourceFrame.canId;
|
||||
targetFrame.isExtended = sourceFrame.isExtended;
|
||||
@@ -32,6 +35,7 @@ DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
|
||||
for (const SignalInfo &sourceSignal: sourceFrame.signals) {
|
||||
DecodeSignal targetSignal;
|
||||
|
||||
targetSignal.attributes = sourceSignal.attributes;
|
||||
targetSignal.multiplexing = sourceSignal.multiplexing;
|
||||
targetSignal.name = sourceSignal.name;
|
||||
targetSignal.startBit = sourceSignal.startBit;
|
||||
|
||||
@@ -19,7 +19,9 @@ enum class DbcDiagnosticCode {
|
||||
InvalidStatement,
|
||||
InvalidValueDescription,
|
||||
InvalidMultiplexing,
|
||||
UnsupportedStatement
|
||||
UnsupportedStatement,
|
||||
InvalidAttribute,
|
||||
UnsupportedAttribute
|
||||
};
|
||||
|
||||
struct DbcDiagnostic {
|
||||
|
||||
@@ -142,6 +142,7 @@ DbcParseResult DbcParser::ParseFileWithDiagnostics(const std::string &filePath)
|
||||
context.ResolveComments(filePath);
|
||||
context.ResolveValueDescriptions(filePath);
|
||||
context.ValidateMultiplexing(filePath);
|
||||
context.ResolveAttributes(filePath, result.diagnostics);
|
||||
} catch (const DbcDiagnosticError &error) {
|
||||
result.diagnostics.push_back(error.diagnostic);
|
||||
return result;
|
||||
|
||||
@@ -31,6 +31,7 @@ struct DbcParserOptions {
|
||||
* - CM_ SG_
|
||||
* - basic M / mN multiplexing
|
||||
* - VAL_ signal value descriptions
|
||||
* - BA_DEF_, BA_DEF_DEF_, BA_ typed attributes, frame format and cycle time
|
||||
*
|
||||
* Frame/signal comments support multiline quoted text and deferred references.
|
||||
*
|
||||
|
||||
@@ -385,6 +385,9 @@ DbcStatementDispatcher::DbcStatementDispatcher() {
|
||||
RegisterType<DbcSignalHandler>("SG_");
|
||||
RegisterType<DbcCommentHandler>("CM_");
|
||||
RegisterType<DbcValueDescriptionHandler>("VAL_");
|
||||
RegisterType<DbcAttributeHandler>("BA_DEF_");
|
||||
RegisterType<DbcAttributeHandler>("BA_DEF_DEF_");
|
||||
RegisterType<DbcAttributeHandler>("BA_");
|
||||
}
|
||||
|
||||
void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
|
||||
|
||||
@@ -37,6 +37,11 @@ public:
|
||||
DbcDiagnostic diagnostic;
|
||||
};
|
||||
|
||||
struct PendingAttribute {
|
||||
std::string statement;
|
||||
std::size_t lineNumber;
|
||||
};
|
||||
|
||||
/** Mutable state belonging to one file parse. Handlers do not retain it. */
|
||||
struct DbcParseContext {
|
||||
DbcDatabase &database;
|
||||
@@ -51,6 +56,9 @@ struct DbcParseContext {
|
||||
std::vector<PendingComment> pendingComments = {};
|
||||
void ResolveComments(const std::string &filePath);
|
||||
|
||||
std::vector<PendingAttribute> pendingAttributes = {};
|
||||
void ResolveAttributes(const std::string &filePath, std::vector<DbcDiagnostic> &diagnostics);
|
||||
|
||||
void ResolveValueDescriptions(const std::string &filePath);
|
||||
};
|
||||
|
||||
@@ -79,7 +87,11 @@ private:
|
||||
class DbcCommentHandler : public DbcStatementHandler {
|
||||
public:
|
||||
void Handle(const std::string &line, DbcParseContext &context) const override;
|
||||
};
|
||||
|
||||
class DbcAttributeHandler : public DbcStatementHandler {
|
||||
public:
|
||||
void Handle(const std::string &line, DbcParseContext &context) const override;
|
||||
};
|
||||
|
||||
class DbcValueDescriptionHandler : public DbcStatementHandler {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#ifndef DECODE_DATABASE_H
|
||||
#define DECODE_DATABASE_H
|
||||
|
||||
#include "attributes.h"
|
||||
#include "definition_key.h"
|
||||
#include "multiplexing.h"
|
||||
#include "value_descriptions.h"
|
||||
@@ -56,6 +57,8 @@ struct DecodeSignal {
|
||||
ValueDescriptions valueDescriptions; /**< Raw integer state descriptions. */
|
||||
std::string comment; /**< Comment. */
|
||||
|
||||
AttributeMap attributes;
|
||||
|
||||
DecodeSignal() = default;
|
||||
};
|
||||
|
||||
@@ -73,6 +76,10 @@ struct DecodeFrame {
|
||||
std::string comment; /**< Frame comment. */
|
||||
std::vector<DecodeSignal> signals; /**< Signal definitions. */
|
||||
|
||||
AttributeMap attributes;
|
||||
std::optional<std::string> frameFormat;
|
||||
std::optional<std::uint32_t> cycleTimeMs;
|
||||
|
||||
DecodeFrame() = default;
|
||||
|
||||
FrameKey GetKey() const {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#ifndef FRAME_INFO_H
|
||||
#define FRAME_INFO_H
|
||||
|
||||
#include "attributes.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -32,6 +33,11 @@ struct FrameInfo {
|
||||
std::string comment; /**< Optional frame comment. */
|
||||
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
|
||||
|
||||
std::optional<std::string> frameFormat; /**< VFrameFormat enum label. */
|
||||
std::optional<std::uint32_t> cycleTimeMs; /**< GenMsgCycleTime; zero means unspecified. */
|
||||
|
||||
AttributeMap attributes; /**< Effective values, including defaults. */
|
||||
|
||||
FrameInfo() = default;
|
||||
};
|
||||
|
||||
|
||||
+4
-1
@@ -12,8 +12,9 @@
|
||||
#ifndef SIGNAL_INFO_H
|
||||
#define SIGNAL_INFO_H
|
||||
|
||||
#include "value_descriptions.h"
|
||||
#include "attributes.h"
|
||||
#include "multiplexing.h"
|
||||
#include "value_descriptions.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -37,6 +38,8 @@ struct SignalInfo {
|
||||
ValueDescriptions valueDescriptions; /**< Raw integer state descriptions. */
|
||||
std::string comment; /**< Optional signal comment. */
|
||||
|
||||
AttributeMap attributes; /**< Effective values, including defaults. */
|
||||
|
||||
SignalInfo() = default;
|
||||
};
|
||||
|
||||
|
||||
+18
-1
@@ -66,7 +66,7 @@ line numbers for skipped syntax and immediate/deferred errors; warnings precedin
|
||||
an error are retained, and failed parses return an empty database. Common headers
|
||||
and namespace symbol lists stay quiet. Tests also check file-open errors, fresh
|
||||
parse sessions, and compatibility with the legacy throwing API.
|
||||
The diagnostic audit also covers 15 unsupported-statement variants, all namespace
|
||||
The diagnostic audit also covers 13 unsupported-statement variants, all namespace
|
||||
termination forms, CRLF and missing final newlines, relative paths with spaces,
|
||||
first-error stopping, deferred diagnostic ordering, duplicate definitions/values,
|
||||
selector ranges, overlaps, and preservation of supported decoding and metadata.
|
||||
@@ -220,3 +220,20 @@ Empty and headers-only fixtures produce an empty runtime database and a valid
|
||||
empty tree root. Replacement fixtures change definitions, metadata and decoded
|
||||
values, remove old lookup keys, and then clear both outputs through an empty
|
||||
fixture. Saved decoded results and trees retain their original metadata.
|
||||
|
||||
## Attribute coverage
|
||||
|
||||
Parser slots `attributes`, `invalidAttributes`, and `attributeWarnings` cover
|
||||
all five scopes and types, deferred definitions/defaults/targets, explicit
|
||||
values overriding defaults, repeated assignments, escaped strings, typed
|
||||
range/scope errors, duplicate definitions/defaults, unknown attributes and
|
||||
missing targets. They check source diagnostics and the throwing API, distinct
|
||||
standard/extended targets, frame cycle times and enum labels, unsupported
|
||||
formats and ID conflicts, unchanged J1939 opt-in, and runtime/tree propagation.
|
||||
Existing unsupported-statement checks now use relation forms, which remain
|
||||
unsupported, instead of supported attribute forms.
|
||||
|
||||
Attribute changes were verified with `qmake6 ../dbc.pro`, `make -j4`, and
|
||||
`make check` in `build/`: all six QtTest suites and the qmake tree target
|
||||
passed. The standalone C++17 tree test also passed. The existing named
|
||||
comma-decimal C locale test skipped because the locale is not installed.
|
||||
+133
-4
@@ -3,6 +3,7 @@
|
||||
#include "dbc_parser.h"
|
||||
#include "dbc_statement_handler.h"
|
||||
#include "dbc_tree_builder.h"
|
||||
#include <algorithm>
|
||||
#include <clocale>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
@@ -87,6 +88,9 @@ private:
|
||||
}
|
||||
}
|
||||
private Q_SLOTS:
|
||||
void attributes();
|
||||
void invalidAttributes();
|
||||
void attributeWarnings();
|
||||
void diagnostics();
|
||||
void diagnosticErrors();
|
||||
void diagnosticUnsupportedStatements();
|
||||
@@ -113,6 +117,133 @@ private Q_SLOTS:
|
||||
void invalidValueDescriptions();
|
||||
};
|
||||
|
||||
void DbcParserTest::attributes() {
|
||||
Fixture fixture;
|
||||
fixture.Write(
|
||||
"BA_ \"GenMsgCycleTime\" BO_ 1 25;\n"
|
||||
"BA_ \"VFrameFormat\" BO_ 2147483649 1;\n"
|
||||
"BA_ \"Label\" SG_ 1 Value \"a\\\"b\\\\c\";\n"
|
||||
"BA_ \"NodeLabel\" BU_ ECU \"node\";\n"
|
||||
"BA_ \"Environment\" EV_ Env 2;\n"
|
||||
"BA_DEF_DEF_ \"GenMsgCycleTime\" 100;\n"
|
||||
"BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN\";\n"
|
||||
"BA_DEF_DEF_ \"Label\" \"default\";\n"
|
||||
"BA_DEF_DEF_ \"BusLabel\" \"CAN\";\n"
|
||||
"BA_DEF_DEF_ \"Ratio\" 1.5e-1;\n"
|
||||
"BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 10000;\n"
|
||||
"BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"StandardCAN_FD\";\n"
|
||||
"BA_DEF_ SG_ \"Label\" STRING;\n"
|
||||
"BA_DEF_ \"BusLabel\" STRING;\n"
|
||||
"BA_DEF_ BO_ \"Ratio\" FLOAT -1 1;\n"
|
||||
"BA_DEF_ BU_ \"NodeLabel\" STRING;\n"
|
||||
"BA_DEF_ EV_ \"Environment\" HEX 0 255;\n"
|
||||
"BO_ 1 Standard: 1 ECU\n"
|
||||
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
|
||||
"BO_ 2147483649 Extended: 1 ECU\n"
|
||||
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n");
|
||||
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
|
||||
QVERIFY(!result.HasErrors());
|
||||
QVERIFY(result.diagnostics.empty());
|
||||
const DbcDatabase &database = result.database;
|
||||
QCOMPARE(database.attributeDefinitions.size(), std::size_t(7));
|
||||
QCOMPARE(database.attributeAssignments.size(), std::size_t(5));
|
||||
QCOMPARE(std::get<std::string>(database.attributes.at("BusLabel")), std::string("CAN"));
|
||||
QCOMPARE(database.frames[0].cycleTimeMs.value(), 25U);
|
||||
QCOMPARE(database.frames[1].cycleTimeMs.value(), 100U);
|
||||
QCOMPARE(database.frames[1].frameFormat.value(), std::string("ExtendedCAN"));
|
||||
QVERIFY(!database.frames[1].hasPgn);
|
||||
QCOMPARE(std::get<double>(database.frames[0].attributes.at("Ratio")), 0.15);
|
||||
QCOMPARE(std::get<std::string>(database.frames[0].signals[0].attributes.at("Label")), std::string("a\"b\\c"));
|
||||
QCOMPARE(std::get<std::string>(database.frames[1].signals[0].attributes.at("Label")), std::string("default"));
|
||||
const DecodeDatabase runtime = DbcDecodeBuilder().Build(database);
|
||||
QCOMPARE(runtime.GetFrames()[0].cycleTimeMs.value(), 25U);
|
||||
QCOMPARE(runtime.GetFrames()[1].frameFormat.value(), std::string("ExtendedCAN"));
|
||||
QCOMPARE(runtime.GetFrames()[0].signals[0].attributes, database.frames[0].signals[0].attributes);
|
||||
const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(database);
|
||||
QCOMPARE(tree->GetChild(0)->GetFrame()->cycleTimeMs.value(), 25U);
|
||||
QCOMPARE(tree->GetChild(0)->GetChild(0)->GetSignal()->attributes, database.frames[0].signals[0].attributes);
|
||||
fixture.Write("BA_DEF_ BO_ \"Period\" INT 0 10;\nBA_DEF_DEF_ \"Period\" 1;\n"
|
||||
"BA_ \"Period\" BO_ 1 2;\nBA_ \"Period\" BO_ 1 3;\nBO_ 1 F: 0 ECU\n");
|
||||
const DbcDatabase repeated = DbcParser().ParseFile(fixture.path.string());
|
||||
QCOMPARE(std::get<std::int64_t>(repeated.frames[0].attributes.at("Period")), std::int64_t(3));
|
||||
|
||||
fixture.Write("BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967295;\n"
|
||||
"BA_DEF_DEF_ \"GenMsgCycleTime\" 0;\n"
|
||||
"BA_ \"GenMsgCycleTime\" BO_ 1 4294967295;\n"
|
||||
"BA_DEF_ \"Wide\" INT -9223372036854775808 9223372036854775807;\n"
|
||||
"BA_DEF_DEF_ \"Wide\" -9223372036854775808;\n"
|
||||
"BA_DEF_ SG_ \"State\" ENUM \"off\",\"on\";\n"
|
||||
"BA_DEF_DEF_ \"State\" 1;\n"
|
||||
"BO_ 1 F: 0 ECU\nBO_ 2 Empty: 0 ECU\n");
|
||||
const DbcDatabase boundaries = DbcParser().ParseFile(fixture.path.string());
|
||||
QCOMPARE(boundaries.frames[0].cycleTimeMs.value(), std::numeric_limits<std::uint32_t>::max());
|
||||
QCOMPARE(boundaries.frames[1].cycleTimeMs.value(), 0U);
|
||||
QVERIFY(!boundaries.frames[0].frameFormat);
|
||||
QCOMPARE(std::get<std::int64_t>(boundaries.attributes.at("Wide")), std::numeric_limits<std::int64_t>::min());
|
||||
QCOMPARE(std::get<std::int64_t>(*boundaries.attributeDefinitions.at("State").defaultValue), std::int64_t(1));
|
||||
}
|
||||
|
||||
void DbcParserTest::invalidAttributes() {
|
||||
Fixture fixture;
|
||||
const std::vector<std::string> invalid = {
|
||||
"BA_DEF_ BO_ \"A\" INT 2 1;",
|
||||
"BA_DEF_ BO_ \"A\" FLOAT nan 1;",
|
||||
"BA_DEF_ BO_ \"A\" FLOAT 0 inf;",
|
||||
"BA_DEF_ BO_ \"A\" INT 0 9223372036854775808;",
|
||||
"BA_DEF_ BO_ \"A\" STRING; trailing",
|
||||
"BA_DEF_ BO_ \"A\" ENUM;",
|
||||
"BA_DEF_ BO_ \"A\" ENUM \"one\",;",
|
||||
"BA_DEF_ BO_ \"A\" ENUM \"one\",\"one\";",
|
||||
"BA_DEF_ BO_ \"VFrameFormat\" STRING;",
|
||||
"BA_DEF_ SG_ \"GenMsgCycleTime\" INT 0 10;",
|
||||
"BA_ \"Unknown\" BO_ 1 0;",
|
||||
"BA_DEF_DEF_ \"Unknown\" 0;",
|
||||
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 2;",
|
||||
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 \"0\";",
|
||||
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 S 0;",
|
||||
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 2 0;",
|
||||
"BA_DEF_ SG_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 Missing 0;",
|
||||
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_ BO_ \"A\" INT 0 1;",
|
||||
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_DEF_ \"A\" 0;\nBA_DEF_DEF_ \"A\" 1;",
|
||||
"BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"bad\\q\";",
|
||||
"BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"unterminated;",
|
||||
"BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_DEF_DEF_ \"A\" \"missing\";",
|
||||
"BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_ \"A\" BO_ 1 1;",
|
||||
"BA_DEF_ BO_ \"GenMsgCycleTime\" INT -1 4294967296;\nBA_DEF_DEF_ \"GenMsgCycleTime\" -1;",
|
||||
"BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967296;\nBA_ \"GenMsgCycleTime\" BO_ 1 4294967296;"};
|
||||
for (const std::string &statement: invalid) {
|
||||
fixture.Write("BO_ 1 F: 0 ECU\n" + statement + '\n');
|
||||
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
|
||||
QVERIFY2(result.HasErrors(), statement.c_str());
|
||||
QVERIFY(result.database.frames.empty());
|
||||
QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
|
||||
QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(2 + std::count(statement.begin(), statement.end(), '\n')));
|
||||
QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
|
||||
}
|
||||
}
|
||||
|
||||
void DbcParserTest::attributeWarnings() {
|
||||
Fixture fixture;
|
||||
fixture.Write("BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"J1939PG\",\"StandardCAN_FD\";\n"
|
||||
"BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN_FD\";\n"
|
||||
"BA_ \"VFrameFormat\" BO_ 1 2;\n"
|
||||
"BA_ \"VFrameFormat\" BO_ 2147483649 0;\n"
|
||||
"BO_ 1 F: 0 ECU\nBO_ 2147483649 E: 0 ECU\nBO_ 2 Default: 0 ECU\n");
|
||||
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
|
||||
QVERIFY(!result.HasErrors());
|
||||
QCOMPARE(result.diagnostics.size(), std::size_t(3));
|
||||
const std::size_t lines[] = {3U, 4U, 2U};
|
||||
for (std::size_t index = 0; index < 3U; ++index) {
|
||||
QCOMPARE(result.diagnostics[index].code, DbcDiagnosticCode::UnsupportedAttribute);
|
||||
QCOMPARE(result.diagnostics[index].severity, DbcDiagnosticSeverity::Warning);
|
||||
QCOMPARE(result.diagnostics[index].lineNumber, lines[index]);
|
||||
QCOMPARE(result.diagnostics[index].filePath, fixture.path.string());
|
||||
}
|
||||
QCOMPARE(result.database.frames[0].frameFormat.value(), std::string("J1939PG"));
|
||||
QVERIFY(!result.database.frames[0].hasPgn);
|
||||
QVERIFY(result.database.frames[1].isExtended);
|
||||
}
|
||||
|
||||
void DbcParserTest::diagnostics() {
|
||||
Fixture fixture;
|
||||
fixture.Write("VERSION \"test\"\n"
|
||||
@@ -126,7 +257,7 @@ void DbcParserTest::diagnostics() {
|
||||
"BO_ 1 Frame: 1 ECU\n"
|
||||
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
|
||||
"SIG_VALTYPE_ 1 Value : 1;\n"
|
||||
"BA_ \"SignalType\" SG_ 1 Value 1;\n"
|
||||
"BA_REL_ \"SignalType\" SG_ 1 Value 1;\n"
|
||||
"VENDOR_EXTENSION_ data;\n");
|
||||
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
|
||||
CHECK(!result.HasErrors() && result.database.frames.size() == 1U,
|
||||
@@ -264,9 +395,7 @@ void DbcParserTest::diagnosticUnsupportedStatements() {
|
||||
"CM_ SG_ 1 Value \"Signal comment\";\nVAL_ 1 Value 1 \"On\";\n";
|
||||
const std::vector<std::string> statements = {
|
||||
"SIG_VALTYPE_ 1 Value : 1;", "SIG_VALTYPE_ 1 Value : 2;",
|
||||
"BA_ \"SignalType\" SG_ 1 Value 1;",
|
||||
"BA_DEF_ SG_ \"SignalType\" INT 0 1;",
|
||||
"BA_DEF_DEF_ \"SignalType\" 0;",
|
||||
"BA_REL_ \"SignalType\" SG_ 1 Value 1;",
|
||||
"BA_DEF_REL_ BU_SG_REL_ \"Relation\" INT 0 1;",
|
||||
"BA_REL_ \"Relation\" BU_SG_REL_ ECU SG_ 1 Value 1;",
|
||||
"SGTYPE_ Type : 8@1+ (1,0) [0|255] \"\" 0, Table;",
|
||||
|
||||
Reference in new issue
Block a user