///|
/// A half-open source range. Lines and columns are one-based; the end position
/// points immediately after the declaration.
pub(all) struct SourceSpan {
start_line : Int
start_column : Int
end_line : Int
end_column : Int
} derive(Eq, Debug)
///|
pub(all) struct MessageSourceLocation {
message_id : UInt
declaration : SourceSpan
} derive(Eq, Debug)
///|
pub(all) struct SignalSourceLocation {
message_id : UInt
signal_name : String
declaration : SourceSpan
} derive(Eq, Debug)
///|
pub(all) struct CommentSourceLocation {
target : DbcCommentTarget
declaration : SourceSpan
} derive(Eq, Debug)
///|
pub(all) struct ValueTableSourceLocation {
message_id : UInt
signal_name : String
declaration : SourceSpan
} derive(Eq, Debug)
///|
pub(all) struct GlobalValueTableSourceLocation {
name : String
declaration : SourceSpan
} derive(Eq, Debug)
///|
pub(all) struct EnvironmentVariableSourceLocation {
name : String
declaration : SourceSpan
} derive(Eq, Debug)
///|
pub(all) struct SignalGroupSourceLocation {
message_id : UInt
name : String
declaration : SourceSpan
} derive(Eq, Debug)
///|
/// Source locations are kept separately from the semantic model so manually
/// constructed databases and existing `parse` callers remain source-agnostic.
pub(all) struct DbcSourceMap {
messages : Array[MessageSourceLocation]
signals : Array[SignalSourceLocation]
comments : Array[CommentSourceLocation]
value_tables : Array[ValueTableSourceLocation]
global_value_tables : Array[GlobalValueTableSourceLocation]
environment_variables : Array[EnvironmentVariableSourceLocation]
signal_groups : Array[SignalGroupSourceLocation]
} derive(Eq, Debug)
///|
pub(all) struct SourceParseResult {
database : Database
diagnostics : Array[Diagnostic]
source_map : DbcSourceMap
} derive(Eq, Debug)
///|
pub(all) struct LocatedValidationIssue {
issue : ValidationIssue
source : SourceSpan?
} derive(Eq, Debug)
///|
pub(all) struct LocatedDatabaseChange {
change : DatabaseChange
before : SourceSpan?
after : SourceSpan?
} derive(Eq, Debug)
///|
pub fn SourceParseResult::is_valid(self : SourceParseResult) -> Bool {
self.diagnostics.is_empty()
}
///|
pub fn DbcSourceMap::find_message(
self : DbcSourceMap,
message_id : UInt,
) -> SourceSpan? {
for location in self.messages {
if location.message_id == message_id {
return Some(location.declaration)
}
}
None
}
///|
pub fn DbcSourceMap::find_signal(
self : DbcSourceMap,
message_id : UInt,
signal_name : StringView,
) -> SourceSpan? {
for location in self.signals {
if location.message_id == message_id &&
location.signal_name.view() == signal_name {
return Some(location.declaration)
}
}
None
}
///|
pub fn DbcSourceMap::find_comment(
self : DbcSourceMap,
target : DbcCommentTarget,
) -> SourceSpan? {
for location in self.comments {
if location.target == target {
return Some(location.declaration)
}
}
None
}
///|
pub fn DbcSourceMap::find_value_table(
self : DbcSourceMap,
message_id : UInt,
signal_name : StringView,
) -> SourceSpan? {
for location in self.value_tables {
if location.message_id == message_id &&
location.signal_name.view() == signal_name {
return Some(location.declaration)
}
}
None
}
///|
pub fn DbcSourceMap::find_global_value_table(
self : DbcSourceMap,
name : StringView,
) -> SourceSpan? {
for location in self.global_value_tables {
if location.name.view() == name {
return Some(location.declaration)
}
}
None
}
///|
pub fn DbcSourceMap::find_environment_variable(
self : DbcSourceMap,
name : StringView,
) -> SourceSpan? {
for location in self.environment_variables {
if location.name.view() == name {
return Some(location.declaration)
}
}
None
}
///|
pub fn DbcSourceMap::find_signal_group(
self : DbcSourceMap,
message_id : UInt,
name : StringView,
) -> SourceSpan? {
for location in self.signal_groups {
if location.message_id == message_id && location.name.view() == name {
return Some(location.declaration)
}
}
None
}
///|
fn change_source(
source_map : DbcSourceMap,
change : DatabaseChange,
) -> SourceSpan? {
match change {
VersionChangedChange(_, _)
| NodesChangedChange(_, _)
| AttributesChangedChange(_, _) => None
CommentAddedChange(comment) | CommentRemovedChange(comment) =>
source_map.find_comment(comment.target)
CommentModifiedChange(target, _, _) => source_map.find_comment(target)
MessageAddedChange(message) | MessageRemovedChange(message) =>
source_map.find_message(message.id)
MessageMetadataChangedChange(id, _, _) => source_map.find_message(id)
SignalAddedChange(id, signal) | SignalRemovedChange(id, signal) =>
source_map.find_signal(id, signal.name)
SignalModifiedChange(id, name, _, _) => source_map.find_signal(id, name)
ValueTableAddedChange(table) | ValueTableRemovedChange(table) =>
source_map.find_value_table(table.message_id, table.signal_name)
ValueTableModifiedChange(id, name, _, _) =>
source_map.find_value_table(id, name)
GlobalValueTableAddedChange(table) | GlobalValueTableRemovedChange(table) =>
source_map.find_global_value_table(table.name)
GlobalValueTableModifiedChange(name, _, _) =>
source_map.find_global_value_table(name)
EnvironmentVariableAddedChange(variable)
| EnvironmentVariableRemovedChange(variable) =>
source_map.find_environment_variable(variable.name)
EnvironmentVariableModifiedChange(name, _, _) =>
source_map.find_environment_variable(name)
SignalGroupAddedChange(group) | SignalGroupRemovedChange(group) =>
source_map.find_signal_group(group.message_id, group.name)
SignalGroupModifiedChange(id, name, _, _) =>
source_map.find_signal_group(id, name)
}
}
///|
/// Compare parsed DBCs and attach the old and new declaration ranges to each
/// change. Global metadata has no declaration mapping and returns `None`.
pub fn SourceParseResult::diff_with_locations(
self : SourceParseResult,
newer : SourceParseResult,
) -> Array[LocatedDatabaseChange] {
let result : Array[LocatedDatabaseChange] = []
for change in self.database.diff(newer.database) {
let old_location = match change {
CommentAddedChange(_)
| MessageAddedChange(_)
| SignalAddedChange(_, _)
| ValueTableAddedChange(_)
| GlobalValueTableAddedChange(_)
| EnvironmentVariableAddedChange(_)
| SignalGroupAddedChange(_) => None
_ => change_source(self.source_map, change)
}
let new_location = match change {
CommentRemovedChange(_)
| MessageRemovedChange(_)
| SignalRemovedChange(_, _)
| ValueTableRemovedChange(_)
| GlobalValueTableRemovedChange(_)
| EnvironmentVariableRemovedChange(_)
| SignalGroupRemovedChange(_) => None
_ => change_source(newer.source_map, change)
}
result.push({ change, before: old_location, after: new_location, })
}
result
}
///|
fn validation_issue_source(
source_map : DbcSourceMap,
issue : ValidationIssue,
) -> SourceSpan? {
match issue {
InvalidMessageIdentifierIssue(id) => source_map.find_message(id)
DuplicateMessageIdIssue(id) => source_map.find_message(id)
InvalidPayloadSizeIssue(id, _) => source_map.find_message(id)
MultipleMultiplexersIssue(id) => source_map.find_message(id)
DuplicateMessageTransmitterIssue(id, _) => source_map.find_message(id)
DuplicateSignalNameIssue(id, name) => source_map.find_signal(id, name)
InvalidSignalLayoutIssue(id, name, _, _) => source_map.find_signal(id, name)
InvalidSignalValueTypeIssue(id, name, _, _) =>
source_map.find_signal(id, name)
FloatingMultiplexerIssue(id, name, _) => source_map.find_signal(id, name)
SignalOutsidePayloadIssue(id, name) => source_map.find_signal(id, name)
InvalidScaleIssue(id, name, _, _) => source_map.find_signal(id, name)
InvalidPhysicalRangeIssue(id, name, _, _) =>
source_map.find_signal(id, name)
MissingMultiplexerIssue(id, name) => source_map.find_signal(id, name)
MultiplexSelectorOutOfRangeIssue(id, name, _) =>
source_map.find_signal(id, name)
InvalidMultiplexRangeIssue(id, name, _, _) =>
source_map.find_signal(id, name)
UnknownMultiplexSelectorIssue(id, name, _) =>
source_map.find_signal(id, name)
OverlappingSignalsIssue(id, _, right) => source_map.find_signal(id, right)
DuplicateCommentIssue(target) => source_map.find_comment(target)
UnknownCommentNodeIssue(node) => source_map.find_comment(NodeComment(node))
UnknownCommentMessageIssue(id) =>
source_map.find_comment(MessageComment(id))
UnknownCommentSignalIssue(id, name) =>
source_map.find_comment(SignalComment(id, name))
UnknownCommentEnvironmentIssue(name) =>
source_map.find_comment(EnvironmentComment(name))
DuplicateAttributeDefinitionIssue(_)
| DuplicateAttributeDefaultIssue(_)
| DuplicateAttributeAssignmentIssue(_, _)
| UnknownAttributeDefinitionIssue(_)
| InvalidAttributeTargetIssue(_, _)
| AttributeScopeMismatchIssue(_, _)
| InvalidAttributeValueIssue(_) => None
DuplicateGlobalValueTableIssue(name) | InvalidGlobalValueTableIssue(name) =>
source_map.find_global_value_table(name)
DuplicateEnvironmentVariableIssue(name)
| InvalidEnvironmentVariableIssue(name) =>
source_map.find_environment_variable(name)
DuplicateSignalGroupIssue(id, name)
| InvalidSignalGroupIssue(id, name)
| UnknownSignalGroupMessageIssue(id, name) =>
source_map.find_signal_group(id, name)
UnknownSignalGroupMemberIssue(id, name, _) =>
source_map.find_signal_group(id, name)
}
}
///|
/// Validate the parsed model and attach declaration locations where available.
/// Overlap issues point to the later signal; comment issues without declaration
/// locations retain `None` rather than inventing a source position.
pub fn SourceParseResult::locate_validation_issues(
self : SourceParseResult,
) -> Array[LocatedValidationIssue] {
self.database
.validate()
.map(issue => {
issue,
source: validation_issue_source(self.source_map, issue),
})
}