///|
/// Kind of semantic revision; no source line comparison is involved.
pub(all) enum ChangeKind {
Added
Removed
Changed
} derive(Eq, @debug.Debug)
///|
/// One changed semantic field; breaking marks frame interpretation changes.
pub(all) struct Change {
kind : ChangeKind
message_id : UInt?
message_name : String?
signal_name : String?
field : String
before : String
after : String
breaking : Bool
} derive(Eq, @debug.Debug)
///|
fn add_change(
out : Array[Change],
field : String,
before : String,
after : String,
message? : Message? = None,
signal? : Signal? = None,
breaking? : Bool = false,
kind? : ChangeKind = Changed,
) -> Unit {
if before == after {
return
}
out.push({
kind,
message_id: message.map(fn(m) { m.raw_dbc_id }),
message_name: message.map(fn(m) { m.name }),
signal_name: signal.map(fn(s) { s.name }),
field,
before,
after,
breaking,
})
}
///|
fn values_signature(s : Signal) -> String {
let values = s.values.map(fn(v) { "\{v.raw}=\{quote_dbc(v.label)}" })
values.sort()
values.join(";")
}
///|
fn receivers_signature(s : Signal) -> String {
let names = s.receivers.copy()
names.sort()
names.join(",")
}
///|
fn signal_diff(
old : Signal,
new : Signal,
m : Message,
out : Array[Change],
) -> Unit {
let message = Some(m)
let signal = Some(new)
add_change(
out,
"start_bit",
old.start_bit.to_string(),
new.start_bit.to_string(),
message~,
signal~,
breaking=true,
)
add_change(
out,
"bit_length",
old.bit_length.to_string(),
new.bit_length.to_string(),
message~,
signal~,
breaking=true,
)
add_change(
out,
"byte_order",
if old.byte_order == Intel {
"Intel"
} else {
"Motorola"
},
if new.byte_order == Intel {
"Intel"
} else {
"Motorola"
},
message~,
signal~,
breaking=true,
)
add_change(
out,
"signed",
old.signed.to_string(),
new.signed.to_string(),
message~,
signal~,
breaking=true,
)
add_change(
out,
"factor",
double_text(old.factor),
double_text(new.factor),
message~,
signal~,
breaking=true,
)
add_change(
out,
"offset",
double_text(old.offset),
double_text(new.offset),
message~,
signal~,
breaking=true,
)
add_change(
out,
"minimum",
double_text(old.minimum),
double_text(new.minimum),
message~,
signal~,
)
add_change(
out,
"maximum",
double_text(old.maximum),
double_text(new.maximum),
message~,
signal~,
)
add_change(out, "unit", old.unit, new.unit, message~, signal~)
add_change(
out,
"receivers",
receivers_signature(old),
receivers_signature(new),
message~,
signal~,
)
add_change(
out,
"values",
values_signature(old),
values_signature(new),
message~,
signal~,
)
let mux_text = fn(s : Signal) {
match s.multiplex {
Always => "always"
Multiplexer => "M"
Branch(n) => "m\{n}"
}
}
add_change(
out,
"multiplex",
mux_text(old),
mux_text(new),
message~,
signal~,
breaking=true,
)
}
///|
fn metadata_signature(db : Database) -> String {
let parts = []
for c in db.comments {
parts.push("CM_ " + object_text(c.target) + quote_dbc(c.text))
}
for d in db.attribute_definitions {
parts.push(
"BA_DEF_ " +
scope_text(d.scope) +
quote_dbc(d.name) +
attribute_type_text(d.kind),
)
}
for a in db.attributes {
parts.push(
"BA_ " +
quote_dbc(a.name) +
(match a.target {
Some(t) => object_text(t)
None => "default "
}) +
attribute_value_text(a.value),
)
}
for u in db.unsupported {
parts.push(u.text)
}
parts.sort()
parts.join("\n")
}
///|
/// Compare message identity, signal semantics and global metadata, ignoring source spans.
pub fn diff_database(old : Database, new : Database) -> Array[Change] {
let out = []
add_change(out, "version", old.version, new.version)
let old_nodes = old.nodes.map(fn(n) { n.name })
let new_nodes = new.nodes.map(fn(n) { n.name })
old_nodes.sort()
new_nodes.sort()
add_change(out, "nodes", old_nodes.join(","), new_nodes.join(","))
add_change(out, "metadata", metadata_signature(old), metadata_signature(new))
for m in old.messages {
match new.message_by_id(m.frame_id) {
None =>
add_change(
out,
"message",
m.name,
"",
message=Some(m),
kind=Removed,
breaking=true,
)
Some(n) => {
add_change(out, "name", m.name, n.name, message=Some(n), breaking=true)
add_change(
out,
"length",
m.length.to_string(),
n.length.to_string(),
message=Some(n),
breaking=true,
)
add_change(
out,
"transmitter",
m.transmitter,
n.transmitter,
message=Some(n),
)
for s in m.signals {
match n.signal_by_name(s.name) {
None =>
add_change(
out,
"signal",
s.name,
"",
message=Some(n),
signal=Some(s),
kind=Removed,
breaking=true,
)
Some(t) => signal_diff(s, t, n, out)
}
}
for s in n.signals {
if m.signal_by_name(s.name) is None {
add_change(
out,
"signal",
"",
s.name,
message=Some(n),
signal=Some(s),
kind=Added,
)
}
}
}
}
}
for m in new.messages {
if old.message_by_id(m.frame_id) is None {
add_change(out, "message", "", m.name, message=Some(m), kind=Added)
}
}
out
}