///|
fn words(line : String) -> Array[String] {
line
.split(" ")
.filter(fn(x) { x != "" })
.map(fn(x) { x.to_owned() })
.collect()
}
///|
fn parse_time(value : String) -> Result[Int64, ParseError] {
if value.trim().to_owned() == "" {
return Err(InvalidTime(value))
}
let mut result : Int64 = 0L
for char in value {
if !char.is_ascii_digit() {
return Err(InvalidTime(value))
}
result = result * 10L + (char.to_int().to_int64() - 48L)
}
Ok(result)
}
///|
fn parse_width(value : String) -> Int? {
if value == "" {
return None
}
let mut result = 0
for char in value {
if !char.is_ascii_digit() {
return None
}
result = result * 10 + char.to_int() - 48
}
Some(result)
}
///|
fn parse_value(line : String, time : Int64) -> Result[Change, ParseError] {
let fields : Array[String] = line
.split(" ")
.filter(fn(x) { x != "" })
.map(fn(x) { x.to_owned() })
.collect()
if fields.length() == 0 {
return Err(InvalidValue(line))
}
let token = fields[0]
let value = token.get_char(0).unwrap()
if value == '0' ||
value == '1' ||
value == 'x' ||
value == 'X' ||
value == 'z' ||
value == 'Z' {
if token.length() <= 1 {
Err(InvalidValue(line))
} else {
Ok({
time,
id: token.get_view(start=1).unwrap().to_owned(),
value: Scalar(value),
})
}
} else if value == 'b' || value == 'B' {
if fields.length() < 2 || token.length() <= 1 {
if token.length() <= 1 {
return Err(UnsupportedValue(line))
}
Err(InvalidValue(line))
} else {
Ok({
time,
id: fields[1],
value: Vector(token.get_view(start=1).unwrap().to_owned()),
})
}
} else if value == 'r' || value == 'R' {
if fields.length() < 2 || token.length() <= 1 {
if token.length() <= 1 {
return Err(UnsupportedValue(line))
}
Err(InvalidValue(line))
} else {
match parse_real(token.get_view(start=1).unwrap().to_owned()) {
Some(number) => Ok({ time, id: fields[1], value: Real(number) })
None => Err(InvalidValue(line))
}
}
} else {
Err(InvalidValue(line))
}
}
///|
fn parse_real(value : String) -> Double? {
let text = value.trim().to_owned()
if text == "" {
return None
}
let mut position = 0
let mut negative = false
let mut seen_digit = false
let mut after_decimal = false
let mut whole = 0.0
let mut fraction = 0.0
let mut divisor = 10.0
for char in text {
if position == 0 && (char == '-' || char == '+') {
negative = char == '-'
} else if char == '.' && !after_decimal {
after_decimal = true
} else if char.is_ascii_digit() {
seen_digit = true
let digit = (char.to_int() - 48).to_double()
if after_decimal {
fraction += digit / divisor
divisor *= 10.0
} else {
whole = whole * 10.0 + digit
}
} else {
return None
}
position += 1
}
if !seen_digit {
None
} else if negative {
Some(0.0 - whole - fraction)
} else {
Some(whole + fraction)
}
}
///|
/// Parse a VCD document held in memory.
pub fn parse(input : String) -> Result[Trace, ParseError] {
if input.trim().to_owned() == "" {
return Err(EmptyInput)
}
let lines : Array[String] = input
.split("\n")
.map(fn(x) { x.to_owned() })
.collect()
let mut date : String? = None
let mut version : String? = None
let mut timescale : String? = None
let mut comment : String? = None
let scope : Array[String] = []
let signals : Array[Signal] = []
let changes : Array[Change] = []
let mut time : Int64 = 0L
let mut definitions = true
let mut saw_enddefinitions = false
let mut i = 0
while i < lines.length() {
let line = lines[i].trim().to_owned()
if line == "" {
i += 1
continue
}
let ws = words(line)
if definitions {
if line.has_prefix("$date") {
date = Some(
line
.replace(old="$date", new="")
.replace(old="$end", new="")
.trim()
.to_owned(),
)
} else if line.has_prefix("$version") {
version = Some(
line
.replace(old="$version", new="")
.replace(old="$end", new="")
.trim()
.to_owned(),
)
} else if line.has_prefix("$timescale") {
timescale = Some(
line
.replace(old="$timescale", new="")
.replace(old="$end", new="")
.trim()
.to_owned(),
)
} else if line.has_prefix("$comment") {
comment = Some(
line
.replace(old="$comment", new="")
.replace(old="$end", new="")
.trim()
.to_owned(),
)
} else if line.has_prefix("$scope") {
if ws.length() < 3 {
return Err(InvalidDirective(line))
}
scope.push(ws[2])
} else if line.has_prefix("$upscope") {
if scope.length() == 0 {
return Err(ScopeUnderflow)
}
ignore(scope.pop())
} else if line.has_prefix("$var") {
if ws.length() < 5 {
return Err(InvalidVariable(line))
}
let width = match parse_width(ws[2]) {
Some(value) if value > 0 => value
_ => return Err(InvalidVariable(line))
}
if signals.any(fn(signal) { signal.id == ws[3] }) {
return Err(DuplicateIdentifier(ws[3]))
}
signals.push({
kind: ws[1],
width,
id: ws[3],
name: ws[4],
scope: scope.copy(),
})
} else if line.has_prefix("$enddefinitions") {
definitions = false
saw_enddefinitions = true
}
} else if line == "$dumpvars" ||
line == "$end" ||
line == "$dumpon" ||
line == "$dumpoff" ||
line == "$dumpall" ||
line == "$dumpportsall" {
// VCD control blocks delimit value assignments but do not change state.
()
} else if line.has_prefix("#") {
match parse_time(line.get_view(start=1).unwrap().trim().to_owned()) {
Ok(n) => time = n
Err(e) => return Err(e)
}
} else {
match parse_value(line, time) {
Ok(change) => changes.push(change)
Err(e) => return Err(e)
}
}
i += 1
}
if !saw_enddefinitions {
return Err(MissingEnddefinitions)
}
for change in changes {
if !signals.any(fn(signal) { signal.id == change.id }) {
return Err(UnknownIdentifier(change.id))
}
}
Ok({
header: { date, version, timescale, comment },
signals,
changes,
end_time: time,
})
}