1use crate::bin_table::Value;
2use std::error::Error;
3use std::str::from_utf8;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum TableColumnFormat {
11 String(usize),
13 StringArray(usize, usize),
15 Boolean(usize),
17 Bit(usize),
19 U8(usize),
21 I8(usize),
23 U16(usize),
25 I16(usize),
27 U32(usize),
29 I32(usize),
31 I64(usize),
33 F32(usize),
35 F64(usize),
37 C32(usize),
39 M64(usize),
41 VariableLengthArray {
47 element: TableElementFormat,
49 descriptor: ArrayDescriptor,
51 max: usize,
55 },
56}
57
58#[derive(Debug, Clone, Copy, PartialEq)]
60pub enum ArrayDescriptor {
61 P32,
63 Q64,
65}
66
67impl ArrayDescriptor {
68 pub fn bytes_len(&self) -> usize {
70 match self {
71 ArrayDescriptor::P32 => 8,
72 ArrayDescriptor::Q64 => 16,
73 }
74 }
75
76 pub fn read(&self, bytes: &[u8]) -> Option<(usize, usize)> {
82 let (count, offset) = match self {
83 ArrayDescriptor::P32 => {
84 let (count, offset) = bytes.get(..8)?.split_at(4);
85 (
86 i32::from_be_bytes(count.try_into().ok()?) as i64,
87 i32::from_be_bytes(offset.try_into().ok()?) as i64,
88 )
89 }
90 ArrayDescriptor::Q64 => {
91 let (count, offset) = bytes.get(..16)?.split_at(8);
92 (
93 i64::from_be_bytes(count.try_into().ok()?),
94 i64::from_be_bytes(offset.try_into().ok()?),
95 )
96 }
97 };
98
99 Some((usize::try_from(count).ok()?, usize::try_from(offset).ok()?))
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq)]
106pub enum TableElementFormat {
107 Character,
109 Boolean,
111 Bit,
113 U8,
115 I8,
117 U16,
119 I16,
121 U32,
123 I32,
125 I64,
127 F32,
129 F64,
131 C32,
133 M64,
135}
136
137impl TableElementFormat {
138 pub fn code(&self) -> char {
140 match self {
141 TableElementFormat::Character => 'A',
142 TableElementFormat::Boolean => 'L',
143 TableElementFormat::Bit => 'X',
144 TableElementFormat::U8 => 'B',
145 TableElementFormat::I8 => 'S',
146 TableElementFormat::U16 => 'U',
147 TableElementFormat::I16 => 'I',
148 TableElementFormat::U32 => 'V',
149 TableElementFormat::I32 => 'J',
150 TableElementFormat::I64 => 'K',
151 TableElementFormat::F32 => 'E',
152 TableElementFormat::F64 => 'D',
153 TableElementFormat::C32 => 'C',
154 TableElementFormat::M64 => 'M',
155 }
156 }
157
158 fn from_code(code: char) -> Option<Self> {
159 Some(match code {
160 'A' => TableElementFormat::Character,
161 'L' => TableElementFormat::Boolean,
162 'X' => TableElementFormat::Bit,
163 'B' => TableElementFormat::U8,
164 'S' => TableElementFormat::I8,
165 'U' => TableElementFormat::U16,
166 'I' => TableElementFormat::I16,
167 'V' => TableElementFormat::U32,
168 'J' => TableElementFormat::I32,
169 'K' => TableElementFormat::I64,
170 'E' => TableElementFormat::F32,
171 'D' => TableElementFormat::F64,
172 'C' => TableElementFormat::C32,
173 'M' => TableElementFormat::M64,
174 _ => return None,
175 })
176 }
177
178 pub fn repeated(&self, count: usize) -> TableColumnFormat {
183 match self {
184 TableElementFormat::Character => TableColumnFormat::String(count),
185 TableElementFormat::Boolean => TableColumnFormat::Boolean(count),
186 TableElementFormat::Bit => TableColumnFormat::Bit(count),
187 TableElementFormat::U8 => TableColumnFormat::U8(count),
188 TableElementFormat::I8 => TableColumnFormat::I8(count),
189 TableElementFormat::U16 => TableColumnFormat::U16(count),
190 TableElementFormat::I16 => TableColumnFormat::I16(count),
191 TableElementFormat::U32 => TableColumnFormat::U32(count),
192 TableElementFormat::I32 => TableColumnFormat::I32(count),
193 TableElementFormat::I64 => TableColumnFormat::I64(count),
194 TableElementFormat::F32 => TableColumnFormat::F32(count),
195 TableElementFormat::F64 => TableColumnFormat::F64(count),
196 TableElementFormat::C32 => TableColumnFormat::C32(count),
197 TableElementFormat::M64 => TableColumnFormat::M64(count),
198 }
199 }
200}
201
202impl TableColumnFormat {
203 pub fn parse_into_value(&self, data: &[u8], heap: &[u8]) -> crate::Result<Value> {
209 if let TableColumnFormat::VariableLengthArray {
210 element,
211 descriptor,
212 ..
213 } = self
214 {
215 return self.parse_array_from_heap(*element, *descriptor, data, heap);
216 }
217
218 let width = self.bytes_len();
219
220 let bytes = data.get(..width).ok_or_else(|| {
221 crate::Error::DeserializationError(format!(
222 "Column of format {} needs {} bytes but only {} remain in the row",
223 String::from(*self),
224 width,
225 data.len()
226 ))
227 })?;
228
229 match self {
230 TableColumnFormat::String(_) => Ok(Value::String(decode_string(bytes)?)),
231
232 TableColumnFormat::StringArray(_, substring_width) => {
233 let substring_width = (*substring_width).max(1);
235
236 Ok(Value::StringArray(
237 bytes
238 .chunks(substring_width)
239 .map(decode_string)
240 .collect::<crate::Result<_>>()?,
241 ))
242 }
243
244 TableColumnFormat::Boolean(_) => Ok(Value::Boolean(
247 bytes.iter().map(|byte| *byte == b'T').collect(),
248 )),
249
250 TableColumnFormat::Bit(count) => Ok(Value::Bit {
252 bytes: bytes.to_vec(),
253 len: *count,
254 }),
255
256 TableColumnFormat::U8(_) => Ok(Value::U8(bytes.to_vec())),
257 TableColumnFormat::I8(_) => {
258 Ok(Value::I8(bytes.iter().map(|byte| *byte as i8).collect()))
259 }
260
261 TableColumnFormat::U16(_) => Ok(Value::U16(
262 bytes
263 .as_chunks::<2>()
264 .0
265 .iter()
266 .map(|value| u16::from_be_bytes(*value))
267 .collect(),
268 )),
269 TableColumnFormat::I16(_) => Ok(Value::I16(
270 bytes
271 .as_chunks::<2>()
272 .0
273 .iter()
274 .map(|value| i16::from_be_bytes(*value))
275 .collect(),
276 )),
277 TableColumnFormat::U32(_) => Ok(Value::U32(
278 bytes
279 .as_chunks::<4>()
280 .0
281 .iter()
282 .map(|value| u32::from_be_bytes(*value))
283 .collect(),
284 )),
285 TableColumnFormat::I32(_) => Ok(Value::I32(
286 bytes
287 .as_chunks::<4>()
288 .0
289 .iter()
290 .map(|value| i32::from_be_bytes(*value))
291 .collect(),
292 )),
293 TableColumnFormat::I64(_) => Ok(Value::I64(
294 bytes
295 .as_chunks::<8>()
296 .0
297 .iter()
298 .map(|value| i64::from_be_bytes(*value))
299 .collect(),
300 )),
301 TableColumnFormat::F32(_) => Ok(Value::F32(
302 bytes
303 .as_chunks::<4>()
304 .0
305 .iter()
306 .map(|value| f32::from_be_bytes(*value))
307 .collect(),
308 )),
309 TableColumnFormat::F64(_) => Ok(Value::F64(
310 bytes
311 .as_chunks::<8>()
312 .0
313 .iter()
314 .map(|value| f64::from_be_bytes(*value))
315 .collect(),
316 )),
317
318 TableColumnFormat::C32(_) => Ok(Value::C32(
320 bytes
321 .as_chunks::<8>()
322 .0
323 .iter()
324 .map(|value| {
325 let (real, imaginary) = value.split_at(4);
326 (
327 f32::from_be_bytes(real.try_into().expect("4 of 8 bytes")),
328 f32::from_be_bytes(imaginary.try_into().expect("4 of 8 bytes")),
329 )
330 })
331 .collect(),
332 )),
333 TableColumnFormat::M64(_) => Ok(Value::M64(
334 bytes
335 .as_chunks::<16>()
336 .0
337 .iter()
338 .map(|value| {
339 let (real, imaginary) = value.split_at(8);
340 (
341 f64::from_be_bytes(real.try_into().expect("8 of 16 bytes")),
342 f64::from_be_bytes(imaginary.try_into().expect("8 of 16 bytes")),
343 )
344 })
345 .collect(),
346 )),
347
348 TableColumnFormat::VariableLengthArray { .. } => {
351 unreachable!("a variable length array column is decoded from the heap")
352 }
353 }
354 }
355
356 fn parse_array_from_heap(
359 &self,
360 element: TableElementFormat,
361 descriptor: ArrayDescriptor,
362 data: &[u8],
363 heap: &[u8],
364 ) -> crate::Result<Value> {
365 let (count, offset) = descriptor.read(data).ok_or_else(|| {
366 crate::Error::DeserializationError(format!(
367 "Column of format {} has an unreadable array descriptor",
368 String::from(*self)
369 ))
370 })?;
371
372 let format = element.repeated(count);
375 if count == 0 {
376 return format.parse_into_value(&[], &[]);
377 }
378
379 let width = format.bytes_len();
380 let bytes = heap
381 .get(offset..)
382 .and_then(|heap| heap.get(..width))
383 .ok_or_else(|| {
384 crate::Error::DeserializationError(format!(
385 "Column of format {} points at bytes {}..{} of a {} byte heap",
386 String::from(*self),
387 offset,
388 offset + width,
389 heap.len()
390 ))
391 })?;
392
393 format.parse_into_value(bytes, &[])
394 }
395
396 pub fn bytes_len(&self) -> usize {
401 match self {
402 TableColumnFormat::String(count) => *count,
405 TableColumnFormat::StringArray(count, _) => *count,
406
407 TableColumnFormat::Bit(count) => count.div_ceil(8),
409
410 TableColumnFormat::Boolean(count) => *count,
411 TableColumnFormat::U8(count) => *count,
412 TableColumnFormat::I8(count) => *count,
413 TableColumnFormat::U16(count) => 2 * count,
414 TableColumnFormat::I16(count) => 2 * count,
415 TableColumnFormat::U32(count) => 4 * count,
416 TableColumnFormat::I32(count) => 4 * count,
417 TableColumnFormat::I64(count) => 8 * count,
418 TableColumnFormat::F32(count) => 4 * count,
419 TableColumnFormat::F64(count) => 8 * count,
420
421 TableColumnFormat::C32(count) => 8 * count,
423 TableColumnFormat::M64(count) => 16 * count,
424
425 TableColumnFormat::VariableLengthArray { descriptor, .. } => descriptor.bytes_len(),
427 }
428 }
429
430 pub fn len(&self) -> usize {
432 match self {
433 TableColumnFormat::String(_) => 1,
434 TableColumnFormat::StringArray(count, substring_width) => {
435 count / (*substring_width).max(1)
436 }
437 TableColumnFormat::Boolean(count)
438 | TableColumnFormat::Bit(count)
439 | TableColumnFormat::U8(count)
440 | TableColumnFormat::I8(count)
441 | TableColumnFormat::U16(count)
442 | TableColumnFormat::I16(count)
443 | TableColumnFormat::U32(count)
444 | TableColumnFormat::I32(count)
445 | TableColumnFormat::I64(count)
446 | TableColumnFormat::F32(count)
447 | TableColumnFormat::F64(count)
448 | TableColumnFormat::C32(count)
449 | TableColumnFormat::M64(count) => *count,
450
451 TableColumnFormat::VariableLengthArray { max, .. } => *max,
454 }
455 }
456
457 pub fn is_empty(&self) -> bool {
459 self.len() == 0
460 }
461}
462
463fn decode_string(bytes: &[u8]) -> crate::Result<String> {
466 Ok(from_utf8(bytes)
467 .map_err(|e| crate::Error::DeserializationError(format!("Not valid UTF-8: {}", e)))?
468 .replace("\0", "")
469 .trim_ascii()
470 .to_string())
471}
472
473impl From<TableColumnFormat> for String {
474 fn from(value: TableColumnFormat) -> String {
475 match value {
476 TableColumnFormat::String(repeat) => format!("{}A", repeat),
477 TableColumnFormat::StringArray(repeat, items) => format!("{}A{}", repeat, items),
478 TableColumnFormat::Boolean(repeat) => format!("{}L", repeat),
479 TableColumnFormat::Bit(repeat) => format!("{}X", repeat),
480 TableColumnFormat::U8(repeat) => format!("{}B", repeat),
481 TableColumnFormat::I8(repeat) => format!("{}S", repeat),
482 TableColumnFormat::U16(repeat) => format!("{}U", repeat),
483 TableColumnFormat::I16(repeat) => format!("{}I", repeat),
484 TableColumnFormat::U32(repeat) => format!("{}V", repeat),
485 TableColumnFormat::I32(repeat) => format!("{}J", repeat),
486 TableColumnFormat::I64(repeat) => format!("{}K", repeat),
487 TableColumnFormat::F32(repeat) => format!("{}E", repeat),
488 TableColumnFormat::F64(repeat) => format!("{}D", repeat),
489 TableColumnFormat::C32(repeat) => format!("{}C", repeat),
490 TableColumnFormat::M64(repeat) => format!("{}M", repeat),
491 TableColumnFormat::VariableLengthArray {
492 element,
493 descriptor,
494 max,
495 } => {
496 let code = match descriptor {
497 ArrayDescriptor::P32 => 'P',
498 ArrayDescriptor::Q64 => 'Q',
499 };
500 format!("1{}{}({})", code, element.code(), max)
501 }
502 }
503 }
504}
505
506impl TryFrom<String> for TableColumnFormat {
507 type Error = Box<dyn Error + Send + Sync>;
508
509 fn try_from(value: String) -> Result<Self, Self::Error> {
510 let (repeat, format, items) = extract_parts(&value)?;
511 match format {
512 'A' => {
513 if items > 0 {
514 Ok(TableColumnFormat::StringArray(repeat, items))
515 } else {
516 Ok(TableColumnFormat::String(repeat))
517 }
518 }
519 'L' => Ok(TableColumnFormat::Boolean(repeat)),
520 'X' => Ok(TableColumnFormat::Bit(repeat)),
521 'B' => Ok(TableColumnFormat::U8(repeat)),
522 'S' => Ok(TableColumnFormat::I8(repeat)),
523 'I' => Ok(TableColumnFormat::I16(repeat)),
524 'U' => Ok(TableColumnFormat::U16(repeat)),
525 'J' => Ok(TableColumnFormat::I32(repeat)),
526 'V' => Ok(TableColumnFormat::U32(repeat)),
527 'K' => Ok(TableColumnFormat::I64(repeat)),
528 'E' => Ok(TableColumnFormat::F32(repeat)),
529 'D' => Ok(TableColumnFormat::F64(repeat)),
530 'C' => Ok(TableColumnFormat::C32(repeat)),
531 'M' => Ok(TableColumnFormat::M64(repeat)),
532 'P' | 'Q' => parse_variable_length_array(&value, repeat, format),
533 _ => Err(From::from(format!(
534 "Invalid TableColumnFormat value: {}",
535 value
536 ))),
537 }
538 }
539}
540
541fn parse_variable_length_array(
547 value: &str,
548 repeat: usize,
549 code: char,
550) -> Result<TableColumnFormat, Box<dyn Error + Send + Sync>> {
551 if repeat > 1 {
552 return Err(From::from(format!(
553 "A variable length array column holds one descriptor, so its repeat count must be 0 \
554 or 1, but {} says {}",
555 value, repeat
556 )));
557 }
558
559 let descriptor = match code {
560 'P' => ArrayDescriptor::P32,
561 _ => ArrayDescriptor::Q64,
562 };
563
564 let rest = value
566 .trim_start_matches(|c: char| c.is_ascii_digit())
567 .get(1..)
568 .unwrap_or_default();
569
570 let (element_code, rest) = {
571 let mut chars = rest.chars();
572 let element_code = chars.next().ok_or_else(|| {
573 format!(
574 "Variable length array format {} names no element type",
575 value
576 )
577 })?;
578 (element_code, chars.as_str())
579 };
580
581 let element = TableElementFormat::from_code(element_code).ok_or_else(|| {
582 format!(
583 "Variable length array format {} has an invalid element type: {}",
584 value, element_code
585 )
586 })?;
587
588 let max = match rest
590 .trim()
591 .strip_prefix('(')
592 .and_then(|rest| rest.strip_suffix(')'))
593 {
594 Some(max) => max.trim().parse::<usize>().map_err(|_| {
595 format!(
596 "Variable length array format {} has an invalid maximum",
597 value
598 )
599 })?,
600 None if rest.trim().is_empty() => 0,
601 None => {
602 return Err(From::from(format!(
603 "Trailing characters in variable length array format {}",
604 value
605 )));
606 }
607 };
608
609 Ok(TableColumnFormat::VariableLengthArray {
610 element,
611 descriptor,
612 max,
613 })
614}
615
616fn extract_parts(value: &str) -> Result<(usize, char, usize), Box<dyn Error + Send + Sync>> {
617 let mut chars = value.chars().peekable();
618 let mut repeat_str = String::new();
619 while let Some(c) = chars.peek() {
620 if c.is_ascii_digit() {
621 repeat_str.push(*c);
622 chars.next();
623 } else {
624 break;
625 }
626 }
627
628 let repeat = if repeat_str.is_empty() {
629 1
630 } else {
631 repeat_str
632 .parse::<usize>()
633 .map_err(|_| "Invalid repeat count")?
634 };
635
636 let code = chars
638 .next()
639 .ok_or_else(|| "Missing format code".to_string())?;
640
641 let mut width_str = String::new();
642 while let Some(c) = chars.peek() {
643 if c.is_ascii_digit() {
644 width_str.push(*c);
645 chars.next();
646 } else {
647 break;
648 }
649 }
650
651 let width = if width_str.is_empty() {
652 0
653 } else {
654 width_str
655 .parse::<usize>()
656 .map_err(|_| "Invalid string width")?
657 };
658
659 Ok((repeat, code, width))
660}
661
662#[cfg(test)]
663mod tests {
664 use super::{ArrayDescriptor, TableColumnFormat, TableElementFormat};
665 use crate::bin_table::Value;
666
667 fn format(tform: &str) -> TableColumnFormat {
668 TableColumnFormat::try_from(tform.to_string())
669 .unwrap_or_else(|error| panic!("{tform} should parse: {error}"))
670 }
671
672 #[test]
675 fn every_format_code_reports_its_standard_width() {
676 let cases = [
677 ("1L", 1),
678 ("8L", 8),
679 ("1X", 1),
681 ("8X", 1),
682 ("9X", 2),
683 ("16X", 2),
684 ("17X", 3),
685 ("1B", 1),
686 ("4B", 4),
687 ("1I", 2),
688 ("4I", 8),
689 ("1J", 4),
690 ("1K", 8),
691 ("1E", 4),
692 ("1D", 8),
693 ("1C", 8),
695 ("3C", 24),
696 ("1M", 16),
697 ("3M", 48),
698 ("20A", 20),
700 ("60A20", 60),
701 ];
702
703 for (tform, expected) in cases {
704 assert_eq!(format(tform).bytes_len(), expected, "TFORM {tform}");
705 }
706 }
707
708 #[test]
709 fn element_counts_match_the_repeat_count() {
710 assert_eq!(format("20A").len(), 1);
711 assert_eq!(format("60A20").len(), 3);
712 assert_eq!(format("4J").len(), 4);
713 assert_eq!(format("3C").len(), 3);
714 }
715
716 #[test]
717 fn single_precision_complex_decodes_both_components() {
718 let mut bytes = Vec::new();
719 bytes.extend_from_slice(&1.5_f32.to_be_bytes());
720 bytes.extend_from_slice(&(-2.5_f32).to_be_bytes());
721
722 let Ok(Value::C32(values)) = format("1C").parse_into_value(&bytes, &[]) else {
723 panic!("a 1C column should decode to a complex value");
724 };
725
726 assert_eq!(values, vec![(1.5, -2.5)]);
727 }
728
729 #[test]
730 fn double_precision_complex_decodes_both_components() {
731 let mut bytes = Vec::new();
732 bytes.extend_from_slice(&1.5_f64.to_be_bytes());
733 bytes.extend_from_slice(&(-2.5_f64).to_be_bytes());
734
735 let Ok(Value::M64(values)) = format("1M").parse_into_value(&bytes, &[]) else {
736 panic!("a 1M column should decode to a complex value");
737 };
738
739 assert_eq!(values, vec![(1.5, -2.5)]);
740 }
741
742 #[test]
743 fn a_string_array_splits_into_substrings_of_the_declared_width() {
744 let Ok(Value::StringArray(values)) =
747 format("15A5").parse_into_value(b"alphabeta gamma", &[])
748 else {
749 panic!("a 15A5 column should decode to a string array");
750 };
751
752 assert_eq!(values, vec!["alpha", "beta", "gamma"]);
753 }
754
755 #[test]
756 fn logical_columns_distinguish_true_from_false() {
757 let Ok(Value::Boolean(values)) = format("3L").parse_into_value(b"TF\0", &[]) else {
759 panic!("a 3L column should decode to logicals");
760 };
761
762 assert_eq!(values, vec![true, false, false]);
763 }
764
765 #[test]
766 fn a_row_too_short_for_the_column_is_an_error() {
767 let error = format("4J")
768 .parse_into_value(&[0, 0, 0, 1, 0, 0], &[])
769 .expect_err("a 16 byte column cannot be read from 6 bytes");
770
771 assert!(error.to_string().contains("needs 16 bytes"), "got: {error}");
772 }
773
774 #[test]
775 fn variable_length_array_formats_parse() {
776 assert_eq!(
777 format("1PJ(10)"),
778 TableColumnFormat::VariableLengthArray {
779 element: TableElementFormat::I32,
780 descriptor: ArrayDescriptor::P32,
781 max: 10,
782 }
783 );
784
785 assert_eq!(
787 format("1QE"),
788 TableColumnFormat::VariableLengthArray {
789 element: TableElementFormat::F32,
790 descriptor: ArrayDescriptor::Q64,
791 max: 0,
792 }
793 );
794
795 assert_eq!(format("1PJ(10)").bytes_len(), 8);
797 assert_eq!(format("1QE").bytes_len(), 16);
798 }
799
800 #[test]
801 fn a_variable_length_array_repeat_count_above_one_is_rejected() {
802 let error = TableColumnFormat::try_from("2PJ(10)".to_string())
804 .expect_err("a repeat count above one is invalid");
805
806 assert!(error.to_string().contains("repeat count"), "got: {error}");
807 }
808
809 #[test]
810 fn a_variable_length_array_reads_its_values_from_the_heap() {
811 let mut descriptor = Vec::new();
813 descriptor.extend_from_slice(&3_i32.to_be_bytes());
814 descriptor.extend_from_slice(&4_i32.to_be_bytes());
815
816 let mut heap = vec![0xFF; 4];
817 for value in [7_i32, 8, 9] {
818 heap.extend_from_slice(&value.to_be_bytes());
819 }
820
821 let Ok(Value::I32(values)) = format("1PJ(10)").parse_into_value(&descriptor, &heap) else {
822 panic!("a 1PJ column should decode to its heap values");
823 };
824
825 assert_eq!(values, vec![7, 8, 9]);
826 }
827
828 #[test]
829 fn an_empty_variable_length_array_points_nowhere() {
830 let descriptor = [0_u8; 8];
831
832 let Ok(Value::I32(values)) = format("1PJ(10)").parse_into_value(&descriptor, &[]) else {
833 panic!("a zero-length array should decode to no values");
834 };
835
836 assert!(values.is_empty());
837 }
838
839 #[test]
840 fn a_variable_length_array_past_the_end_of_the_heap_is_an_error() {
841 let mut descriptor = Vec::new();
842 descriptor.extend_from_slice(&3_i32.to_be_bytes());
843 descriptor.extend_from_slice(&100_i32.to_be_bytes());
844
845 let error = format("1PJ(10)")
846 .parse_into_value(&descriptor, &[0; 8])
847 .expect_err("an out of range descriptor cannot be followed");
848
849 assert!(error.to_string().contains("heap"), "got: {error}");
850 }
851}