/** * @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 #include #include #include 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(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; }