use crate::sab::Token;
#[derive(Clone, Copy)]
pub struct Cur<'a> {
toks: &'a [Token],
pos: usize,
}
impl<'a> Cur<'a> {
pub fn at(toks: &'a [Token], pos: usize) -> Self {
Self { toks, pos }
}
pub fn pos(&self) -> usize {
self.pos
}
pub(crate) fn set_pos(&mut self, pos: usize) {
self.pos = pos;
}
pub(crate) fn toks(&self) -> &'a [Token] {
self.toks
}
pub(crate) fn peek(&self) -> Option<&'a Token> {
self.toks.get(self.pos)
}
pub(crate) fn bump(&mut self) -> Option<&'a Token> {
let token = self.toks.get(self.pos)?;
self.pos += 1;
Some(token)
}
pub(crate) fn rest(&self) -> &'a [Token] {
self.toks.get(self.pos..).unwrap_or(&[])
}
pub(crate) fn take_f64(&mut self) -> Option<f64> {
match self.peek()? {
Token::Double(value) => {
self.pos += 1;
Some(*value)
}
_ => None,
}
}
pub(crate) fn take_long(&mut self) -> Option<i64> {
match self.peek()? {
Token::Long(value) => {
self.pos += 1;
Some(*value)
}
_ => None,
}
}
pub(crate) fn take_enum(&mut self) -> Option<i64> {
match self.peek()? {
Token::Enum(value) => {
self.pos += 1;
Some(*value)
}
_ => None,
}
}
pub(crate) fn take_bool(&mut self) -> Option<bool> {
match self.peek()? {
Token::True => {
self.pos += 1;
Some(true)
}
Token::False => {
self.pos += 1;
Some(false)
}
_ => None,
}
}
pub(crate) fn take_str(&mut self) -> Option<&'a str> {
match self.peek()? {
Token::Str(value) => {
self.pos += 1;
Some(value)
}
_ => None,
}
}
pub(crate) fn take_ident(&mut self) -> Option<&'a str> {
match self.peek()? {
Token::Ident(value) | Token::SubIdent(value) => {
self.pos += 1;
Some(value)
}
_ => None,
}
}
pub(crate) fn take_position(&mut self) -> Option<[f64; 3]> {
match self.peek()? {
Token::Position(value) => {
self.pos += 1;
Some(*value)
}
_ => None,
}
}
pub(crate) fn take_vector3(&mut self) -> Option<[f64; 3]> {
match self.peek()? {
Token::Vector3(value) => {
self.pos += 1;
Some(*value)
}
_ => None,
}
}
pub(crate) fn take_float_array(&mut self) -> Option<Vec<f64>> {
let mark = self.pos;
let Some(count) = self.take_long().and_then(|c| usize::try_from(c).ok()) else {
self.pos = mark;
return None;
};
let mut values = Vec::new();
for _ in 0..count {
let Some(value) = self.take_f64() else {
self.pos = mark;
return None;
};
values.push(value);
}
Some(values)
}
pub(crate) fn take_range_value(&mut self) -> Option<f64> {
let mark = self.pos;
if matches!(self.peek(), Some(Token::True | Token::False)) {
self.pos += 1;
}
let Some(value) = self.take_f64() else {
self.pos = mark;
return None;
};
Some(value)
}
#[allow(clippy::option_option)] pub(crate) fn take_optional_range_value(&mut self) -> Option<Option<f64>> {
let mark = self.pos;
match self.peek()? {
Token::True => {
self.pos += 1;
let Some(value) = self.take_f64() else {
self.pos = mark;
return None;
};
Some(Some(value))
}
Token::False => {
self.pos += 1;
Some(None)
}
Token::Double(_) => self.take_f64().map(Some),
_ => None,
}
}
}
pub(crate) fn take_knot_table(
cur: &mut Cur<'_>,
n: usize,
degree: i64,
) -> Option<(Vec<f64>, usize)> {
let mut values = Vec::new();
let mut mults = Vec::new();
for _ in 0..n {
values.push(cur.take_f64()?);
mults.push(cur.take_long()?);
}
let sum: i64 = mults.iter().sum();
let n_poles = sum - (degree - 1);
if !(2..=100_000).contains(&n_poles) {
return None;
}
let mut expanded = Vec::new();
for (i, (value, mult)) in values.iter().zip(&mults).enumerate() {
let extra = i64::from(i == 0 || i == n - 1);
for _ in 0..usize::try_from((*mult + extra).max(0)).ok()? {
expanded.push(*value);
}
}
Some((expanded, n_poles as usize))
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum BsplineMarker {
Nubs,
Nurbs,
}
impl BsplineMarker {
pub(crate) fn cp_dims(self) -> usize {
match self {
Self::Nubs => 3,
Self::Nurbs => 4,
}
}
pub(crate) fn rational(self) -> bool {
self == Self::Nurbs
}
}
pub(crate) fn marker_at(toks: &[Token], pos: usize) -> Option<BsplineMarker> {
match toks.get(pos)? {
Token::Ident(name) if name == "nubs" => Some(BsplineMarker::Nubs),
Token::Ident(name) if name == "nurbs" => Some(BsplineMarker::Nurbs),
_ => None,
}
}
pub(crate) fn marker_positions(toks: &[Token]) -> Vec<usize> {
(0..toks.len())
.filter(|&pos| marker_at(toks, pos).is_some())
.collect()
}
pub(crate) fn owned_marker_positions(toks: &[Token]) -> Vec<usize> {
let mut out = Vec::new();
let mut depth = 0usize;
let start = usize::from(matches!(toks.first(), Some(Token::SubtypeOpen)));
for (pos, token) in toks.iter().enumerate().skip(start) {
match token {
Token::SubtypeOpen => depth += 1,
Token::SubtypeClose => depth = depth.saturating_sub(1),
_ => {
if depth == 0 && marker_at(toks, pos).is_some() {
out.push(pos);
}
}
}
}
out
}
pub(crate) fn owned_subtype_defs(toks: &[Token]) -> Vec<(usize, &str)> {
let mut owned = Vec::new();
let mut depth = 0usize;
for (pos, token) in toks.iter().enumerate() {
match token {
Token::SubtypeOpen => {
if depth == 0 {
if let Some(Token::Ident(name) | Token::SubIdent(name)) = toks.get(pos + 1) {
owned.push((pos, name.as_str()));
}
}
depth += 1;
}
Token::SubtypeClose => depth = depth.saturating_sub(1),
_ => {}
}
}
owned
}
pub(crate) fn find_owned_subtype_marker<'n>(
toks: &[Token],
names: &[&'n str],
) -> Option<(usize, &'n str)> {
let owned = owned_subtype_defs(toks);
names.iter().copied().find_map(|name| {
owned
.iter()
.find(|(_, owned_name)| *owned_name == name)
.map(|(start, _)| (*start, name))
})
}
pub fn owned_construction_subtype(toks: &[Token]) -> Option<String> {
owned_subtype_defs(toks)
.into_iter()
.map(|(_, name)| name)
.find(|name| *name != "ref")
.map(|name| canonical_intcurve_kind(name).into())
}
fn canonical_intcurve_kind(name: &str) -> &str {
match name {
"bldcur" => "blend_int_cur",
"blndsprngcur" => "spring_int_cur",
"exactcur" => "exact_int_cur",
"lawintcur" => "law_int_cur",
"offintcur" => "off_int_cur",
"offsetintcur" => "offset_int_cur",
"offsurfintcur" => "off_surf_int_cur",
"parasil" => "para_silh_int_cur",
"parcur" => "par_int_cur",
"projcur" => "proj_int_cur",
"surfcur" => "surf_int_cur",
"surfintcur" => "int_int_cur",
"d5c2_cur" => "skin_int_cur",
"subsetintcur" => "subset_int_cur",
_ => name,
}
}
pub(crate) fn find_owned_intcurve_subtype(toks: &[Token], modern: &str) -> Option<usize> {
let legacy = match modern {
"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",
_ => "",
};
let candidates: Vec<&str> = [modern, legacy]
.into_iter()
.filter(|name| !name.is_empty())
.collect();
find_owned_subtype_marker(toks, &candidates).map(|(marker, _)| marker)
}
pub(crate) fn subtype_span(toks: &[Token], start: usize) -> Option<&[Token]> {
let mut depth = 0usize;
for (pos, token) in toks.iter().enumerate().skip(start) {
match token {
Token::SubtypeOpen => depth += 1,
Token::SubtypeClose => {
depth = depth.checked_sub(1)?;
if depth == 0 {
return toks.get(start..=pos);
}
}
_ => {}
}
}
None
}
pub(crate) fn subtype_refs(toks: &[Token]) -> Vec<usize> {
let mut refs = Vec::new();
for (pos, token) in toks.iter().enumerate() {
if !matches!(token, Token::SubtypeOpen) {
continue;
}
match (toks.get(pos + 1), toks.get(pos + 2)) {
(Some(Token::Ident(name)), Some(Token::Long(index)))
if name == "ref" && *index >= 0 =>
{
refs.push(*index as usize);
}
(Some(Token::Long(index)), Some(Token::SubtypeClose)) if *index >= 0 => {
refs.push(*index as usize);
}
_ => {}
}
}
refs
}
pub fn payload_subtype_toks<'r>(
record: &'r crate::sab::Record,
chunk_index: usize,
expected: &str,
) -> Option<&'r [Token]> {
let mut chunk = 0usize;
let mut open = None;
for (pos, token) in record.tokens.iter().enumerate() {
if token.is_payload_ident() {
continue;
}
if chunk == chunk_index {
open = Some(pos);
break;
}
chunk += 1;
}
let open = open?;
if !matches!(record.tokens.get(open), Some(Token::SubtypeOpen)) {
return None;
}
let (Token::Ident(name) | Token::SubIdent(name)) = record.tokens.get(open + 1)? else {
return None;
};
if name != expected {
return None;
}
let span = subtype_span(&record.tokens, open)?;
span.get(2..span.len() - 1)
}
pub struct SubtypeTable {
defs: Vec<(std::sync::Arc<[Token]>, usize)>,
save_format_version: Option<u32>,
}
impl SubtypeTable {
pub fn from_records(records: &[crate::sab::Record]) -> Self {
let mut defs = Vec::new();
for record in records {
for (pos, token) in record.tokens.iter().enumerate() {
if matches!(token, Token::SubtypeOpen) {
if let Some(Token::Ident(name) | Token::SubIdent(name)) =
record.tokens.get(pos + 1)
{
if name != "ref" {
defs.push((record.tokens.clone(), pos));
}
}
}
}
}
Self {
defs,
save_format_version: None,
}
}
#[must_use]
pub fn with_save_format_version(mut self, version: Option<u32>) -> Self {
self.save_format_version = version;
self
}
pub(crate) fn save_format_version(&self) -> Option<u32> {
self.save_format_version
}
pub(crate) fn span(&self, index: usize) -> Option<&[Token]> {
let (tokens, token_pos) = self.defs.get(index)?;
subtype_span(tokens, *token_pos)
}
}
pub fn lex_test_span(bytes: &[u8], ref_width: usize) -> std::sync::Arc<[Token]> {
let mut wrapped = vec![0x0d, 1, b'x'];
wrapped.extend_from_slice(bytes);
wrapped.push(0x11);
let records =
crate::sab::frame(&wrapped, 0, wrapped.len(), ref_width).expect("test span lexes");
records.into_iter().next().expect("one record").tokens
}
pub fn test_table(bytes: &[u8], ref_width: usize) -> SubtypeTable {
let record = crate::sab::Record {
index: 0,
name: String::new(),
head: String::new(),
tokens: lex_test_span(bytes, ref_width),
offset: 0,
len: 0,
};
SubtypeTable::from_records(&[record])
}
#[cfg(test)]
mod tests {
use super::*;
fn ident(name: &str) -> Token {
Token::Ident(name.to_string())
}
#[test]
fn cursor_take_methods_do_not_advance_on_mismatch() {
let toks = [Token::Double(2.5), Token::True, Token::Long(7)];
let mut cur = Cur::at(&toks, 0);
assert_eq!(cur.take_long(), None);
assert_eq!(cur.take_f64(), Some(2.5));
assert_eq!(cur.take_bool(), Some(true));
assert_eq!(cur.take_long(), Some(7));
assert_eq!(cur.bump(), None);
}
#[test]
fn float_array_restores_position_on_a_truncated_body() {
let toks = [Token::Long(2), Token::Double(1.0), Token::True];
let mut cur = Cur::at(&toks, 0);
assert_eq!(cur.take_float_array(), None);
assert_eq!(cur.pos(), 0);
}
#[test]
fn owned_defs_skip_nested_constructions() {
let toks = [
Token::SubtypeOpen,
ident("exactcur"),
Token::SubtypeOpen,
ident("ref"),
Token::Long(3),
Token::SubtypeClose,
Token::SubtypeClose,
];
assert_eq!(owned_subtype_defs(&toks), vec![(0, "exactcur")]);
assert_eq!(subtype_refs(&toks), vec![3]);
assert_eq!(
owned_construction_subtype(&toks),
Some("exact_int_cur".to_string())
);
assert_eq!(subtype_span(&toks, 2), Some(&toks[2..=5]));
}
#[test]
fn owned_markers_ignore_nested_scopes_and_a_leading_open() {
let toks = [
Token::SubtypeOpen,
ident("nubs"),
Token::SubtypeOpen,
ident("nurbs"),
Token::SubtypeClose,
];
assert_eq!(owned_marker_positions(&toks), vec![1]);
assert_eq!(marker_positions(&toks), vec![1, 3]);
assert_eq!(marker_at(&toks, 1), Some(BsplineMarker::Nubs));
assert_eq!(marker_at(&toks, 3), Some(BsplineMarker::Nurbs));
}
}