use crate::kernel_header::RefWidth;
use crate::sab::int_le_at;
use cadmpeg_core::decode::View;
pub(super) const INTCURVE_ALIASES: &[(&str, &str)] = &[
("blend_int_cur", "bldcur"),
("spring_int_cur", "blndsprngcur"),
("exact_int_cur", "exactcur"),
("law_int_cur", "lawintcur"),
("off_int_cur", "offintcur"),
("offset_int_cur", "offsetintcur"),
("off_surf_int_cur", "offsurfintcur"),
("para_silh_int_cur", "parasil"),
("par_int_cur", "parcur"),
("proj_int_cur", "projcur"),
("surf_int_cur", "surfcur"),
("int_int_cur", "surfintcur"),
("skin_int_cur", "d5c2_cur"),
("subset_int_cur", "subsetintcur"),
];
pub(super) fn owned_subtype_defs(bytes: &[u8], int_width: RefWidth) -> Option<Vec<(usize, &[u8])>> {
let mut owned = Vec::new();
let mut depth = 0usize;
let mut pos = 0usize;
while pos < bytes.len() {
match bytes[pos] {
0x0f => {
if depth == 0 && matches!(bytes.get(pos + 1), Some(0x0d | 0x0e)) {
if let Some(&len) = bytes.get(pos + 2) {
if let Some(name) = bytes.get(pos + 3..pos + 3 + usize::from(len)) {
owned.push((pos, name));
}
}
}
depth += 1;
}
0x10 => depth = depth.checked_sub(1)?,
_ => {}
}
match next_token(bytes, pos, int_width) {
Some(next) => pos = next,
None => break,
}
}
Some(owned)
}
pub(super) fn find_owned_subtype_marker<'n>(
bytes: &[u8],
names: &[&'n [u8]],
int_width: RefWidth,
) -> Option<(usize, &'n [u8])> {
let owned = owned_subtype_defs(bytes, int_width)?;
names.iter().copied().find_map(|name| {
owned
.iter()
.find(|(_, owned_name)| *owned_name == name)
.map(|(start, _)| (*start, name))
})
}
pub(super) fn find_owned_intcurve_subtype(
bytes: &[u8],
modern: &[u8],
int_width: RefWidth,
) -> Option<(usize, usize)> {
if modern.is_empty() {
return None;
}
let legacy = INTCURVE_ALIASES
.iter()
.find_map(|(name, alias)| (name.as_bytes() == modern).then_some(alias.as_bytes()));
let found = match legacy {
Some(legacy) => find_owned_subtype_marker(bytes, &[modern, legacy], int_width),
None => find_owned_subtype_marker(bytes, &[modern], int_width),
};
found.map(|(marker, name)| (marker, name.len()))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubtypeScope<'a>(&'a [u8]);
impl<'a> SubtypeScope<'a> {
pub fn bytes(&self) -> &'a [u8] {
self.0
}
}
pub fn subtype_span(bytes: &[u8], start: usize, int_width: RefWidth) -> Option<SubtypeScope<'_>> {
(bytes.get(start) == Some(&0x0f)).then_some(())?;
let mut depth = 0usize;
let mut pos = start;
while pos < bytes.len() {
match bytes[pos] {
0x0f => depth += 1,
0x10 => {
depth = depth.checked_sub(1)?;
if depth == 0 {
return bytes.get(start..=pos).map(SubtypeScope);
}
}
_ => {}
}
pos = next_token(bytes, pos, int_width)?;
}
None
}
pub(super) fn next_token(bytes: &[u8], pos: usize, int_width: RefWidth) -> Option<usize> {
let tag = *bytes.get(pos)?;
let fixed = match tag {
0x02 => 2,
0x03 => 3,
0x04 | 0x0c | 0x15 => 1 + int_width.bytes(),
0x06 | 0x17 => 9,
0x05 => 5,
0x0a | 0x0b | 0x0f | 0x10 | 0x11 => 1,
0x13 | 0x14 => 25,
0x16 => 17,
0x07 | 0x0d | 0x0e => 2 + usize::from(*bytes.get(pos + 1)?),
0x08 => 3 + usize::from(View::u16_le_at(bytes, pos + 1)?),
0x09 | 0x12 => {
let length = int_le_at(bytes, pos + 1, int_width)?;
1 + int_width.bytes() + usize::try_from(length).ok()?
}
_ => return None,
};
let next = pos.checked_add(fixed)?;
(next <= bytes.len()).then_some(next)
}
#[cfg(test)]
mod ownership_tests {
use super::{find_owned_intcurve_subtype, subtype_span};
use crate::kernel_header::RefWidth;
fn open(bytes: &mut Vec<u8>, name: &[u8]) {
bytes.push(0x0f);
bytes.push(0x0d);
bytes.push(u8::try_from(name.len()).expect("short name"));
bytes.extend_from_slice(name);
}
#[test]
fn a_nested_intcurve_construction_does_not_own_the_record() {
let arena = cadmpeg_core::decode::DecodeArena::new();
let policy = cadmpeg_core::decode::DecodePolicy::service();
let (ctx, _) =
cadmpeg_core::decode::DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
for int_width in [RefWidth::Four, RefWidth::Eight] {
let mut bytes = Vec::new();
open(&mut bytes, b"defm_int_cur");
bytes.push(0x04);
bytes.extend_from_slice(&vec![0u8; int_width.bytes()]);
open(&mut bytes, b"int_int_cur");
bytes.push(0x10);
bytes.push(0x10);
assert_eq!(
find_owned_intcurve_subtype(&bytes, b"defm_int_cur", int_width),
Some((0, b"defm_int_cur".len()))
);
assert_eq!(
find_owned_intcurve_subtype(&bytes, b"int_int_cur", int_width),
None
);
assert_eq!(
crate::nurbs::toks::owned_construction_subtype(
&ctx,
&crate::nurbs::toks::lex_test_span(&bytes, int_width)
.expect("valid single-record byte fixture")
)
.transpose()
.unwrap()
.as_deref(),
Some("defm_int_cur")
);
}
}
#[test]
fn a_byte_scope_yields_its_owned_markers_with_no_refusal_to_answer() {
for int_width in [RefWidth::Four, RefWidth::Eight] {
let mut bytes = Vec::new();
open(&mut bytes, b"exact_int_cur");
let owned_marker = bytes.len();
bytes.extend_from_slice(b"\x0d\x04nubs");
open(&mut bytes, b"ref");
bytes.extend_from_slice(b"\x0d\x05nurbs");
bytes.push(0x10);
bytes.push(0x10);
let scope = subtype_span(&bytes, 0, int_width).expect("balanced scope");
let owned: Vec<usize> = scope.owned_marker_positions(int_width);
assert_eq!(owned, vec![owned_marker]);
assert_eq!(scope.bytes(), bytes.as_slice());
}
}
#[test]
fn a_byte_span_that_does_not_open_at_start_is_not_a_scope() {
for int_width in [RefWidth::Four, RefWidth::Eight] {
let mut bytes = vec![0x0du8, 0x01, b'x'];
let open_at = bytes.len();
open(&mut bytes, b"exact_int_cur");
bytes.push(0x10);
assert_eq!(subtype_span(&bytes, 0, int_width), None);
assert_eq!(subtype_span(&bytes, 1, int_width), None);
assert_eq!(subtype_span(&bytes, bytes.len(), int_width), None);
let scope = subtype_span(&bytes, open_at, int_width).expect("balanced scope");
assert_eq!(scope.bytes(), &bytes[open_at..]);
}
}
}