///|
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, }
}