#[derive(Default)]
pub(crate) struct Ann<'a> {
pub(crate) wire_type: &'a str, pub(crate) field_number: Option<u64>,
pub(crate) field_type: &'a str, pub(crate) is_packed: bool,
pub(crate) tag_overhang_count: Option<u64>,
pub(crate) value_overhang_count: Option<u64>,
pub(crate) length_overhang_count: Option<u64>,
pub(crate) missing_bytes_count: Option<u64>,
pub(crate) mismatched_group_end: Option<u64>,
pub(crate) open_ended_group: bool,
pub(crate) end_tag_overhang_count: Option<u64>,
pub(crate) records_overhung_count: Vec<u64>,
pub(crate) neg_int32_truncated: bool, pub(crate) records_neg_int32_truncated: Vec<bool>, pub(crate) enum_packed_values: Vec<i64>,
pub(crate) enum_scalar_value: Option<i64>,
pub(crate) nan_bits: Option<u64>,
pub(crate) pack_size: Option<usize>,
pub(crate) elem_ohb: Option<u64>,
pub(crate) elem_neg_trunc: bool,
}
#[inline]
pub(super) fn parse_u64_str(s: &str) -> u64 {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
u64::from_str_radix(&s[2..], 16).unwrap_or(0)
} else {
s.parse::<u64>().unwrap_or(0)
}
}
pub(crate) fn parse_annotation(ann_str: &str) -> Ann<'_> {
let mut ann = Ann::default();
for raw_token in ann_str.split(';') {
let token = raw_token.trim();
if token.is_empty() {
continue;
}
if let Some(colon) = token.find(':') {
let before = &token[..colon];
if !before.contains(' ') {
let name = before.trim();
let value = token[colon + 1..].trim();
match name {
"tag_ohb" => ann.tag_overhang_count = Some(parse_u64_str(value)),
"val_ohb" => ann.value_overhang_count = Some(parse_u64_str(value)),
"len_ohb" => ann.length_overhang_count = Some(parse_u64_str(value)),
"MISSING" => ann.missing_bytes_count = Some(parse_u64_str(value)),
"END_MISMATCH" => ann.mismatched_group_end = Some(parse_u64_str(value)),
"etag_ohb" => ann.end_tag_overhang_count = Some(parse_u64_str(value)),
"packed_ohb" => {
let inner = value.trim_start_matches('[').trim_end_matches(']');
ann.records_overhung_count = inner.split(',').map(parse_u64_str).collect();
}
"packed_truncated_neg" => {
let inner = value.trim_start_matches('[').trim_end_matches(']');
ann.records_neg_int32_truncated =
inner.split(',').map(|s| s.trim() == "1").collect();
}
"nan_bits" => ann.nan_bits = Some(parse_u64_str(value)),
"pack_size" => ann.pack_size = Some(parse_u64_str(value) as usize),
"ohb" => ann.elem_ohb = Some(parse_u64_str(value)),
_ => {}
}
continue;
}
}
if token.contains('=') {
parse_field_decl_into(token, &mut ann);
} else {
match token {
"OPEN_GROUP" => {
ann.open_ended_group = true;
}
"truncated_neg" => {
ann.neg_int32_truncated = true;
}
"neg" => {
ann.elem_neg_trunc = true;
}
"TAG_OOR" | "ETAG_OOR" | "TYPE_MISMATCH" | "ENUM_UNKNOWN" => {}
_ => {
ann.wire_type = token;
}
}
}
}
ann
}
pub(crate) fn parse_field_decl_into<'a>(token: &'a str, ann: &mut Ann<'a>) {
if let Some(paren_pos) = token.find('(') {
parse_enum_field_decl(token, paren_pos, ann);
return;
}
let mut it = token.split_ascii_whitespace();
let Some(first) = it.next() else { return };
let Some(second) = it.next() else { return };
if second == "=" {
let Some(n) = it.next() else { return };
ann.field_type = first;
ann.is_packed = false;
ann.field_number = Some(parse_u64_str(n));
return;
}
if second == "[packed=true]" {
let Some(eq_tok) = it.next() else { return };
if eq_tok != "=" {
return;
}
let Some(n) = it.next() else { return };
ann.field_type = first;
ann.is_packed = true;
ann.field_number = Some(parse_u64_str(n));
return;
}
let Some(tok3) = it.next() else { return };
let (is_packed, number_tok) = if tok3 == "=" {
let Some(n) = it.next() else { return };
(false, n)
} else {
let Some(eq_tok) = it.next() else { return };
if eq_tok != "=" {
return;
}
let Some(n) = it.next() else { return };
(tok3 == "[packed=true]", n)
};
ann.field_type = second;
ann.is_packed = is_packed;
ann.field_number = Some(parse_u64_str(number_tok));
}
fn parse_enum_field_decl(token: &str, paren_pos: usize, ann: &mut Ann<'_>) {
let Some(close_paren) = token[paren_pos..].find(')') else {
return;
};
let close_paren = paren_pos + close_paren;
let inner = &token[paren_pos + 1..close_paren];
let before_paren = token[..paren_pos].trim();
let after_paren = token[close_paren + 1..].trim();
let _is_repeated = before_paren.starts_with("repeated ");
ann.field_type = "enum";
if inner.starts_with('[') {
let list_inner = inner.trim_start_matches('[').trim_end_matches(']');
ann.is_packed = true;
ann.enum_packed_values = list_inner
.split(',')
.map(|s| s.trim().parse::<i64>().unwrap_or(0))
.collect();
let field_num_str = if let Some(eq_pos) = after_paren.rfind('=') {
after_paren[eq_pos + 1..].trim()
} else {
return;
};
ann.field_number = Some(parse_u64_str(field_num_str));
} else {
let n: i64 = inner.trim().parse::<i64>().unwrap_or(0);
ann.enum_scalar_value = Some(n);
ann.is_packed = after_paren.contains("[packed=true]");
let field_num_str = if let Some(eq_pos) = after_paren.rfind('=') {
after_paren[eq_pos + 1..].trim()
} else {
return;
};
ann.field_number = Some(parse_u64_str(field_num_str));
}
}