///|
fn words(text : StringView) -> Array[StringView] {
let result : Array[StringView] = []
for part in text.split(" ") {
let word = part.trim(chars=" \t")
if !word.is_empty() {
result.push(word)
}
}
result
}
///|
fn parse_int(text : StringView) -> Int? {
Some(@strconv.parse_int(text)) catch {
_ => None
}
}
///|
fn parse_uint(text : StringView) -> UInt? {
Some(@strconv.parse_uint(text)) catch {
_ => None
}
}
///|
fn parse_int64(text : StringView) -> Int64? {
Some(@strconv.parse_int64(text)) catch {
_ => None
}
}
///|
fn parse_double(text : StringView) -> Double? {
Some(@strconv.parse_double(text)) catch {
_ => None
}
}
///|
priv struct PendingValueTable {
line : Int
message_id : UInt
signal_name : String
entries : Array[ValueDescription]
declaration : SourceSpan
}
///|
priv struct PendingGlobalValueTable {
line : Int
name : String
entries : Array[ValueDescription]
declaration : SourceSpan
}
///|
priv struct PendingEnvironmentData {
line : Int
name : String
size : UInt
}
///|
fn parse_environment_variable_line(
line : StringView,
) -> Result[EnvironmentVariable, String] {
if !line.has_suffix(";") {
return Err("environment variable declaration is missing ';'")
}
let body = line[4:line.length() - 1].trim()
let colon = match body.find(":") {
Some(value) => value
None => return Err("environment variable declaration is missing ':'")
}
let name = body[:colon].trim()
if !dbc_token_is_valid(name) {
return Err("environment variable name is invalid")
}
let fields = words(body[colon + 1:])
if fields.length() < 2 {
return Err("environment variable type or range is missing")
}
let value_type = match parse_int(fields[0]) {
Some(value) if value >= 0 && value <= 2 => value
_ => return Err("environment variable type must be 0, 1, or 2")
}
let rest = body[colon + 1:].trim()
let range_start = match rest.find("[") {
Some(value) => value
None => return Err("environment variable range is missing")
}
if words(rest[:range_start]).length() != 1 {
return Err("environment variable type is malformed")
}
let range_end = match find_after(rest, range_start + 1, "]") {
Some(value) => value
None => return Err("environment variable range is missing ']'")
}
let bounds = rest[range_start + 1:range_end].split("|").collect()
if bounds.length() != 2 {
return Err("environment variable range requires minimum and maximum")
}
let minimum = match parse_double(bounds[0].trim()) {
Some(value) => value
None => return Err("environment variable minimum is invalid")
}
let maximum = match parse_double(bounds[1].trim()) {
Some(value) => value
None => return Err("environment variable maximum is invalid")
}
let (unit, tail) = match
parse_value_description_label(
rest[range_end + 1:].trim(),
"environment variable unit",
) {
Ok(value) => value
Err(message) => return Err(message)
}
let tail_fields = words(tail)
if tail_fields.length() != 4 {
return Err(
"environment variable requires initial value, id, access type, and nodes",
)
}
let initial_value = match parse_double(tail_fields[0]) {
Some(value) => value
None => return Err("environment variable initial value is invalid")
}
let id = match parse_uint(tail_fields[1]) {
Some(value) => value
None => return Err("environment variable id is not unsigned")
}
if !tail_fields[2].has_prefix("DUMMY_NODE_VECTOR") {
return Err("environment variable access type is invalid")
}
let access_type = match parse_int(tail_fields[2][17:]) {
Some(value) if value >= 0 && value <= 3 => value
_ => return Err("environment variable access type must be 0 through 3")
}
let access_nodes : Array[String] = []
for raw_node in tail_fields[3].split(",") {
let node = raw_node.trim()
if !dbc_token_is_valid(node) {
return Err("environment variable access node is invalid")
}
access_nodes.push(node.to_owned())
}
Ok({
name: name.to_owned(),
value_type,
minimum,
maximum,
unit,
initial_value,
id,
access_type,
access_nodes,
data_size: None,
})
}
///|
fn parse_environment_data_line(
line : StringView,
) -> Result[(String, UInt), String] {
if !line.has_suffix(";") {
return Err("environment data declaration is missing ';'")
}
let body = line[12:line.length() - 1].trim()
let colon = match body.find(":") {
Some(value) => value
None => return Err("environment data declaration is missing ':'")
}
let name = body[:colon].trim()
if !dbc_token_is_valid(name) {
return Err("environment data variable name is invalid")
}
let size = match parse_uint(body[colon + 1:].trim()) {
Some(value) => value
None => return Err("environment data size is not unsigned")
}
Ok((name.to_owned(), size))
}
///|
priv struct PendingDbcComment {
line : Int
target : DbcCommentTarget
text : String
declaration : SourceSpan
}
///|
priv struct PendingMessageTransmitters {
line : Int
message_id : UInt
transmitters : Array[String]
}
///|
priv struct PendingSignalValueType {
line : Int
message_id : UInt
signal_name : String
value_type : SignalValueType
}
///|
priv struct PendingMultiplexRanges {
line : Int
message_id : UInt
signal_name : String
selector_name : String
ranges : Array[MultiplexRange]
}
///|
priv struct PendingSignalGroup {
line : Int
message_id : UInt
name : String
repetition : Int
signals : Array[String]
declaration : SourceSpan
}
///|
priv struct PendingAttributeDefault {
line : Int
name : String
raw_value : String
}
///|
priv struct PendingAttributeAssignment {
line : Int
name : String
target : DbcAttributeTarget
raw_value : String
}
///|
fn find_after(text : StringView, start : Int, needle : StringView) -> Int? {
if start < 0 || start > text.length() {
return None
}
match text[start:].find(needle) {
Some(offset) => Some(start + offset)
None => None
}
}
///|
fn parse_message_line(line : StringView) -> Result[Message, String] {
let body = line[3:].trim()
let colon = match body.find(":") {
Some(value) => value
None => return Err("message declaration is missing ':'")
}
let header = words(body[:colon])
if header.length() != 2 {
return Err("message declaration requires an id and name")
}
let id = match parse_uint(header[0]) {
Some(value) => value
None => return Err("message id is not an unsigned integer")
}
if dbc_message_id_parts(id) is None {
return Err(
"message id must be an 11-bit standard id or a bit-31 flagged 29-bit id",
)
}
let tail = words(body[colon + 1:])
if tail.length() < 2 {
return Err("message declaration requires payload size and transmitter")
}
let payload_size = match parse_int(tail[0]) {
Some(value) => value
None => return Err("message payload size is not an integer")
}
if payload_size < 0 || payload_size > 64 {
return Err("message payload size must be between 0 and 64 bytes")
}
Ok({
id,
name: header[1].to_owned(),
payload_size,
transmitter: tail[1].to_owned(),
additional_transmitters: [],
signals: [],
})
}
///|
fn parse_message_transmitters_line(
line : StringView,
) -> Result[(UInt, Array[String]), String] {
let body = line[9:].trim()
if !body.has_suffix(";") {
return Err("message transmitter declaration is missing ';'")
}
let content = body[:body.length() - 1].trim()
let colon = match content.find(":") {
Some(value) => value
None => return Err("message transmitter declaration is missing ':'")
}
let header = words(content[:colon])
if header.length() != 1 {
return Err("message transmitter declaration requires one message id")
}
let message_id = match parse_uint(header[0]) {
Some(value) => value
None => return Err("message transmitter id is not an unsigned integer")
}
if dbc_message_id_parts(message_id) is None {
return Err("message transmitter id is outside the DBC identifier range")
}
let transmitter_text = content[colon + 1:].trim()
if transmitter_text.is_empty() {
return Err("message transmitter declaration requires at least one node")
}
let transmitters : Array[String] = []
for part in transmitter_text.split(",") {
let transmitter = part.trim()
if transmitter.is_empty() {
return Err("message transmitter declaration contains an empty node")
}
for existing in transmitters {
if existing.view() == transmitter {
return Err("message transmitter declaration contains a duplicate node")
}
}
transmitters.push(transmitter.to_owned())
}
Ok((message_id, transmitters))
}
///|
fn parse_signal_value_type_line(
line : StringView,
) -> Result[(UInt, String, SignalValueType), String] {
let body = line[13:].trim()
if !body.has_suffix(";") {
return Err("signal value type declaration is missing ';'")
}
let content = body[:body.length() - 1].trim()
let colon = match content.find(":") {
Some(value) => value
None => return Err("signal value type declaration is missing ':'")
}
let header = words(content[:colon])
if header.length() != 2 {
return Err("signal value type requires a message id and signal name")
}
let message_id = match parse_uint(header[0]) {
Some(value) => value
None =>
return Err("signal value type message id is not an unsigned integer")
}
if dbc_message_id_parts(message_id) is None {
return Err(
"signal value type message id is outside the DBC identifier range",
)
}
let type_fields = words(content[colon + 1:])
if type_fields.length() != 1 {
return Err("signal value type requires one type code")
}
let value_type = match type_fields[0] {
"1" => Float32
"2" => Float64
_ => return Err("signal value type code must be 1 or 2")
}
Ok((message_id, header[1].to_owned(), value_type))
}
///|
fn parse_multiplex_ranges_line(
line : StringView,
) -> Result[(UInt, String, String, Array[MultiplexRange]), String] {
let body = line[11:].trim()
if !body.has_suffix(";") {
return Err("extended multiplex declaration is missing ';'")
}
let content = body[:body.length() - 1].trim()
let fields = words(content)
if fields.length() < 4 {
return Err(
"extended multiplex declaration requires an id, signal, selector and ranges",
)
}
let message_id = match parse_uint(fields[0]) {
Some(value) if dbc_message_id_parts(value) is Some(_) => value
_ => return Err("extended multiplex message id is invalid")
}
let range_parts : Array[String] = []
for index = 3; index < fields.length(); index = index + 1 {
range_parts.push(fields[index].to_owned())
}
let range_text = range_parts.join(" ")
let ranges : Array[MultiplexRange] = []
for item in range_text.view().split(",") {
let bounds : Array[StringView] = []
for part in item.trim().split("-") {
bounds.push(part)
}
if bounds.length() != 2 {
return Err("extended multiplex range must be start-end")
}
let start = match parse_int(bounds[0].trim()) {
Some(value) if value >= 0 => value
_ => return Err("extended multiplex range start is invalid")
}
let end = match parse_int(bounds[1].trim()) {
Some(value) if value >= start => value
_ => return Err("extended multiplex range end is invalid")
}
ranges.push({ start, end, })
}
Ok((message_id, fields[1].to_owned(), fields[2].to_owned(), ranges))
}
///|
fn parse_signal_group_line(
line : StringView,
) -> Result[(UInt, String, Int, Array[String]), String] {
let body = line[10:].trim()
if !body.has_suffix(";") {
return Err("signal group declaration is missing ';'")
}
let content = body[:body.length() - 1].trim()
let colon = match content.find(":") {
Some(value) => value
None => return Err("signal group declaration is missing ':'")
}
let header = words(content[:colon])
if header.length() != 3 {
return Err("signal group requires a message id, name, and repetition")
}
let message_id = match parse_uint(header[0]) {
Some(value) if dbc_message_id_parts(value) is Some(_) => value
_ => return Err("signal group message id is invalid")
}
let repetition = match parse_int(header[2]) {
Some(value) if value >= 0 => value
_ => return Err("signal group repetition must be a nonnegative integer")
}
let signals : Array[String] = []
for member_list in content[colon + 1:].split(",") {
let names = words(member_list)
if names.is_empty() {
return Err("signal group contains an empty member")
}
for signal in names {
signals.push(signal.to_owned())
}
}
Ok((message_id, header[1].to_owned(), repetition, signals))
}
///|
fn parse_multiplex(text : StringView) -> MultiplexRole? {
if text == "M" {
return Some(Multiplexer)
}
if text.has_prefix("m") {
let value_text = if text.has_suffix("M") {
text[1:text.length() - 1]
} else {
text[1:]
}
return match parse_int(value_text) {
Some(value) if value >= 0 => Some(Multiplexed(value))
_ => None
}
}
None
}
///|
fn parse_signal_line(line : StringView) -> Result[Signal, String] {
let body = line[3:].trim()
let colon = match body.find(":") {
Some(value) => value
None => return Err("signal declaration is missing ':'")
}
let header = words(body[:colon])
if header.length() < 1 || header.length() > 2 {
return Err("signal declaration has an invalid name or multiplex marker")
}
let multiplex = if header.length() == 2 {
match parse_multiplex(header[1]) {
Some(value) => value
None => return Err("signal multiplex marker is invalid")
}
} else {
Plain
}
let layout = body[colon + 1:].trim()
let pipe = match layout.find("|") {
Some(value) => value
None => return Err("signal layout is missing '|'")
}
let at = match find_after(layout, pipe + 1, "@") {
Some(value) => value
None => return Err("signal layout is missing '@'")
}
if at + 3 > layout.length() {
return Err("signal layout is missing byte order or signedness")
}
let start_bit = match parse_int(layout[:pipe].trim()) {
Some(value) => value
None => return Err("signal start bit is not an integer")
}
let bit_length = match parse_int(layout[pipe + 1:at].trim()) {
Some(value) => value
None => return Err("signal bit length is not an integer")
}
if start_bit < 0 {
return Err("signal start bit must not be negative")
}
if bit_length < 1 || bit_length > 64 {
return Err("signal bit length must be between 1 and 64")
}
let byte_order = match layout[at + 1:at + 2] {
"0" => Motorola
"1" => Intel
_ => return Err("signal byte order must be 0 or 1")
}
let value_kind = match layout[at + 2:at + 3] {
"+" => Unsigned
"-" => Signed
_ => return Err("signal signedness must be '+' or '-'")
}
let open_paren = match find_after(layout, at + 3, "(") {
Some(value) => value
None => return Err("signal scale is missing '('")
}
let comma = match find_after(layout, open_paren + 1, ",") {
Some(value) => value
None => return Err("signal scale is missing ','")
}
let close_paren = match find_after(layout, comma + 1, ")") {
Some(value) => value
None => return Err("signal scale is missing ')'")
}
let factor = match parse_double(layout[open_paren + 1:comma].trim()) {
Some(value) => value
None => return Err("signal factor is not a number")
}
let offset = match parse_double(layout[comma + 1:close_paren].trim()) {
Some(value) => value
None => return Err("signal offset is not a number")
}
let open_range = match find_after(layout, close_paren + 1, "[") {
Some(value) => value
None => return Err("signal range is missing '['")
}
let range_pipe = match find_after(layout, open_range + 1, "|") {
Some(value) => value
None => return Err("signal range is missing '|'")
}
let close_range = match find_after(layout, range_pipe + 1, "]") {
Some(value) => value
None => return Err("signal range is missing ']'")
}
let minimum = match parse_double(layout[open_range + 1:range_pipe].trim()) {
Some(value) => value
None => return Err("signal minimum is not a number")
}
let maximum = match parse_double(layout[range_pipe + 1:close_range].trim()) {
Some(value) => value
None => return Err("signal maximum is not a number")
}
if minimum > maximum {
return Err("signal minimum must not exceed maximum")
}
let open_quote = match find_after(layout, close_range + 1, "\"") {
Some(value) => value
None => return Err("signal unit is missing an opening quote")
}
let close_quote = match find_after(layout, open_quote + 1, "\"") {
Some(value) => value
None => return Err("signal unit is missing a closing quote")
}
let receivers : Array[String] = []
let receiver_text = layout[close_quote + 1:].trim()
if !receiver_text.is_empty() {
for receiver in receiver_text.split(",") {
let name = receiver.trim()
if !name.is_empty() {
receivers.push(name.to_owned())
}
}
}
Ok({
name: header[0].to_owned(),
start_bit,
bit_length,
byte_order,
value_kind,
value_type: Integer,
factor,
offset,
minimum,
maximum,
unit: layout[open_quote + 1:close_quote].to_owned(),
receivers,
multiplex,
})
}
///|
fn parse_version_line(line : StringView) -> String? {
match line.find("\"") {
None => None
Some(first_quote) =>
match find_after(line, first_quote + 1, "\"") {
None => None
Some(second_quote) =>
Some(line[first_quote + 1:second_quote].to_owned())
}
}
}
///|
fn parse_comment_text(text : StringView) -> Result[String, String] {
let chars : Array[Char] = []
let mut escaped = false
let mut closed = false
for char in text.iter() {
if closed {
return Err("comment has text after its closing quote")
}
if escaped {
if char == '"' || char == '\\' {
chars.push(char)
} else {
chars.push('\\')
chars.push(char)
}
escaped = false
} else if char == '\\' {
escaped = true
} else if char == '"' {
closed = true
} else {
chars.push(char)
}
}
if escaped || !closed {
Err("comment is missing a closing quote")
} else {
Ok(String::from_array(chars))
}
}
///|
fn parse_comment_line(
line : StringView,
) -> Result[(DbcCommentTarget, String), String] {
let body = line[3:].trim()
if !body.has_suffix(";") {
return Err("comment declaration is missing ';'")
}
let content = body[:body.length() - 1].trim()
let open_quote = match content.find("\"") {
Some(value) => value
None => return Err("comment is missing an opening quote")
}
let text = match parse_comment_text(content[open_quote + 1:]) {
Ok(value) => value
Err(message) => return Err(message)
}
let header = words(content[:open_quote])
let target = match header {
[] => DatabaseComment
[kind, node] if kind == "BU_" => NodeComment(node.to_owned())
[kind, name] if kind == "EV_" =>
if dbc_token_is_valid(name) {
EnvironmentComment(name.to_owned())
} else {
return Err("environment comment name is invalid")
}
[kind, message_id] if kind == "BO_" =>
match parse_uint(message_id) {
Some(value) =>
if dbc_message_id_parts(value) is Some(_) {
MessageComment(value)
} else {
return Err("message comment id is outside the DBC identifier range")
}
None => return Err("message comment id is not an unsigned integer")
}
[kind, message_id, signal] if kind == "SG_" => {
let id = match parse_uint(message_id) {
Some(value) => value
None =>
return Err("signal comment message id is not an unsigned integer")
}
if dbc_message_id_parts(id) is None {
return Err(
"signal comment message id is outside the DBC identifier range",
)
}
SignalComment(id, signal.to_owned())
}
_ =>
return Err("comment target must be the database, BU_, BO_, SG_, or EV_")
}
Ok((target, text))
}
///|
fn contains_value_description(
entries : Array[ValueDescription],
value : Int64,
) -> Bool {
for entry in entries {
if entry.value == value {
return true
}
}
false
}
///|
fn parse_value_description_label(
text : StringView,
context : StringView,
) -> Result[(String, StringView), String] {
if !text.has_prefix("\"") {
return Err("\{context} is missing an opening quote")
}
let mut search = 1
let mut close_quote = -1
while close_quote < 0 {
let position = match find_after(text, search, "\"") {
Some(value) => value
None => return Err("\{context} is missing a closing quote")
}
let mut slash_count = 0
let mut before = position - 1
while before >= 0 && text[before:before + 1] == "\\" {
slash_count += 1
before -= 1
}
if slash_count % 2 == 0 {
close_quote = position
} else {
search = position + 1
}
}
let label = match parse_comment_text(text[1:close_quote + 1]) {
Ok(value) => value
Err(_) => return Err("\{context} is invalid")
}
Ok((label, text[close_quote + 1:].trim()))
}
///|
fn parse_value_line(
line : StringView,
) -> Result[(UInt, String, Array[ValueDescription]), String] {
let body = line[5:].trim()
if !body.has_suffix(";") {
return Err("value table declaration is missing ';'")
}
let content = body[:body.length() - 1].trim()
let fields = words(content)
if fields.length() < 4 {
return Err("value table requires a message id, signal name, and entry")
}
let message_id = match parse_uint(fields[0]) {
Some(value) => value
None => return Err("value table message id is not an unsigned integer")
}
if dbc_message_id_parts(message_id) is None {
return Err("value table message id is outside the DBC identifier range")
}
let signal_start = match find_after(content, fields[0].length(), fields[1]) {
Some(value) => value
None => return Err("value table signal name is missing")
}
let signal_name = fields[1].to_owned()
let mut rest = content[signal_start + fields[1].length():].trim()
let entries : Array[ValueDescription] = []
while !rest.is_empty() {
let open_quote = match rest.find("\"") {
Some(value) => value
None => return Err("value description is missing an opening quote")
}
let raw_fields = words(rest[:open_quote])
if raw_fields.length() != 1 {
return Err("value description requires one integer value")
}
let value = match parse_int64(raw_fields[0]) {
Some(value) => value
None => return Err("value description key is not an integer")
}
if contains_value_description(entries, value) {
return Err("value table contains a duplicate key")
}
let (label, remaining) = match
parse_value_description_label(rest[open_quote:], "value description") {
Ok(value) => value
Err(message) => return Err(message)
}
entries.push({ value, label, })
rest = remaining
}
Ok((message_id, signal_name, entries))
}
///|
fn parse_global_value_table_line(
line : StringView,
) -> Result[(String, Array[ValueDescription]), String] {
let body = line[10:].trim()
if !body.has_suffix(";") {
return Err("global value table declaration is missing ';'")
}
let content = body[:body.length() - 1].trim()
let first_quote = match content.find("\"") {
Some(value) => value
None => return Err("global value table requires an entry")
}
let header = words(content[:first_quote])
if header.length() != 2 {
return Err("global value table requires a name and entry value")
}
let name = header[0]
if !dbc_token_is_valid(name) {
return Err("global value table name is invalid")
}
let entries : Array[ValueDescription] = []
let mut rest = content[name.length():].trim()
while !rest.is_empty() {
let open_quote = match rest.find("\"") {
Some(value) => value
None => return Err("global value description is missing an opening quote")
}
let raw_fields = words(rest[:open_quote])
if raw_fields.length() != 1 {
return Err("global value description requires one integer value")
}
let value = match parse_int64(raw_fields[0]) {
Some(value) => value
None => return Err("global value description key is not an integer")
}
if contains_value_description(entries, value) {
return Err("global value table contains a duplicate key")
}
let (label, remaining) = match
parse_value_description_label(
rest[open_quote:],
"global value description",
) {
Ok(value) => value
Err(message) => return Err(message)
}
entries.push({ value, label, })
rest = remaining
}
Ok((name.to_owned(), entries))
}
///|
fn contains_message_id(messages : Array[Message], id : UInt) -> Bool {
for message in messages {
if message.id == id {
return true
}
}
false
}
///|
fn find_message_index(messages : Array[Message], id : UInt) -> Int? {
for index, message in messages {
if message.id == id {
return Some(index)
}
}
None
}
///|
fn contains_signal_name(message : Message, name : StringView) -> Bool {
for signal in message.signals {
if signal.name.view() == name {
return true
}
}
false
}
///|
fn find_signal_index(message : Message, name : StringView) -> Int? {
for index, signal in message.signals {
if signal.name.view() == name {
return Some(index)
}
}
None
}
///|
fn contains_signal_reference(
references : Array[(UInt, String)],
message_id : UInt,
signal_name : StringView,
) -> Bool {
for reference in references {
if reference.0 == message_id && reference.1.view() == signal_name {
return true
}
}
false
}
///|
fn contains_node(nodes : Array[String], name : StringView) -> Bool {
for node in nodes {
if node.view() == name {
return true
}
}
false
}
///|
fn contains_value_table(
tables : Array[ValueTable],
message_id : UInt,
signal_name : StringView,
) -> Bool {
for table in tables {
if table.message_id == message_id && table.signal_name.view() == signal_name {
return true
}
}
false
}
///|
fn database_has_global_value_table(
tables : Array[GlobalValueTable],
name : StringView,
) -> Bool {
for table in tables {
if table.name.view() == name {
return true
}
}
false
}
///|
fn contains_comment_target(
comments : Array[DbcComment],
target : DbcCommentTarget,
) -> Bool {
for comment in comments {
if comment.target == target {
return true
}
}
false
}
///|
fn contains_message_reference(ids : Array[UInt], id : UInt) -> Bool {
for candidate in ids {
if candidate == id {
return true
}
}
false
}
///|
fn add_diagnostic(
diagnostics : Array[Diagnostic],
line : Int,
code : String,
message : String,
) -> Unit {
diagnostics.push({ line, column: 1, code, message, })
}
///|
fn declaration_span(
raw_line : StringView,
declaration : StringView,
line : Int,
) -> SourceSpan {
let offset = match raw_line.find(declaration) {
Some(value) => value
None => 0
}
{
start_line: line,
start_column: offset + 1,
end_line: line,
end_column: offset + declaration.length() + 1,
}
}
///|
/// Parse the core declarations of a DBC document while retaining all recoverable
/// errors and declaration locations.
pub fn parse_with_source_map(source : StringView) -> SourceParseResult {
let messages : Array[Message] = []
let message_locations : Array[MessageSourceLocation] = []
let signal_locations : Array[SignalSourceLocation] = []
let comment_locations : Array[CommentSourceLocation] = []
let value_table_locations : Array[ValueTableSourceLocation] = []
let global_value_table_locations : Array[GlobalValueTableSourceLocation] = []
let environment_variable_locations : Array[EnvironmentVariableSourceLocation] = []
let signal_group_locations : Array[SignalGroupSourceLocation] = []
let nodes : Array[String] = []
let pending_value_tables : Array[PendingValueTable] = []
let value_tables : Array[ValueTable] = []
let pending_global_value_tables : Array[PendingGlobalValueTable] = []
let global_value_tables : Array[GlobalValueTable] = []
let environment_variables : Array[EnvironmentVariable] = []
let pending_environment_data : Array[PendingEnvironmentData] = []
let pending_comments : Array[PendingDbcComment] = []
let comments : Array[DbcComment] = []
let pending_message_transmitters : Array[PendingMessageTransmitters] = []
let resolved_transmitter_messages : Array[UInt] = []
let pending_signal_value_types : Array[PendingSignalValueType] = []
let resolved_signal_value_types : Array[(UInt, String)] = []
let pending_multiplex_ranges : Array[PendingMultiplexRanges] = []
let pending_signal_groups : Array[PendingSignalGroup] = []
let signal_groups : Array[SignalGroup] = []
let resolved_multiplex_ranges : Array[(UInt, String)] = []
let attributes = DbcAttributes::empty()
let pending_attribute_defaults : Array[PendingAttributeDefault] = []
let pending_attribute_assignments : Array[PendingAttributeAssignment] = []
let diagnostics : Array[Diagnostic] = []
let mut version : String? = None
let mut current_message : Int? = None
let mut in_namespace = false
let mut line_number = 0
for raw_line in source.split("\n") {
line_number += 1
let line = raw_line.trim(chars=" \t\r")
if line.is_empty() {
in_namespace = false
continue
}
if line == "NS_ :" || line == "NS_:" {
in_namespace = true
continue
}
// NS_ lists declaration names, not declarations. Never discard their
// actual occurrences elsewhere in the document.
if in_namespace &&
(line.has_suffix("_") || line == "FILTER") &&
!line.contains(" ") &&
!line.contains("\t") {
continue
}
in_namespace = false
if line == "BS_:" {
continue
}
if line.has_prefix("VERSION") {
match parse_version_line(line) {
Some(value) => version = Some(value)
None =>
add_diagnostic(
diagnostics, line_number, "DBC001", "invalid VERSION declaration",
)
}
} else if line.has_prefix("BU_:") {
for node in words(line[4:]) {
if !contains_node(nodes, node) {
nodes.push(node.to_owned())
}
}
} else if line.has_prefix("ENVVAR_DATA_ ") {
match parse_environment_data_line(line) {
Ok((name, size)) =>
pending_environment_data.push({ line: line_number, name, size, })
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC051", message)
}
} else if line.has_prefix("EV_ ") {
match parse_environment_variable_line(line) {
Ok(variable) =>
if environment_variables.any(existing => {
existing.name == variable.name
}) {
add_diagnostic(
diagnostics, line_number, "DBC050", "duplicate environment variable",
)
} else {
environment_variables.push(variable)
environment_variable_locations.push({
name: variable.name,
declaration: declaration_span(raw_line, line, line_number),
})
}
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC049", message)
}
} else if line.has_prefix("BO_TX_BU_ ") {
match parse_message_transmitters_line(line) {
Ok((message_id, transmitters)) =>
pending_message_transmitters.push({
line: line_number,
message_id,
transmitters,
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC016", message)
}
} else if line.has_prefix("SIG_VALTYPE_ ") {
match parse_signal_value_type_line(line) {
Ok((message_id, signal_name, value_type)) =>
pending_signal_value_types.push({
line: line_number,
message_id,
signal_name,
value_type,
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC020", message)
}
} else if line.has_prefix("SG_MUL_VAL_ ") {
match parse_multiplex_ranges_line(line) {
Ok((message_id, signal_name, selector_name, ranges)) =>
pending_multiplex_ranges.push({
line: line_number,
message_id,
signal_name,
selector_name,
ranges,
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC024", message)
}
} else if line.has_prefix("SIG_GROUP_ ") {
match parse_signal_group_line(line) {
Ok((message_id, name, repetition, signals)) =>
pending_signal_groups.push({
line: line_number,
message_id,
name,
repetition,
signals,
declaration: declaration_span(raw_line, line, line_number),
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC043", message)
}
} else if line.has_prefix("BA_DEF_DEF_ ") {
match parse_attribute_default_line(line) {
Ok((name, raw_value)) =>
pending_attribute_defaults.push({
line: line_number,
name,
raw_value,
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC033", message)
}
} else if line.has_prefix("BA_DEF_ ") {
match parse_attribute_definition_line(line) {
Ok(definition) =>
if find_attribute_definition(attributes.definitions, definition.name)
is Some(_) {
add_diagnostic(
diagnostics, line_number, "DBC032", "duplicate attribute definition",
)
} else {
attributes.definitions.push(definition)
}
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC031", message)
}
} else if line.has_prefix("BA_ ") {
match parse_attribute_assignment_line(line) {
Ok((name, target, raw_value)) =>
pending_attribute_assignments.push({
line: line_number,
name,
target,
raw_value,
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC037", message)
}
} else if line.has_prefix("BO_ ") {
match parse_message_line(line) {
Ok(message) =>
if contains_message_id(messages, message.id) {
add_diagnostic(
diagnostics,
line_number,
"DBC003",
"duplicate message id \{message.id}",
)
current_message = None
} else {
messages.push(message)
message_locations.push({
message_id: message.id,
declaration: declaration_span(raw_line, line, line_number),
})
current_message = Some(messages.length() - 1)
}
Err(message) => {
add_diagnostic(diagnostics, line_number, "DBC002", message)
current_message = None
}
}
} else if line.has_prefix("SG_ ") {
match current_message {
None =>
add_diagnostic(
diagnostics, line_number, "DBC004", "signal declaration appears before a valid message",
)
Some(message_index) =>
match parse_signal_line(line) {
Ok(signal) => {
let message = messages[message_index]
if contains_signal_name(message, signal.name) {
add_diagnostic(
diagnostics,
line_number,
"DBC006",
"duplicate signal name \{signal.name}",
)
} else {
message.signals.push(signal)
signal_locations.push({
message_id: message.id,
signal_name: signal.name,
declaration: declaration_span(raw_line, line, line_number),
})
}
}
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC005", message)
}
}
} else if line.has_prefix("CM_ ") {
match parse_comment_line(line) {
Ok((target, text)) =>
pending_comments.push({
line: line_number,
target,
text,
declaration: declaration_span(raw_line, line, line_number),
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC011", message)
}
} else if line.has_prefix("VAL_ ") {
match parse_value_line(line) {
Ok((message_id, signal_name, entries)) =>
pending_value_tables.push({
line: line_number,
message_id,
signal_name,
entries,
declaration: declaration_span(raw_line, line, line_number),
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC007", message)
}
} else if line.has_prefix("VAL_TABLE_ ") {
match parse_global_value_table_line(line) {
Ok((name, entries)) =>
pending_global_value_tables.push({
line: line_number,
name,
entries,
declaration: declaration_span(raw_line, line, line_number),
})
Err(message) =>
add_diagnostic(diagnostics, line_number, "DBC047", message)
}
} else {
add_diagnostic(
diagnostics, line_number, "DBC019", "unsupported DBC declaration; the source cannot be used without losing data",
)
}
}
for pending in pending_value_tables {
match find_message_index(messages, pending.message_id) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC008",
"value table refers to an unknown message",
)
Some(message_index) => {
let message = messages[message_index]
if !contains_signal_name(message, pending.signal_name) {
add_diagnostic(
diagnostics,
pending.line,
"DBC009",
"value table refers to an unknown signal",
)
} else if contains_value_table(
value_tables,
pending.message_id,
pending.signal_name,
) {
add_diagnostic(
diagnostics,
pending.line,
"DBC010",
"duplicate value table for the same signal",
)
} else {
value_tables.push({
message_id: pending.message_id,
signal_name: pending.signal_name,
entries: pending.entries,
})
value_table_locations.push({
message_id: pending.message_id,
signal_name: pending.signal_name,
declaration: pending.declaration,
})
}
}
}
}
for pending in pending_global_value_tables {
if database_has_global_value_table(global_value_tables, pending.name) {
add_diagnostic(
diagnostics,
pending.line,
"DBC048",
"duplicate global value table name",
)
} else {
global_value_tables.push({ name: pending.name, entries: pending.entries, })
global_value_table_locations.push({
name: pending.name,
declaration: pending.declaration,
})
}
}
for pending in pending_environment_data {
let mut found = false
for index, variable in environment_variables {
if variable.name == pending.name {
found = true
if variable.data_size is Some(_) {
add_diagnostic(
diagnostics,
pending.line,
"DBC053",
"duplicate environment data declaration",
)
} else {
environment_variables[index] = {
..variable,
data_size: Some(pending.size),
}
}
break
}
}
if !found {
add_diagnostic(
diagnostics,
pending.line,
"DBC052",
"environment data refers to an unknown variable",
)
}
}
for pending in pending_comments {
comment_locations.push({
target: pending.target,
declaration: pending.declaration,
})
let target_exists = match pending.target {
DatabaseComment => true
NodeComment(name) =>
if contains_node(nodes, name) {
true
} else {
add_diagnostic(
diagnostics,
pending.line,
"DBC012",
"comment refers to an unknown node",
)
false
}
MessageComment(message_id) =>
if find_message_index(messages, message_id) is Some(_) {
true
} else {
add_diagnostic(
diagnostics,
pending.line,
"DBC013",
"comment refers to an unknown message",
)
false
}
SignalComment(message_id, signal_name) =>
match find_message_index(messages, message_id) {
None => {
add_diagnostic(
diagnostics,
pending.line,
"DBC013",
"comment refers to an unknown message",
)
false
}
Some(message_index) =>
if contains_signal_name(messages[message_index], signal_name) {
true
} else {
add_diagnostic(
diagnostics,
pending.line,
"DBC014",
"comment refers to an unknown signal",
)
false
}
}
EnvironmentComment(name) =>
if environment_variables.any(variable => variable.name == name) {
true
} else {
add_diagnostic(
diagnostics,
pending.line,
"DBC054",
"comment refers to an unknown environment variable",
)
false
}
}
if target_exists {
if contains_comment_target(comments, pending.target) {
add_diagnostic(
diagnostics,
pending.line,
"DBC015",
"duplicate comment for the same target",
)
} else {
comments.push({ target: pending.target, text: pending.text, })
}
}
}
for pending in pending_message_transmitters {
match find_message_index(messages, pending.message_id) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC017",
"message transmitter declaration refers to an unknown message",
)
Some(message_index) =>
if contains_message_reference(
resolved_transmitter_messages,
pending.message_id,
) {
add_diagnostic(
diagnostics,
pending.line,
"DBC018",
"duplicate transmitter declaration for the same message",
)
} else {
let message = messages[message_index]
for transmitter in pending.transmitters {
if transmitter != message.transmitter {
message.additional_transmitters.push(transmitter)
}
}
resolved_transmitter_messages.push(pending.message_id)
}
}
}
for pending in pending_signal_value_types {
match find_message_index(messages, pending.message_id) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC021",
"signal value type refers to an unknown message",
)
Some(message_index) => {
let message = messages[message_index]
match find_signal_index(message, pending.signal_name) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC022",
"signal value type refers to an unknown signal",
)
Some(signal_index) =>
if contains_signal_reference(
resolved_signal_value_types,
pending.message_id,
pending.signal_name,
) {
add_diagnostic(
diagnostics,
pending.line,
"DBC023",
"duplicate value type declaration for the same signal",
)
} else {
let signal = message.signals[signal_index]
message.signals[signal_index] = {
..signal,
value_type: pending.value_type,
}
resolved_signal_value_types.push(
(pending.message_id, pending.signal_name),
)
}
}
}
}
}
for pending in pending_multiplex_ranges {
match find_message_index(messages, pending.message_id) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC025",
"extended multiplex declaration refers to an unknown message",
)
Some(message_index) => {
let message = messages[message_index]
match find_signal_index(message, pending.signal_name) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC026",
"extended multiplex declaration refers to an unknown signal",
)
Some(signal_index) =>
if contains_signal_reference(
resolved_multiplex_ranges,
pending.message_id,
pending.signal_name,
) {
add_diagnostic(
diagnostics,
pending.line,
"DBC027",
"duplicate extended multiplex declaration for the same signal",
)
} else {
match find_signal_index(message, pending.selector_name) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC028",
"extended multiplex declaration refers to an unknown selector",
)
Some(selector_index) =>
if message.signals[selector_index].multiplex != Multiplexer {
add_diagnostic(
diagnostics,
pending.line,
"DBC029",
"extended multiplex selector is not a multiplexer",
)
} else if message.signals[signal_index].multiplex
is Multiplexer {
add_diagnostic(
diagnostics,
pending.line,
"DBC030",
"extended multiplex signal cannot be a multiplexer",
)
} else {
let signal = message.signals[signal_index]
message.signals[signal_index] = {
..signal,
multiplex: MultiplexedRanges(
pending.selector_name,
pending.ranges,
),
}
resolved_multiplex_ranges.push(
(pending.message_id, pending.signal_name),
)
}
}
}
}
}
}
}
for pending in pending_signal_groups {
match find_message_index(messages, pending.message_id) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC044",
"signal group refers to an unknown message",
)
Some(message_index) => {
let mut duplicate = false
for existing in signal_groups {
if existing.message_id == pending.message_id &&
existing.name == pending.name {
duplicate = true
}
}
if duplicate {
add_diagnostic(
diagnostics,
pending.line,
"DBC045",
"duplicate signal group for the same message and name",
)
} else {
let message = messages[message_index]
let mut valid = true
for signal_name in pending.signals {
if !contains_signal_name(message, signal_name) {
add_diagnostic(
diagnostics,
pending.line,
"DBC046",
"signal group refers to an unknown signal",
)
valid = false
break
}
}
if valid {
signal_groups.push({
message_id: pending.message_id,
name: pending.name,
repetition: pending.repetition,
signals: pending.signals,
})
signal_group_locations.push({
message_id: pending.message_id,
name: pending.name,
declaration: pending.declaration,
})
}
}
}
}
}
for pending in pending_attribute_defaults {
match find_attribute_definition(attributes.definitions, pending.name) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC034",
"attribute default refers to an unknown definition",
)
Some(definition) => {
let mut duplicate = false
for existing in attributes.defaults {
if existing.name == pending.name {
duplicate = true
}
}
if duplicate {
add_diagnostic(
diagnostics,
pending.line,
"DBC035",
"duplicate attribute default",
)
} else {
match
attribute_value_from_text(pending.raw_value, definition.value_type) {
Ok(value) =>
attributes.defaults.push({ name: pending.name, value, })
Err(message) =>
add_diagnostic(diagnostics, pending.line, "DBC036", message)
}
}
}
}
}
for pending in pending_attribute_assignments {
match find_attribute_definition(attributes.definitions, pending.name) {
None =>
add_diagnostic(
diagnostics,
pending.line,
"DBC038",
"attribute assignment refers to an unknown definition",
)
Some(definition) => {
if definition.scope != attribute_target_scope(pending.target) {
add_diagnostic(
diagnostics,
pending.line,
"DBC039",
"attribute assignment has the wrong target scope",
)
continue
}
let target_exists = attribute_target_exists(
nodes,
messages,
pending.target,
)
if !target_exists {
add_diagnostic(
diagnostics,
pending.line,
"DBC040",
"attribute assignment refers to an unknown target",
)
continue
}
let mut duplicate = false
for existing in attributes.assignments {
if existing.name == pending.name && existing.target == pending.target {
duplicate = true
}
}
if duplicate {
add_diagnostic(
diagnostics,
pending.line,
"DBC041",
"duplicate attribute assignment",
)
} else {
match
attribute_value_from_text(pending.raw_value, definition.value_type) {
Ok(value) =>
attributes.assignments.push({
name: pending.name,
target: pending.target,
value,
})
Err(message) =>
add_diagnostic(diagnostics, pending.line, "DBC042", message)
}
}
}
}
}
{
database: {
version,
nodes,
messages,
value_tables,
global_value_tables,
environment_variables,
signal_groups,
comments,
attributes,
},
diagnostics,
source_map: {
messages: message_locations,
signals: signal_locations,
comments: comment_locations,
value_tables: value_table_locations,
global_value_tables: global_value_table_locations,
environment_variables: environment_variable_locations,
signal_groups: signal_group_locations,
},
}
}
///|
/// Parse the semantic DBC model while retaining all recoverable errors as
/// source-line diagnostics. Use `parse_with_source_map` when declaration ranges
/// are needed for editors or visualizations.
pub fn parse(source : StringView) -> ParseResult {
let result = parse_with_source_map(source)
{ database: result.database, diagnostics: result.diagnostics, }
}