///|
/// A parse attempt summary that keeps errors machine-readable.
pub struct ParseSummary {
ok : Bool
signal_count : Int
change_count : Int
end_time : Int64
error : String?
} derive(Debug, Eq)
///|
/// Classify a parse error without exposing implementation details.
pub fn parse_error_code(error : ParseError) -> String {
match error {
InvalidDirective(_) => "invalid-directive"
InvalidVariable(_) => "invalid-variable"
InvalidValue(_) => "invalid-value"
InvalidTime(_) => "invalid-time"
EmptyInput => "empty-input"
MissingEnddefinitions => "missing-enddefinitions"
ScopeUnderflow => "scope-underflow"
DuplicateIdentifier(_) => "duplicate-identifier"
UnknownIdentifier(_) => "unknown-identifier"
UnsupportedValue(_) => "unsupported-value"
}
}
///|
/// Render a parse error with its original payload.
pub fn parse_error_message(error : ParseError) -> String {
match error {
InvalidDirective(value) => "invalid directive: " + value
InvalidVariable(value) => "invalid variable: " + value
InvalidValue(value) => "invalid value: " + value
InvalidTime(value) => "invalid time: " + value
EmptyInput => "input is empty"
MissingEnddefinitions => "missing $enddefinitions"
ScopeUnderflow => "$upscope has no matching $scope"
DuplicateIdentifier(value) => "duplicate identifier: " + value
UnknownIdentifier(value) => "unknown identifier: " + value
UnsupportedValue(value) => "unsupported value: " + value
}
}
///|
/// Parse text while returning a summary instead of raising or branching at call sites.
pub fn summarize_parse(input : String) -> ParseSummary {
match parse(input) {
Ok(trace) =>
{
ok: true,
signal_count: trace.signals.length(),
change_count: trace.changes.length(),
end_time: trace.end_time,
error: None,
}
Err(error) =>
{
ok: false,
signal_count: 0,
change_count: 0,
end_time: 0L,
error: Some(parse_error_message(error)),
}
}
}
///|
/// Parse every non-empty document in a line-separated batch.
pub fn parse_batch_safely(input : String) -> Array[ParseSummary] {
input
.split("\n\n")
.filter(fn(item) { item.trim().to_owned() != "" })
.map(fn(item) { summarize_parse(item.to_owned()) })
.to_array()
}
///|
/// Return whether a string has the minimum VCD structure.
pub fn looks_like_vcd(input : String) -> Bool {
input.contains("$enddefinitions") && input.contains("$var")
}
///|
/// Return the number of non-empty physical lines in an input.
pub fn physical_line_count(input : String) -> Int {
input.split("\n").count_if(fn(line) { line.trim().to_owned() != "" })
}
///|
/// Validate a text document and return a diagnostic suitable for a CLI.
pub fn text_diagnostic(input : String) -> String {
let summary = summarize_parse(input)
match summary.error {
Some(error) => "error: " + error
None =>
"ok: signals=" +
summary.signal_count.to_string() +
" changes=" +
summary.change_count.to_string() +
" end=" +
summary.end_time.to_string()
}
}