///|
/// Collect all semantic errors without throwing, including multiplex-aware overlaps.
pub fn validate_database(db : Database) -> Array[Diagnostic] {
let out = db.diagnostics.copy()
let node_names : Map[String, Bool] = Map([])
for n in db.nodes {
if node_names.contains(n.name) {
out.push(
diagnostic(
"node.duplicate",
"duplicate node \{n.name}",
location=n.location,
),
)
}
if !valid_name(n.name) {
out.push(
diagnostic("node.name", "invalid node name", location=n.location),
)
}
node_names[n.name] = true
}
let message_ids : Map[UInt, Bool] = Map([])
let message_names : Map[String, Bool] = Map([])
for m in db.messages {
if message_ids.contains(m.frame_id.to_dbc()) {
out.push(
message_diagnostic(
m, "message.duplicate_id", "duplicate frame identity",
),
)
}
if message_names.contains(m.name) {
out.push(
message_diagnostic(
m, "message.duplicate_name", "duplicate message name",
),
)
}
message_ids[m.frame_id.to_dbc()] = true
message_names[m.name] = true
if !valid_name(m.name) {
out.push(message_diagnostic(m, "message.name", "invalid message name"))
}
try FrameId::new(m.frame_id.id, extended=m.frame_id.extended) catch {
Failure(d) => out.push(message_diagnostic(m, d.code, d.message))
} noraise {
_ => ()
}
if m.raw_dbc_id != m.frame_id.to_dbc() {
out.push(
message_diagnostic(
m, "message.raw_id", "raw DBC ID differs from normalized ID",
),
)
}
if !known_node(db, m.transmitter) {
out.push(
message_diagnostic(
m,
"message.transmitter",
"unknown transmitter \{m.transmitter}",
),
)
}
validate_message(m, db, out)
}
for c in db.comments {
if !object_exists(db, c.target) {
out.push(
diagnostic(
"comment.target",
"comment references unknown object",
location=c.location,
),
)
}
}
validate_attributes(db, out)
out
}
///|
fn known_node(db : Database, name : String) -> Bool {
name == "Vector__XXX" || db.node_by_name(name) is Some(_)
}
///|
fn message_diagnostic(m : Message, code : String, text : String) -> Diagnostic {
diagnostic(code, text, location=m.location, message_name=Some(m.name))
}
///|
fn signal_diagnostic(
m : Message,
s : Signal,
code : String,
text : String,
) -> Diagnostic {
diagnostic(
code,
text,
location=s.location,
message_name=Some(m.name),
signal_name=Some(s.name),
)
}
///|
fn validate_message(
m : Message,
db : Database,
out : Array[Diagnostic],
check_nodes? : Bool = true,
) -> Unit {
if m.length < 0 || m.length > 64 {
out.push(
message_diagnostic(m, "message.length", "payload must be 0..64 bytes"),
)
return
}
let names : Map[String, Bool] = Map([])
let owners : Array[Array[Int]] = Array::makei(m.length * 8, fn(_) { [] })
let overlap_pairs : Map[(Int, Int), Bool] = Map([])
let mut selectors = 0
let mut branches = 0
let mut selector : Signal? = None
for i, s in m.signals {
if names.contains(s.name) {
out.push(
signal_diagnostic(m, s, "signal.duplicate", "duplicate signal name"),
)
}
names[s.name] = true
if !valid_name(s.name) {
out.push(signal_diagnostic(m, s, "signal.name", "invalid signal name"))
}
if !finite(s.factor) || s.factor == 0.0 || !finite(s.offset) {
out.push(
signal_diagnostic(
m, s, "signal.factor", "factor must be finite and nonzero; offset finite",
),
)
}
if !finite(s.minimum) || !finite(s.maximum) || s.minimum > s.maximum {
out.push(
signal_diagnostic(m, s, "signal.range", "invalid physical range"),
)
}
for receiver in s.receivers {
if check_nodes && !known_node(db, receiver) {
out.push(
signal_diagnostic(
m,
s,
"signal.receiver",
"unknown receiver \{receiver}",
),
)
}
}
match s.multiplex {
Multiplexer => {
selectors += 1
selector = Some(s)
if s.signed {
out.push(
signal_diagnostic(
m, s, "mux.signed", "multiplex selector must be unsigned",
),
)
}
}
Branch(n) => {
branches += 1
if n < 0L {
out.push(
signal_diagnostic(m, s, "mux.branch", "negative multiplex branch"),
)
}
}
Always => ()
}
try signal_bits(s, m.length) catch {
Failure(d) => out.push(signal_diagnostic(m, s, d.code, d.message))
} noraise {
bits =>
for bit in bits {
for j in owners[bit] {
let other = m.signals[j]
if active_together(s.multiplex, other.multiplex) &&
!overlap_pairs.contains((j, i)) {
overlap_pairs[(j, i)] = true
out.push(
signal_diagnostic(
m,
s,
"signal.overlap",
"overlap with \{other.name} at bit \{bit}",
),
)
}
}
owners[bit].push(i)
}
}
let raw_values : Map[Int64, Bool] = Map([])
let labels : Map[String, Bool] = Map([])
for v in s.values {
if raw_values.contains(v.raw) {
out.push(
signal_diagnostic(
m, s, "values.duplicate", "duplicate raw enumeration value",
),
)
}
if labels.contains(v.label) {
out.push(
signal_diagnostic(m, s, "values.label", "duplicate enumeration label"),
)
}
raw_values[v.raw] = true
labels[v.label] = true
if !integer_fits(s, v.raw) {
out.push(
signal_diagnostic(
m, s, "values.range", "enumeration is outside signal raw range",
),
)
}
}
}
if selectors > 1 {
out.push(
message_diagnostic(
m, "mux.multiple", "basic multiplexing permits one selector",
),
)
}
if branches > 0 && selectors == 0 {
out.push(
message_diagnostic(m, "mux.missing", "branch signals require a selector"),
)
}
if selector is Some(sel) {
for s in m.signals {
if s.multiplex is Branch(n) && !integer_fits(sel, n) {
out.push(
signal_diagnostic(
m, s, "mux.range", "branch value does not fit selector",
),
)
}
}
}
}
///|
fn integer_fits(s : Signal, raw : Int64) -> Bool {
if s.bit_length < 1 || s.bit_length > 64 {
return false
}
if s.signed {
if s.bit_length == 64 {
return true
}
let limit = 1L << (s.bit_length - 1)
raw >= -limit && raw < limit
} else {
raw >= 0L && (s.bit_length >= 63 || raw < 1L << s.bit_length)
}
}
///|
fn object_exists(db : Database, target : ObjectRef) -> Bool {
match target {
DatabaseRef => true
NodeRef(n) => db.node_by_name(n) is Some(_)
MessageRef(id) => db.messages.iter().any(fn(m) { m.raw_dbc_id == id })
SignalRef(id, n) =>
db.messages
.iter()
.any(fn(m) { m.raw_dbc_id == id && m.signal_by_name(n) is Some(_) })
}
}
///|
fn validate_attributes(db : Database, out : Array[Diagnostic]) -> Unit {
let defs : Map[String, AttributeDefinition] = Map([])
for d in db.attribute_definitions {
if defs.contains(d.name) {
out.push(
diagnostic(
"attribute.duplicate_definition",
"duplicate attribute definition",
location=d.location,
),
)
}
defs[d.name] = d
let valid = match d.kind {
IntType(lo, hi) | HexType(lo, hi) => lo <= hi
FloatType(lo, hi) => finite(lo) && finite(hi) && lo <= hi
EnumType(choices) =>
choices.length() > 0 &&
choices
.iter()
.all(fn(c) { choices.filter(fn(x) { x == c }).length() == 1 })
StringType => true
}
if !valid {
out.push(
diagnostic(
"attribute.definition",
"invalid attribute bounds or enum choices",
location=d.location,
),
)
}
}
let seen : Map[(String, ObjectRef?), Bool] = Map([])
for a in db.attributes {
if seen.contains((a.name, a.target)) {
out.push(
diagnostic(
"attribute.duplicate_assignment",
"duplicate attribute assignment/default",
location=a.location,
),
)
}
seen[(a.name, a.target)] = true
if a.target is Some(target) && !object_exists(db, target) {
out.push(
diagnostic(
"attribute.target",
"unknown attribute object",
location=a.location,
),
)
}
match defs.get(a.name) {
None =>
out.push(
diagnostic(
"attribute.undefined",
"attribute has no definition",
location=a.location,
),
)
Some(d) => {
if a.target is Some(target) && object_scope(target) != d.scope {
out.push(
diagnostic(
"attribute.scope",
"attribute assignment uses wrong scope",
location=a.location,
),
)
}
let valid_enum_default = a.target is None &&
(match (d.kind, a.value) {
(EnumType(choices), Text(label)) => choices.contains(label)
_ => false
})
if !valid_enum_default && !attribute_value_valid(d.kind, a.value) {
out.push(
diagnostic(
"attribute.value",
"attribute value/default violates type or bounds",
location=a.location,
),
)
}
}
}
}
}
///|
fn attribute_value_valid(kind : AttributeType, value : AttributeValue) -> Bool {
match (kind, value) {
(IntType(lo, hi) | HexType(lo, hi), Integer(n)) => n >= lo && n <= hi
(FloatType(lo, hi), Real(n)) => finite(n) && n >= lo && n <= hi
(FloatType(lo, hi), Integer(n)) =>
n.to_double() >= lo && n.to_double() <= hi
(StringType, Text(_)) => true
(EnumType(choices), Integer(n)) =>
n >= 0L && n < choices.length().to_int64()
_ => false
}
}
///|
/// Resolve an explicit assignment or fall back to the attribute's default.
pub fn Database::attribute_value(
self : Database,
name : String,
target : ObjectRef,
) -> AttributeValue? {
for a in self.attributes {
if a.name == name && a.target == Some(target) {
return Some(a.value)
}
}
for d in self.attribute_definitions {
if d.name == name && d.scope == object_scope(target) {
for a in self.attributes {
if a.name == name && a.target is None {
return Some(a.value)
}
}
}
}
None
}