///|
/// Errors from the extended DBC reader.
pub suberror DbcExtendedError {
InvalidMessage
InvalidSignal
InvalidNumber
MissingMessage
InvalidRange
} derive(Debug)
///|
/// A parse result that records ignored non-semantic lines.
pub struct DbcParseReport {
messages : Array[Message]
ignored_lines : Int
signal_lines : Int
}
///|
/// Parse common factor, offset, range and unit fields in SG_ records.
pub fn parse_dbc_extended(
input : String,
) -> DbcParseReport raise DbcExtendedError {
let messages : Array[Message] = []
let mut current : Message? = None
let mut ignored_lines = 0
let mut signal_lines = 0
for raw in input.split("\n") {
let line = raw.trim()
if line.is_empty() ||
line.has_prefix(";") ||
line.has_prefix("VERSION") ||
line.has_prefix("NS_") ||
line.has_prefix("BS_") ||
line.has_prefix("BU_") {
ignored_lines += 1
} else if line.has_prefix("BO_ ") {
let fields = dbc_words(line)
if fields.length() < 4 {
raise InvalidMessage
}
let id = dbc_uint(fields[1])
let name = dbc_before(fields[2], ":")
let dlc = dbc_int(fields[3])
match current {
Some(previous) => messages.push(previous)
None => ()
}
current = Some(message(id, name, dlc))
} else if line.has_prefix("SG_ ") {
match current {
None => raise MissingMessage
Some(item) => {
let fields = dbc_words(line)
if fields.length() < 4 {
raise InvalidSignal
}
let layout = dbc_split(fields[3], "|")
if layout.length() != 2 {
raise InvalidSignal
}
let start_bit = dbc_int(layout[0])
let size_endian = dbc_split(layout[1], "@")
let size = dbc_int(size_endian[0])
let endian_sign = if size_endian.length() > 1 {
size_endian[1]
} else {
"1+"
}
let factor_offset = if fields.length() > 4 {
dbc_parenthesized(fields[4])
} else {
["1", "0"]
}
if factor_offset.length() != 2 {
raise InvalidNumber
}
let range = if fields.length() > 5 {
dbc_parenthesized(fields[5])
} else {
["0", "0"]
}
if range.length() != 2 {
raise InvalidRange
}
let unit = if fields.length() > 6 {
fields[6].replace_all(old="\"", new="")
} else {
""
}
item.add_signal(
signal(
fields[1],
start_bit,
size,
little_endian=endian_sign.has_prefix("1"),
signed=endian_sign.has_suffix("-"),
factor=dbc_double(factor_offset[0]),
offset=dbc_double(factor_offset[1]),
minimum=dbc_double(range[0]),
maximum=dbc_double(range[1]),
unit~,
),
)
signal_lines += 1
}
}
} else {
ignored_lines += 1
}
}
match current {
Some(last) => messages.push(last)
None => ()
}
{ messages, ignored_lines, signal_lines }
}
///|
pub fn DbcParseReport::messages(self : DbcParseReport) -> Array[Message] {
self.messages.copy()
}
///|
pub fn DbcParseReport::message_count(self : DbcParseReport) -> Int {
self.messages.length()
}
///|
pub fn DbcParseReport::ignored_lines(self : DbcParseReport) -> Int {
self.ignored_lines
}
///|
pub fn DbcParseReport::signal_lines(self : DbcParseReport) -> Int {
self.signal_lines
}
///|
/// Return a message's signals ordered by start bit.
pub fn Message::signals_by_start_bit(self : Message) -> Array[Signal] {
let result = self.signals.copy()
result.sort_by((left, right) => {
if left.start_bit < right.start_bit {
-1
} else if left.start_bit > right.start_bit {
1
} else {
0
}
})
result
}
///|
/// Return a message's maximum configured bit end.
pub fn Message::required_bytes(self : Message) -> Int {
let mut maximum = 0
for item in self.signals {
let end = item.start_bit + item.size
if end > maximum {
maximum = end
}
}
(maximum + 7) / 8
}
///|
/// Return a stable schema summary for tooling.
pub fn Message::schema_text(self : Message) -> String {
let builder = StringBuilder()
builder.write_string(self.name)
builder.write_string("[")
for index, item in self.signals {
if index > 0 {
builder.write_string(",")
}
builder.write_string(item.name)
builder.write_string(":")
builder.write_string(item.size.to_string())
}
builder.write_string("]")
builder.to_string()
}
///|
fn dbc_words(input : StringView) -> Array[String] {
let result : Array[String] = []
for part in input.split(" ") {
if !part.is_empty() {
result.push(part.to_owned())
}
}
result
}
///|
fn dbc_split(input : String, separator : String) -> Array[String] {
let result : Array[String] = []
for part in input.split(separator) {
result.push(part.to_owned())
}
result
}
///|
fn dbc_before(input : String, separator : String) -> String {
match input.split_once(separator) {
Some((prefix, _)) => prefix.to_owned()
None => input
}
}
///|
fn dbc_parenthesized(input : String) -> Array[String] {
let trimmed = input.trim(chars="()[]")
let separator = if input.contains("|") { "|" } else { "," }
dbc_split(trimmed.to_owned(), separator)
}
///|
fn dbc_int(value : String) -> Int raise DbcExtendedError {
@strconv.from_str(value) catch {
_ => raise InvalidNumber
}
}
///|
fn dbc_uint(value : String) -> UInt raise DbcExtendedError {
@strconv.from_str(value) catch {
_ => raise InvalidNumber
}
}
///|
fn dbc_double(value : String) -> Double raise DbcExtendedError {
@strconv.from_str(value) catch {
_ => raise InvalidNumber
}
}