151 lines
5.4 KiB
C++
151 lines
5.4 KiB
C++
/**
|
|
* @file dbc_parser.cpp
|
|
* @brief
|
|
*
|
|
* Created: 2026-03-13
|
|
* Author: Deeaitch (Dim. Himro)
|
|
*
|
|
* Licensed under the MIT License.
|
|
* See LICENSE file in the project root for full license text.
|
|
*/
|
|
|
|
#include "dbc_parser.h"
|
|
#include "dbc_statement_handler.h"
|
|
|
|
#include "text_utils.h"
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
// Read through the closing unescaped quote, preserving comment whitespace.
|
|
void ReadCommentLines(std::ifstream &input, std::string &statement,
|
|
std::size_t &lineNumber) {
|
|
bool inQuote = false;
|
|
bool escaped = false;
|
|
std::string part = statement;
|
|
for (;;) {
|
|
for (const char character: part) {
|
|
if (escaped) {
|
|
escaped = false;
|
|
} else if (inQuote && character == '\\') {
|
|
escaped = true;
|
|
} else if (character == '"') {
|
|
if (inQuote)
|
|
return;
|
|
inQuote = true;
|
|
}
|
|
}
|
|
if (!inQuote || !std::getline(input, part))
|
|
return;
|
|
++lineNumber;
|
|
if (!part.empty() && part.back() == '\r')
|
|
part.pop_back();
|
|
statement += '\n';
|
|
statement += part;
|
|
// A physical newline cannot escape the next line's first character.
|
|
escaped = false;
|
|
}
|
|
}
|
|
|
|
DbcDiagnosticCode StatementErrorCode(const std::string &keyword) {
|
|
if (keyword == "SG_MUL_VAL_")
|
|
return DbcDiagnosticCode::UnsupportedStatement;
|
|
if (keyword == "VAL_")
|
|
return DbcDiagnosticCode::InvalidValueDescription;
|
|
return DbcDiagnosticCode::InvalidStatement;
|
|
}
|
|
}
|
|
|
|
DbcDatabase DbcParser::ParseFile(const std::string &filePath) const {
|
|
DbcParseResult result = ParseFileWithDiagnostics(filePath);
|
|
for (const DbcDiagnostic &diagnostic : result.diagnostics) {
|
|
if (diagnostic.severity == DbcDiagnosticSeverity::Error) {
|
|
if (diagnostic.lineNumber == 0U)
|
|
throw std::runtime_error(diagnostic.message);
|
|
throw std::runtime_error(diagnostic.filePath + ":" +
|
|
std::to_string(diagnostic.lineNumber) + ": " + diagnostic.message);
|
|
}
|
|
}
|
|
return std::move(result.database);
|
|
}
|
|
|
|
DbcParseResult DbcParser::ParseFileWithDiagnostics(const std::string &filePath) const {
|
|
DbcParseResult result;
|
|
std::ifstream input(filePath.c_str());
|
|
if (!input.is_open()) {
|
|
result.diagnostics.push_back({DbcDiagnosticSeverity::Error,
|
|
DbcDiagnosticCode::FileOpenError, filePath, 0U,
|
|
"Failed to open DBC file: " + filePath});
|
|
return result;
|
|
}
|
|
|
|
DbcDatabase database;
|
|
DbcParseContext context{database, nullptr};
|
|
const DbcStatementDispatcher dispatcher;
|
|
bool inNamespace = false;
|
|
std::size_t lineNumber = 0U;
|
|
std::string line;
|
|
while (std::getline(input, line)) {
|
|
++lineNumber;
|
|
if (!line.empty() && line.back() == '\r')
|
|
line.pop_back();
|
|
const std::string rawLine = line;
|
|
const std::size_t statementLine = lineNumber;
|
|
line = dbc_detail::TrimText(line);
|
|
if (line.empty())
|
|
continue;
|
|
|
|
std::istringstream tokens(line);
|
|
std::string keyword;
|
|
std::string argument;
|
|
tokens >> keyword;
|
|
const bool hasArgument = static_cast<bool>(tokens >> argument);
|
|
if (inNamespace && !hasArgument && keyword != "BO_" && keyword != "SG_" &&
|
|
keyword != "BS_" && keyword != "BS_:" &&
|
|
keyword != "BU_" && keyword != "BU_:")
|
|
continue; // NS_ lists syntax symbols; BO_, SG_, BS_ and BU_ end the list.
|
|
inNamespace = keyword == "NS_" || keyword == "NS_:";
|
|
|
|
if (keyword == "CM_") {
|
|
line = rawLine;
|
|
ReadCommentLines(input, line, lineNumber);
|
|
}
|
|
|
|
try {
|
|
context.lineNumber = statementLine;
|
|
if (!dispatcher.Dispatch(line, context)) {
|
|
result.diagnostics.push_back({DbcDiagnosticSeverity::Warning,
|
|
DbcDiagnosticCode::UnsupportedStatement,
|
|
filePath, statementLine,
|
|
"Unsupported statement '" + keyword +
|
|
"' is ignored; decoding or metadata may be incomplete."});
|
|
}
|
|
} catch (const std::exception &error) {
|
|
result.diagnostics.push_back({DbcDiagnosticSeverity::Error,
|
|
StatementErrorCode(keyword), filePath,
|
|
statementLine, error.what()});
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (input.bad() || !input.eof()) {
|
|
result.diagnostics.push_back({DbcDiagnosticSeverity::Error,
|
|
DbcDiagnosticCode::FileReadError, filePath, 0U,
|
|
"Failed to read DBC file: " + filePath});
|
|
return result;
|
|
}
|
|
|
|
try {
|
|
context.ResolveComments(filePath);
|
|
context.ResolveValueDescriptions(filePath);
|
|
context.ValidateMultiplexing(filePath);
|
|
} catch (const DbcDiagnosticError &error) {
|
|
result.diagnostics.push_back(error.diagnostic);
|
|
return result;
|
|
}
|
|
result.database = std::move(database);
|
|
return result;
|
|
}
|