///|
/// Text-level inventory collected before full VCD parsing.
pub struct SourceStatistics {
lines : Int
nonempty_lines : Int
bytes : Int
directives : Int
timestamps : Int
assignments : Int
comments : Int
} derive(Debug, Eq)
///|
/// Count physical VCD constructs without allocating a Trace.
pub fn source_statistics(input : String) -> SourceStatistics {
let mut lines = 0
let mut nonempty_lines = 0
let mut directives = 0
let mut timestamps = 0
let mut assignments = 0
let mut comments = 0
for raw in input.split("\n") {
lines += 1
let line = raw.trim().to_owned()
if line == "" {
continue
}
nonempty_lines += 1
if line.has_prefix("$") {
directives += 1
if line.has_prefix("$comment") {
comments += 1
}
} else if line.has_prefix("#") {
timestamps += 1
} else {
assignments += 1
}
}
{
lines,
nonempty_lines,
bytes: input.length(),
directives,
timestamps,
assignments,
comments,
}
}
///|
/// Normalize CRLF, CR, trailing whitespace, and repeated blank lines.
pub fn normalize_vcd_text(input : String) -> String {
let normalized = input
.replace_all(old="\r\n", new="\n")
.replace_all(old="\r", new="\n")
let out = StringBuilder()
let mut previous_blank = false
for raw in normalized.split("\n") {
let line = raw.trim_end().to_owned()
let blank = line.trim().to_owned() == ""
if blank && previous_blank {
continue
}
out.write_string(line)
out.write_char('\n')
previous_blank = blank
}
out.to_string()
}
///|
/// Add a final newline when a generated VCD is embedded in a stream.
pub fn ensure_final_newline(input : String) -> String {
if input.has_suffix("\n") {
input
} else {
input + "\n"
}
}
///|
/// Return a compact source inventory for diagnostics and benchmark labels.
pub fn source_statistics_line(stats : SourceStatistics) -> String {
"lines=" +
stats.lines.to_string() +
" nonempty=" +
stats.nonempty_lines.to_string() +
" bytes=" +
stats.bytes.to_string() +
" directives=" +
stats.directives.to_string() +
" timestamps=" +
stats.timestamps.to_string() +
" assignments=" +
stats.assignments.to_string() +
" comments=" +
stats.comments.to_string()
}
///|
/// Return whether the text contains a complete definition block.
pub fn has_complete_definition_block(input : String) -> Bool {
input.contains("$enddefinitions") &&
input.contains("$scope") &&
input.contains("$var")
}
///|
/// Return whether a line is a VCD control directive.
pub fn is_vcd_directive(line : String) -> Bool {
line.trim().to_owned().has_prefix("$")
}
///|
/// Return whether a line is a timestamp marker.
pub fn is_vcd_timestamp(line : String) -> Bool {
line.trim().to_owned().has_prefix("#")
}
///|
/// Return all directive names in source order.
pub fn directive_names(input : String) -> Array[String] {
let result : Array[String] = []
for raw in input.split("\n") {
let line = raw.trim().to_owned()
if !line.has_prefix("$") {
continue
}
let fields = words(line)
if fields.length() > 0 {
let name = fields[0]
if !result.contains(name) {
result.push(name)
}
}
}
result
}
///|
/// Return a stable directive inventory line.
pub fn directive_inventory(input : String) -> String {
directive_names(input).join(",")
}
///|
/// Parse normalized text after repairing common line-ending issues.
pub fn parse_normalized(input : String) -> Result[Trace, ParseError] {
parse(normalize_vcd_text(ensure_final_newline(input)))
}
///|
/// Parse a batch and retain one diagnostic per source document.
pub fn parse_text_batch(inputs : Array[String]) -> Array[ParseSummary] {
inputs.map(fn(input) { summarize_parse(input) })
}
///|
/// Return the number of parseable documents in a batch.
pub fn parseable_count(summaries : Array[ParseSummary]) -> Int {
summaries.count_if(fn(summary) { summary.ok })
}
///|
/// Return whether two source texts normalize to the same bytes.
pub fn normalized_text_equal(left : String, right : String) -> Bool {
normalize_vcd_text(left) == normalize_vcd_text(right)
}
///|
/// Produce an explicit source/trace consistency line.
pub fn parser_consistency_report(source : String, trace : Trace) -> String {
let source_info = source_statistics(source)
"source=" +
source_statistics_line(source_info) +
" trace_signals=" +
trace.signals.length().to_string() +
" trace_changes=" +
trace.changes.length().to_string() +
" declarations=" +
trace.signals.all(fn(_) { source.contains("$var") }).to_string()
}