#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Char(u8),
Short(i16),
Long(i64),
Float(f32),
Double(f64),
Str(String),
True,
False,
Ref(i64),
SubtypeOpen,
SubtypeClose,
Enum(i64),
Position([f64; 3]),
Vector3([f64; 3]),
Vector2([f64; 2]),
Int64(i64),
}
#[derive(Debug, Clone)]
pub struct Record {
pub index: usize,
pub name: String,
pub head: String,
pub tokens: Vec<Token>,
pub offset: usize,
pub len: usize,
}
impl Record {
pub fn chunk(&self, i: usize) -> Option<&Token> {
self.tokens.get(i)
}
pub fn ref_at(&self, i: usize) -> Option<i64> {
match self.tokens.get(i) {
Some(Token::Ref(v)) if *v >= 0 => Some(*v),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrameError {
pub offset: usize,
pub reason: String,
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SAB framing failed at byte {}: {}",
self.offset, self.reason
)
}
}
enum Lexed {
Value(Token),
Ident(String),
SubIdent(String),
Terminator,
}
fn read_i(bytes: &[u8], at: usize, width: usize) -> Option<i64> {
let slice = bytes.get(at..at + width)?;
let v = match width {
8 => i64::from_le_bytes(
slice
.try_into()
.expect("invariant: bytes.get(at..at+width) with width=8 is an 8-byte slice"),
),
4 => i32::from_le_bytes(
slice
.try_into()
.expect("invariant: bytes.get(at..at+width) with width=4 is a 4-byte slice"),
) as i64,
_ => return None,
};
Some(v)
}
fn read_f64(bytes: &[u8], at: usize) -> Option<f64> {
let slice = bytes.get(at..at + 8)?;
Some(f64::from_le_bytes(
slice
.try_into()
.expect("invariant: bytes.get(at..at+8) is an 8-byte slice"),
))
}
fn read_vec3(bytes: &[u8], at: usize) -> Option<[f64; 3]> {
Some([
read_f64(bytes, at)?,
read_f64(bytes, at + 8)?,
read_f64(bytes, at + 16)?,
])
}
fn read_string(bytes: &[u8], start: usize, len: usize) -> Option<String> {
let slice = bytes.get(start..start + len)?;
Some(String::from_utf8_lossy(slice).into_owned())
}
fn lex(bytes: &[u8], pos: usize, ref_width: usize) -> Result<(Lexed, usize), FrameError> {
let err = |reason: &str| FrameError {
offset: pos,
reason: reason.to_string(),
};
let tag = *bytes.get(pos).ok_or_else(|| err("end of stream"))?;
let p = pos + 1;
let truncated = || FrameError {
offset: pos,
reason: format!("truncated payload for tag {tag:#04x}"),
};
let out = match tag {
0x02 => (
Lexed::Value(Token::Char(*bytes.get(p).ok_or_else(truncated)?)),
p + 1,
),
0x03 => {
let s = bytes.get(p..p + 2).ok_or_else(truncated)?;
(
Lexed::Value(Token::Short(i16::from_le_bytes(
s.try_into()
.expect("invariant: bytes.get(p..p+2) is a 2-byte slice"),
))),
p + 2,
)
}
0x04 => {
let v = read_i(bytes, p, ref_width).ok_or_else(truncated)?;
(Lexed::Value(Token::Long(v)), p + ref_width)
}
0x05 => {
let s = bytes.get(p..p + 4).ok_or_else(truncated)?;
(
Lexed::Value(Token::Float(f32::from_le_bytes(
s.try_into()
.expect("invariant: bytes.get(p..p+4) is a 4-byte slice"),
))),
p + 4,
)
}
0x06 => (
Lexed::Value(Token::Double(read_f64(bytes, p).ok_or_else(truncated)?)),
p + 8,
),
0x07 => {
let len = *bytes.get(p).ok_or_else(truncated)? as usize;
(
Lexed::Value(Token::Str(
read_string(bytes, p + 1, len).ok_or_else(truncated)?,
)),
p + 1 + len,
)
}
0x08 => {
let s = bytes.get(p..p + 2).ok_or_else(truncated)?;
let len = u16::from_le_bytes(
s.try_into()
.expect("invariant: bytes.get(p..p+2) is a 2-byte slice"),
) as usize;
(
Lexed::Value(Token::Str(
read_string(bytes, p + 2, len).ok_or_else(truncated)?,
)),
p + 2 + len,
)
}
0x09 | 0x12 => {
let s = bytes.get(p..p + 4).ok_or_else(truncated)?;
let len = u32::from_le_bytes(
s.try_into()
.expect("invariant: bytes.get(p..p+4) is a 4-byte slice"),
) as usize;
(
Lexed::Value(Token::Str(
read_string(bytes, p + 4, len).ok_or_else(truncated)?,
)),
p + 4 + len,
)
}
0x0a => (Lexed::Value(Token::True), p),
0x0b => (Lexed::Value(Token::False), p),
0x0c => {
let v = read_i(bytes, p, ref_width).ok_or_else(truncated)?;
(Lexed::Value(Token::Ref(v)), p + ref_width)
}
0x0d => {
let len = *bytes.get(p).ok_or_else(truncated)? as usize;
(
Lexed::Ident(read_string(bytes, p + 1, len).ok_or_else(truncated)?),
p + 1 + len,
)
}
0x0e => {
let len = *bytes.get(p).ok_or_else(truncated)? as usize;
(
Lexed::SubIdent(read_string(bytes, p + 1, len).ok_or_else(truncated)?),
p + 1 + len,
)
}
0x0f => (Lexed::Value(Token::SubtypeOpen), p),
0x10 => (Lexed::Value(Token::SubtypeClose), p),
0x11 => (Lexed::Terminator, p),
0x13 => (
Lexed::Value(Token::Position(read_vec3(bytes, p).ok_or_else(truncated)?)),
p + 24,
),
0x14 => (
Lexed::Value(Token::Vector3(read_vec3(bytes, p).ok_or_else(truncated)?)),
p + 24,
),
0x15 => {
let v = read_i(bytes, p, ref_width).ok_or_else(truncated)?;
(Lexed::Value(Token::Enum(v)), p + ref_width)
}
0x16 => {
let u = read_f64(bytes, p).ok_or_else(truncated)?;
let v = read_f64(bytes, p + 8).ok_or_else(truncated)?;
(Lexed::Value(Token::Vector2([u, v])), p + 16)
}
0x17 => {
let v = read_i(bytes, p, 8).ok_or_else(truncated)?;
(Lexed::Value(Token::Int64(v)), p + 8)
}
other => {
return Err(FrameError {
offset: pos,
reason: format!("unrecognized tag {other:#04x}"),
})
}
};
Ok(out)
}
pub(crate) fn payload_token_offsets(
bytes: &[u8],
record: &Record,
ref_width: usize,
tag: u8,
) -> Result<Vec<usize>, FrameError> {
let end = record.offset + record.len;
let mut position = record.offset;
let mut offsets = Vec::new();
while position < end {
let token_offset = position;
let (token, next) = lex(bytes, position, ref_width)?;
if bytes[token_offset] == tag && matches!(&token, Lexed::Value(_)) {
offsets.push(token_offset);
}
position = next;
if matches!(&token, Lexed::Terminator) {
break;
}
}
Ok(offsets)
}
pub fn frame(
bytes: &[u8],
start: usize,
limit: usize,
ref_width: usize,
) -> Result<Vec<Record>, FrameError> {
let limit = limit.min(bytes.len());
let mut records = Vec::new();
let mut pos = start;
let mut index = 0usize;
while pos < limit {
let rec_start = pos;
let mut name_parts: Vec<String> = Vec::new();
let mut tokens: Vec<Token> = Vec::new();
let mut depth = 0i32;
let mut name_done = false;
let mut is_delta = false;
loop {
let (lexed, next) = lex(bytes, pos, ref_width)?;
pos = next;
match lexed {
Lexed::Terminator if depth == 0 => break,
Lexed::Terminator => {
}
Lexed::SubIdent(s) if !name_done => name_parts.push(s),
Lexed::Ident(s) if !name_done => {
name_parts.push(s);
name_done = true;
if name_parts.first().is_some_and(|n| n == "delta_state") {
is_delta = true;
break;
}
}
Lexed::SubIdent(_) | Lexed::Ident(_) => {
}
Lexed::Value(Token::SubtypeOpen) => {
depth += 1;
name_done = true;
tokens.push(Token::SubtypeOpen);
}
Lexed::Value(Token::SubtypeClose) => {
depth -= 1;
tokens.push(Token::SubtypeClose);
}
Lexed::Value(v) => {
name_done = true;
tokens.push(v);
}
}
}
if is_delta {
break;
}
let name = name_parts.join("-");
let head = name_parts.first().cloned().unwrap_or_default();
records.push(Record {
index,
name,
head,
tokens,
offset: rec_start,
len: pos - rec_start,
});
index += 1;
}
Ok(records)
}