///|
/// A detailed diagnostic emitted by deep waveform validation.
pub struct TraceDiagnostic {
severity : String
code : String
signal_id : String?
time : Int64?
message : String
} derive(Debug, Eq)
///|
/// Validate declarations, change ordering, value widths, and metadata.
pub fn deep_validate(trace : Trace) -> Array[TraceDiagnostic] {
let result : Array[TraceDiagnostic] = []
if trace.signals.length() == 0 {
result.push({
severity: "error",
code: "no-signals",
signal_id: None,
time: None,
message: "trace declares no signals",
})
}
let ids : Array[String] = []
for signal in trace.signals {
if ids.contains(signal.id) {
result.push({
severity: "error",
code: "duplicate-id",
signal_id: Some(signal.id),
time: None,
message: "signal identifier is declared more than once",
})
} else {
ids.push(signal.id)
}
if signal.width <= 0 {
result.push({
severity: "error",
code: "invalid-width",
signal_id: Some(signal.id),
time: None,
message: "signal width must be positive",
})
}
if signal.name == "" {
result.push({
severity: "warning",
code: "empty-name",
signal_id: Some(signal.id),
time: None,
message: "signal has an empty leaf name",
})
}
}
let mut previous_time : Int64? = None
for change in trace.changes {
match previous_time {
Some(time) if change.time < time =>
result.push({
severity: "error",
code: "time-order",
signal_id: Some(change.id),
time: Some(change.time),
message: "changes are not monotonic by timestamp",
})
_ => ()
}
previous_time = Some(change.time)
if !ids.contains(change.id) {
result.push({
severity: "error",
code: "unknown-id",
signal_id: Some(change.id),
time: Some(change.time),
message: "change references an undeclared identifier",
})
continue
}
match find_signal(trace, change.id) {
Some(signal) if change.value.width() > 0 &&
change.value.width() > signal.width =>
result.push({
severity: "warning",
code: "width-overflow",
signal_id: Some(change.id),
time: Some(change.time),
message: "value is wider than the declaration",
})
_ => ()
}
if change.time < 0L || change.time > trace.end_time {
result.push({
severity: "warning",
code: "time-range",
signal_id: Some(change.id),
time: Some(change.time),
message: "change is outside the trace time range",
})
}
}
match trace.header.timescale {
Some(text) =>
match parse_timescale(text) {
Ok(_) => ()
Err(_) =>
result.push({
severity: "warning",
code: "timescale",
signal_id: None,
time: None,
message: "header timescale is not recognized",
})
}
None =>
result.push({
severity: "warning",
code: "missing-timescale",
signal_id: None,
time: None,
message: "trace has no timescale header",
})
}
result
}
///|
/// Return whether deep validation reports no errors.
pub fn deep_is_valid(trace : Trace) -> Bool {
!deep_validate(trace).any(fn(item) { item.severity == "error" })
}
///|
/// Return a count of diagnostics grouped by severity.
pub fn diagnostic_counts(items : Array[TraceDiagnostic]) -> (Int, Int) {
let mut errors = 0
let mut warnings = 0
for item in items {
if item.severity == "error" {
errors += 1
} else {
warnings += 1
}
}
(errors, warnings)
}
///|
/// Render detailed validation diagnostics as text.
pub fn diagnostics_report(items : Array[TraceDiagnostic]) -> String {
let out : Array[String] = []
for item in items {
let signal = match item.signal_id {
Some(id) => " signal=" + id
None => ""
}
let time = match item.time {
Some(value) => " time=" + value.to_string()
None => ""
}
out.push(
item.severity + " " + item.code + signal + time + ": " + item.message,
)
}
if out.length() == 0 {
"ok"
} else {
out.join("\n")
}
}
///|
/// Return a trace after dropping changes with unknown identifiers.
pub fn drop_unknown_changes(trace : Trace) -> Trace {
let ids = trace_signal_ids(trace)
let changes = trace.changes.filter(fn(change) { ids.contains(change.id) })
{
header: trace.header,
signals: trace.signals,
changes,
end_time: trace.end_time,
}
}
///|
/// Return a trace after sorting changes and removing redundant writes.
pub fn repair_trace(trace : Trace) -> Trace {
compact_trace(sort_trace_changes(drop_unknown_changes(trace)))
}