///|
/// A group of signals sharing one hierarchical scope.
pub struct SignalGroup {
path : String
signals : Array[Signal]
} derive(Debug)
///|
/// Return path components for a signal.
pub fn signal_path_parts(signal : Signal) -> Array[String] {
let parts = signal.scope.copy()
parts.push(signal.name)
parts
}
///|
/// Return the leaf name of a signal path.
pub fn signal_leaf_name(signal : Signal) -> String {
signal.name
}
///|
/// Return the nearest parent scope, or an empty string for a root signal.
pub fn signal_parent_path(signal : Signal) -> String {
signal.scope.join(".")
}
///|
/// Return the number of hierarchy levels used by a signal.
pub fn signal_depth(signal : Signal) -> Int {
signal.scope.length()
}
///|
/// Return whether two declarations live in the same scope.
pub fn same_signal_scope(left : Signal, right : Signal) -> Bool {
left.scope == right.scope
}
///|
/// Group declarations by their exact parent scope.
pub fn signal_groups(trace : Trace) -> Array[SignalGroup] {
let result : Array[SignalGroup] = []
for signal in trace.signals {
let path = signal_parent_path(signal)
match result.search_by(fn(group) { group.path == path }) {
Some(index) => {
let group = result[index]
group.signals.push(signal)
}
None => result.push({ path, signals: [signal] })
}
}
result
}
///|
/// Return all unique scope paths from shallow to deep order.
pub fn scope_paths(trace : Trace) -> Array[String] {
let result : Array[String] = []
for signal in trace.signals {
let current : Array[String] = []
for part in signal.scope {
current.push(part)
let path = current.join(".")
if !result.contains(path) {
result.push(path)
}
}
}
result
}
///|
/// Return signals declared directly in a scope.
pub fn signals_in_scope(trace : Trace, scope : String) -> Array[Signal] {
trace.signals.filter(fn(signal) { signal_parent_path(signal) == scope })
}
///|
/// Return signals whose fully-qualified path matches a small glob language.
pub fn find_signals_glob(trace : Trace, pattern : String) -> Array[Signal] {
trace.signals.filter(fn(signal) { glob_matches(pattern, signal_path(signal)) })
}
///|
/// Return signal paths containing a case-insensitive substring.
pub fn find_signal_names(trace : Trace, needle : String) -> Array[Signal] {
let query = needle.to_lower()
trace.signals.filter(fn(signal) {
signal_path(signal).to_lower().contains(query)
})
}
///|
/// Return the deepest declared scope depth.
pub fn maximum_scope_depth(trace : Trace) -> Int {
trace.signals.fold(init=0, fn(depth, signal) {
Int::max(depth, signal_depth(signal))
})
}
///|
/// Return one-line hierarchy summary rows.
pub fn hierarchy_report(trace : Trace) -> String {
let out : Array[String] = []
for path in scope_paths(trace) {
out.push(
path +
": " +
signals_in_scope(trace, path).length().to_string() +
" signals",
)
}
if out.length() == 0 {
""
} else {
out.join("\n")
}
}
///|
/// Match `*` against any number of characters and `?` against one character.
pub fn glob_matches(pattern : String, text : String) -> Bool {
glob_match_at(pattern.to_array(), 0, text.to_array(), 0)
}
///|
fn glob_match_at(
pattern : Array[Char],
pi : Int,
text : Array[Char],
ti : Int,
) -> Bool {
if pi == pattern.length() {
return ti == text.length()
}
if pattern[pi] == '*' {
if glob_match_at(pattern, pi + 1, text, ti) {
return true
}
if ti < text.length() {
return glob_match_at(pattern, pi, text, ti + 1)
}
false
} else if ti == text.length() {
false
} else if pattern[pi] == '?' || pattern[pi] == text[ti] {
glob_match_at(pattern, pi + 1, text, ti + 1)
} else {
false
}
}
///|
/// Return a stable table of path, width, and declaration kind.
pub fn signal_catalog(trace : Trace) -> Array[(String, Int, String)] {
trace.signals.map(fn(signal) {
(signal_path(signal), signal.width, signal.kind)
})
}
///|
/// Return the widest declaration in a trace.
pub fn widest_signal(trace : Trace) -> Signal? {
let mut result : Signal? = None
for signal in trace.signals {
match result {
Some(current) if current.width >= signal.width => ()
_ => result = Some(signal)
}
}
result
}