1use asdf_yaml::{CollectionStyle, Document, Node, NodeData, NodeId, ScalarStyle};
9
10use crate::core::datatype::{ByteOrder, Datatype, ScalarType};
11use crate::core::ndarray::{Ndarray, Source};
12use crate::error::{Result, err};
13
14#[derive(Clone, PartialEq, Debug)]
16pub enum Element {
17 Int(i64),
19 Uint(u64),
21 Float(f64),
24 Bool(bool),
26 Text(String),
28 Complex(f64, f64),
30 Record(Vec<Element>),
32}
33
34fn read_uint(bytes: &[u8], order: ByteOrder) -> u64 {
36 let mut acc = 0u64;
37 if order == ByteOrder::Big {
38 for b in bytes {
39 acc = (acc << 8) | u64::from(*b);
40 }
41 } else {
42 for b in bytes.iter().rev() {
43 acc = (acc << 8) | u64::from(*b);
44 }
45 }
46 acc
47}
48
49fn sign_extend(value: u64, bytes: usize) -> i64 {
51 let bits = bytes * 8;
52 if bits >= 64 {
53 return value as i64;
54 }
55 let shift = 64 - bits;
56 ((value << shift) as i64) >> shift
57}
58
59fn effective_order(field: ByteOrder, array: ByteOrder) -> ByteOrder {
61 match field {
62 ByteOrder::Big | ByteOrder::Little => field,
63 _ => match array {
65 ByteOrder::Big | ByteOrder::Little => array,
66 _ => ByteOrder::Little,
67 },
68 }
69}
70
71fn decode_one(datatype: &Datatype, bytes: &[u8], array_order: ByteOrder) -> Result<Element> {
73 if datatype.is_structured() {
74 let mut fields = Vec::with_capacity(datatype.fields.len());
75 let mut offset = 0usize;
76 for field in &datatype.fields {
77 let width = field.datatype.item_size() as usize;
78 let slice = bytes.get(offset..offset + width).ok_or_else(|| {
79 err!(UnexpectedEof, "compound element truncated at field offset {offset}")
80 })?;
81 fields.push(decode_one(&field.datatype, slice, array_order)?);
82 offset += width;
83 }
84 return Ok(Element::Record(fields));
85 }
86
87 let order = effective_order(datatype.byteorder, array_order);
88 let width = datatype.item_size() as usize;
89 let raw = bytes.get(..width).ok_or_else(|| {
90 err!(UnexpectedEof, "element needs {width} bytes, {} available", bytes.len())
91 })?;
92
93 Ok(match datatype.scalar {
94 ScalarType::Bool8 => Element::Bool(raw[0] != 0),
95
96 ScalarType::Uint8 | ScalarType::Uint16 | ScalarType::Uint32 | ScalarType::Uint64 => {
97 Element::Uint(read_uint(raw, order))
98 }
99
100 ScalarType::Int8 | ScalarType::Int16 | ScalarType::Int32 | ScalarType::Int64 => {
101 Element::Int(sign_extend(read_uint(raw, order), width))
102 }
103
104 ScalarType::Float16 => {
105 let bits = read_uint(raw, order) as u16;
106 Element::Float(f64::from(half::f16::from_bits(bits)))
107 }
108 ScalarType::Float32 => {
109 let bits = read_uint(raw, order) as u32;
110 Element::Float(f64::from(f32::from_bits(bits)))
111 }
112 ScalarType::Float64 => Element::Float(f64::from_bits(read_uint(raw, order))),
113
114 ScalarType::Complex64 => {
115 let re = f32::from_bits(read_uint(&raw[..4], order) as u32);
116 let im = f32::from_bits(read_uint(&raw[4..], order) as u32);
117 Element::Complex(f64::from(re), f64::from(im))
118 }
119 ScalarType::Complex128 => {
120 let re = f64::from_bits(read_uint(&raw[..8], order));
121 let im = f64::from_bits(read_uint(&raw[8..], order));
122 Element::Complex(re, im)
123 }
124
125 ScalarType::Ascii => {
126 let end = raw.iter().position(|b| *b == 0).unwrap_or(raw.len());
128 Element::Text(String::from_utf8_lossy(&raw[..end]).into_owned())
129 }
130 ScalarType::Ucs4 => {
131 let mut out = String::new();
132 let (quads, _) = raw.as_chunks::<4>();
133 for chunk in quads {
134 let cp = read_uint(chunk, order) as u32;
135 if cp == 0 {
136 break;
137 }
138 out.push(char::from_u32(cp).unwrap_or(char::REPLACEMENT_CHARACTER));
139 }
140 Element::Text(out)
141 }
142
143 ScalarType::Unknown | ScalarType::Structured => {
144 return Err(err!(
145 InvalidArgument,
146 "cannot decode a {} element",
147 datatype.scalar.name()
148 ));
149 }
150 })
151}
152
153#[deny(clippy::arithmetic_side_effects)]
158pub fn decode_all(nd: &Ndarray, shape: &[u64], bytes: &[u8]) -> Result<Vec<Element>> {
159 let item = nd.datatype.item_size();
160 if item == 0 {
161 return Err(err!(InvalidArgument, "cannot decode elements of zero width"));
162 }
163
164 let count = crate::core::ndarray::element_count(shape)?;
165
166 let needed = count
174 .checked_mul(item)
175 .and_then(|n| n.checked_add(nd.offset))
176 .ok_or_else(|| err!(OverLimit, "array's extent does not fit in 64 bits"))?;
177 if needed > bytes.len() as u64 {
178 return Err(err!(
179 UnexpectedEof,
180 "array of {count} elements needs {needed} bytes but the block holds {}",
181 bytes.len()
182 ));
183 }
184
185 let count = usize::try_from(count)
186 .map_err(|_| err!(OverLimit, "array has too many elements for this platform"))?;
187
188 let strides = match &nd.strides {
189 Some(s) if s.len() == shape.len() => s.clone(),
190 Some(s) => {
191 return Err(err!(
192 InvalidArgument,
193 "strides have {} entries but the shape has {}",
194 s.len(),
195 shape.len()
196 ));
197 }
198 None => Ndarray::c_strides(shape, item)
199 .ok_or_else(|| err!(OverLimit, "shape {shape:?} is too large to stride"))?,
200 };
201
202 let base = usize::try_from(nd.offset)
203 .map_err(|_| err!(InvalidArgument, "ndarray offset overflows this platform"))?;
204
205 let mut out = Vec::with_capacity(count);
206 let mut index = vec![0u64; shape.len()];
207
208 for _ in 0..count {
209 let mut pos = i64::try_from(base)
218 .map_err(|_| err!(InvalidArgument, "ndarray offset overflows this platform"))?;
219 for (dim, idx) in index.iter().enumerate() {
220 let step = i64::try_from(*idx)
221 .ok()
222 .and_then(|i| strides[dim].checked_mul(i))
223 .ok_or_else(|| err!(OverLimit, "strides address a position past 64 bits"))?;
224 pos = pos
225 .checked_add(step)
226 .ok_or_else(|| err!(OverLimit, "strides address a position past 64 bits"))?;
227 }
228 let pos = usize::try_from(pos)
229 .map_err(|_| err!(InvalidArgument, "strides address a negative offset"))?;
230
231 let slice = bytes.get(pos..).ok_or_else(|| {
232 err!(UnexpectedEof, "element at byte {pos} is past the end of the block")
233 })?;
234 out.push(decode_one(&nd.datatype, slice, nd.byteorder)?);
235
236 #[allow(clippy::arithmetic_side_effects, reason = "bounded by shape[dim] on the next line")]
240 for dim in (0..shape.len()).rev() {
241 index[dim] += 1;
242 if index[dim] < shape[dim] {
243 break;
244 }
245 index[dim] = 0;
246 }
247 }
248 Ok(out)
249}
250
251pub fn decode_inline(doc: &Document, array: &Ndarray, shape: &[u64]) -> Result<Vec<Element>> {
265 let Source::Inline(root) = array.source else {
266 return Err(err!(InvalidArgument, "this array's data is not inline"));
267 };
268
269 let expected = crate::core::ndarray::element_count(shape)?;
270
271 let ceiling = doc.node_count() as u64;
278 if expected > ceiling {
279 return Err(err!(
280 InvalidArgument,
281 "inline array of {expected} elements, but the whole tree holds only \
282 {ceiling} nodes"
283 ));
284 }
285
286 let mut out = Vec::new();
287 collect_inline(doc, root, &array.datatype, shape, &mut out)?;
288
289 if out.len() as u64 != expected {
290 return Err(err!(
291 InvalidArgument,
292 "inline data holds {} elements but the shape calls for {expected}",
293 out.len()
294 ));
295 }
296 Ok(out)
297}
298
299fn collect_inline(
301 doc: &Document,
302 node: NodeId,
303 datatype: &Datatype,
304 shape: &[u64],
305 out: &mut Vec<Element>,
306) -> Result<()> {
307 let resolved = doc.resolve(node);
308
309 if shape.len() > MAX_INLINE_DEPTH {
310 return Err(err!(
311 InvalidArgument,
312 "inline array is nested {} deep, past the {MAX_INLINE_DEPTH}-dimension limit",
313 shape.len()
314 ));
315 }
316
317 let Some((dim, rest)) = shape.split_first() else {
318 out.push(leaf_element(doc, resolved, datatype)?);
320 return Ok(());
321 };
322
323 let items = doc.sequence_items(resolved).map(<[_]>::to_vec).ok_or_else(|| {
324 err!(InvalidArgument, "inline array data is not nested {} deep", shape.len())
325 })?;
326 if items.len() as u64 != *dim {
327 return Err(err!(
328 InvalidArgument,
329 "inline dimension holds {} entries but the shape calls for {dim}",
330 items.len()
331 ));
332 }
333 for item in items {
334 collect_inline(doc, item, datatype, rest, out)?;
335 }
336 Ok(())
337}
338
339const MAX_INLINE_DEPTH: usize = 64;
345
346fn leaf_element(doc: &Document, node: NodeId, datatype: &Datatype) -> Result<Element> {
348 if !datatype.fields.is_empty() {
349 let items = doc.sequence_items(node).map(<[_]>::to_vec).ok_or_else(|| {
350 err!(InvalidArgument, "a compound element must be a sequence of its fields")
351 })?;
352 if items.len() != datatype.fields.len() {
353 return Err(err!(
354 InvalidArgument,
355 "a compound element holds {} values but the datatype has {} fields",
356 items.len(),
357 datatype.fields.len()
358 ));
359 }
360 let mut record = Vec::with_capacity(items.len());
361 for (item, field) in items.iter().zip(datatype.fields.iter()) {
362 record.push(leaf_element(doc, doc.resolve(*item), &field.datatype)?);
363 }
364 return Ok(Element::Record(record));
365 }
366
367 let text = doc
368 .resolved(node)
369 .as_str()
370 .ok_or_else(|| err!(InvalidArgument, "inline array data holds a non-scalar leaf"))?;
371 scalar_element(text, datatype.scalar)
372}
373
374fn scalar_element(text: &str, scalar: ScalarType) -> Result<Element> {
376 use ScalarType as S;
377
378 if matches!(text, "null" | "~" | "") {
381 return Ok(match scalar {
382 S::Float16 | S::Float32 | S::Float64 => Element::Float(0.0),
383 S::Complex64 | S::Complex128 => Element::Complex(0.0, 0.0),
384 S::Bool8 => Element::Bool(false),
385 S::Ascii | S::Ucs4 => Element::Text(String::new()),
386 S::Uint8 | S::Uint16 | S::Uint32 | S::Uint64 => Element::Uint(0),
387 _ => Element::Int(0),
388 });
389 }
390
391 let bad = |what: &str| err!(InvalidArgument, "inline {what} value {text:?} does not parse");
392 match scalar {
393 S::Uint8 | S::Uint16 | S::Uint32 | S::Uint64 => {
394 Ok(Element::Uint(text.parse::<u64>().map_err(|_| bad("unsigned"))?))
395 }
396 S::Int8 | S::Int16 | S::Int32 | S::Int64 => {
397 Ok(Element::Int(text.parse::<i64>().map_err(|_| bad("integer"))?))
398 }
399 S::Float16 | S::Float32 | S::Float64 => Ok(Element::Float(parse_inline_float(text)?)),
400 S::Complex64 | S::Complex128 => {
401 let (re, im) = parse_inline_complex(text)?;
402 Ok(Element::Complex(re, im))
403 }
404 S::Bool8 => Ok(Element::Bool(matches!(text, "true" | "True" | "1"))),
405 S::Ascii | S::Ucs4 => Ok(Element::Text(text.to_string())),
406 S::Unknown | S::Structured => {
407 Err(err!(InvalidArgument, "inline data needs a known scalar datatype"))
408 }
409 }
410}
411
412fn parse_inline_float(text: &str) -> Result<f64> {
414 match text {
415 ".nan" | ".NaN" | ".NAN" | "nan" => return Ok(f64::NAN),
416 ".inf" | ".Inf" | ".INF" | "inf" => return Ok(f64::INFINITY),
417 "-.inf" | "-.Inf" | "-.INF" | "-inf" => return Ok(f64::NEG_INFINITY),
418 _ => {}
419 }
420 text.parse::<f64>()
421 .map_err(|_| err!(InvalidArgument, "inline float value {text:?} does not parse"))
422}
423
424fn parse_inline_complex(text: &str) -> Result<(f64, f64)> {
429 let body = text.trim();
430 let body = body.strip_prefix('(').map_or(body, |rest| rest.strip_suffix(')').unwrap_or(rest));
431
432 let imaginary_unit = |c: char| matches!(c, 'i' | 'I' | 'j' | 'J');
433 let Some(unit) = body.char_indices().rev().find(|(_, c)| imaginary_unit(*c)) else {
434 return Ok((parse_inline_float(body)?, 0.0));
436 };
437 if unit.0 + unit.1.len_utf8() != body.len() {
439 return Err(err!(InvalidArgument, "inline complex value {text:?} does not parse"));
440 }
441 let without_unit = &body[..unit.0];
442
443 let split = without_unit
446 .char_indices()
447 .rev()
448 .find(|(index, c)| {
449 (*c == '+' || *c == '-')
450 && *index > 0
451 && !matches!(without_unit.as_bytes()[index - 1], b'e' | b'E')
452 })
453 .map(|(index, _)| index);
454
455 match split {
456 None => Ok((0.0, parse_inline_float(without_unit)?)),
457 Some(index) => {
458 let (real, imaginary) = without_unit.split_at(index);
459 let imaginary = match imaginary {
461 "+" => "1",
462 "-" => "-1",
463 other => other,
464 };
465 Ok((parse_inline_float(real)?, parse_inline_float(imaginary)?))
466 }
467 }
468}
469
470const COMPLEX_TAG: &str = "tag:stsci.edu:asdf/core/complex-1.0.0";
472
473pub fn format_float(value: f64) -> String {
477 if value.is_nan() {
478 return ".nan".to_string();
479 }
480 if value.is_infinite() {
481 return if value.is_sign_negative() { "-.inf".into() } else { ".inf".into() };
482 }
483 let mut s = format!("{value}");
486 if !s.contains('.') && !s.contains('e') && !s.contains("inf") && !s.contains("nan") {
487 s.push_str(".0");
488 }
489 s
490}
491
492fn element_to_node(doc: &mut Document, element: &Element) -> NodeId {
494 match element {
495 Element::Int(v) => doc.add_scalar(v.to_string()),
496 Element::Uint(v) => doc.add_scalar(v.to_string()),
497 Element::Bool(v) => doc.add_scalar(if *v { "true" } else { "false" }),
498 Element::Float(v) => doc.add_scalar(format_float(*v)),
499 Element::Text(s) => doc.add_scalar_styled(s.clone(), ScalarStyle::SingleQuoted),
502 Element::Complex(re, im) => {
503 let node = Node::scalar(crate::core::pyrepr::repr_complex(*re, *im))
507 .with_tag(asdf_yaml::Tag::parse(COMPLEX_TAG));
508 doc.add(node)
509 }
510 Element::Record(fields) => {
511 let items: Vec<NodeId> = fields.iter().map(|f| element_to_node(doc, f)).collect();
512 doc.add_sequence(items)
513 }
514 }
515}
516
517pub fn nest(doc: &mut Document, elements: &[Element], shape: &[u64]) -> NodeId {
519 fn build(
520 doc: &mut Document,
521 elements: &[Element],
522 shape: &[u64],
523 cursor: &mut usize,
524 ) -> NodeId {
525 match shape.split_first() {
526 None => {
527 let node = element_to_node(doc, &elements[*cursor]);
528 *cursor += 1;
529 node
530 }
531 Some((dim, rest)) => {
532 let mut items = Vec::with_capacity(*dim as usize);
533 for _ in 0..*dim {
534 items.push(build(doc, elements, rest, cursor));
535 }
536 let id = doc.add_sequence(items);
537 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(id).data {
540 *style = CollectionStyle::Flow;
541 }
542 id
543 }
544 }
545 }
546
547 let mut cursor = 0;
548 build(doc, elements, shape, &mut cursor)
549}
550
551pub fn inline_ndarray(
557 doc: &mut Document,
558 id: NodeId,
559 elements: &[Element],
560 shape: &[u64],
561) -> Result<()> {
562 let expected = crate::core::ndarray::element_count(shape)?;
568 if expected != elements.len() as u64 {
569 return Err(err!(
570 InvalidArgument,
571 "shape {shape:?} describes {expected} elements but {} were given",
572 elements.len()
573 ));
574 }
575
576 let data = nest(doc, elements, shape);
577 let target = doc.resolve(id);
578
579 if !doc.node(target).is_mapping() {
580 return Ok(());
582 }
583
584 doc.mapping_remove(target, "source");
585 for key in ["byteorder", "offset", "strides"] {
586 doc.mapping_remove(target, key);
587 }
588 if let Some(dt) = doc.mapping_get(target, "datatype")
591 && let Some(fields) = doc.sequence_items(dt).map(<[_]>::to_vec)
592 {
593 for field in fields {
594 let field = doc.resolve(field);
595 if doc.node(field).is_mapping() {
596 doc.mapping_remove(field, "byteorder");
597 }
598 }
599 }
600 doc.mapping_set(target, "data", data);
601
602 let dims: Vec<NodeId> = shape.iter().map(|d| doc.add_scalar(d.to_string())).collect();
604 let shape_node = doc.add_sequence(dims);
605 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(shape_node).data {
606 *style = CollectionStyle::Flow;
607 }
608 doc.mapping_set(target, "shape", shape_node);
609 Ok(())
610}
611
612pub fn element_node(doc: &mut Document, element: &Element) -> NodeId {
614 element_to_node(doc, element)
615}
616
617pub fn tagged(doc: &mut Document, node: Node, tag: asdf_yaml::Tag) -> NodeId {
619 doc.add(node.with_tag(tag))
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625 use asdf_yaml::parse_document;
626
627 fn ndarray(yaml: &str) -> Ndarray {
628 let doc = parse_document(yaml).unwrap();
629 let root = doc.root().unwrap();
630 Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap()
631 }
632
633 #[test]
634 fn inline_integers_decode_from_the_tree() {
635 let doc = parse_document(
636 "a:\n data: [[1, 2, 3], [4, 5, 6]]\n datatype: int32\n shape: [2, 3]\n",
637 )
638 .unwrap();
639 let root = doc.root().unwrap();
640 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
641 let shape = nd.resolved_shape(None).unwrap();
642 assert_eq!(shape, vec![2, 3]);
643
644 let els = decode_inline(&doc, &nd, &shape).unwrap();
645 assert_eq!(
646 els,
647 (1..=6).map(Element::Int).collect::<Vec<_>>(),
648 "row-major order, flattened"
649 );
650 }
651
652 #[test]
653 fn inline_floats_accept_yamls_non_finite_spellings() {
654 let doc = parse_document(
655 "a:\n data: [1.5, .inf, -.inf, .nan]\n datatype: float64\n shape: [4]\n",
656 )
657 .unwrap();
658 let root = doc.root().unwrap();
659 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
660 let els = decode_inline(&doc, &nd, &[4]).unwrap();
661
662 assert_eq!(els[0], Element::Float(1.5));
663 assert_eq!(els[1], Element::Float(f64::INFINITY));
664 assert_eq!(els[2], Element::Float(f64::NEG_INFINITY));
665 let Element::Float(nan) = els[3] else { panic!("{:?}", els[3]) };
666 assert!(nan.is_nan());
667 }
668
669 #[test]
671 fn inline_complex_accepts_every_spelling_the_schema_allows() {
672 let cases = [
673 ("0j", (0.0, 0.0)),
674 ("(1+2j)", (1.0, 2.0)),
675 ("1+2j", (1.0, 2.0)),
676 ("(1-2j)", (1.0, -2.0)),
677 ("-1j", (0.0, -1.0)),
678 ("(-0+0j)", (-0.0, 0.0)),
679 ("3", (3.0, 0.0)),
680 ("2i", (0.0, 2.0)),
681 ("(1.5e-3+2.5e+4j)", (1.5e-3, 2.5e4)),
682 ("(1+j)", (1.0, 1.0)),
684 ("(1-j)", (1.0, -1.0)),
685 ];
686 for (text, (re, im)) in cases {
687 let got = parse_inline_complex(text).unwrap_or_else(|e| panic!("{text}: {e}"));
688 assert_eq!(got.0, re, "real part of {text}");
689 assert_eq!(got.1, im, "imaginary part of {text}");
690 }
691
692 let (re, im) = parse_inline_complex("(nan-infj)").unwrap();
694 assert!(re.is_nan());
695 assert_eq!(im, f64::NEG_INFINITY);
696 }
697
698 #[test]
701 fn complex_spellings_round_trip_through_the_parser() {
702 let values = [
703 (0.0, 0.0),
704 (-0.0, 0.0),
705 (1.0, 2.0),
706 (1.0, -2.0),
707 (0.0, -1.0),
708 (1.5e-3, 2.5e4),
709 (f64::MAX, f64::MIN_POSITIVE),
710 ];
711 for (re, im) in values {
712 let text = crate::core::pyrepr::repr_complex(re, im);
713 let (back_re, back_im) = parse_inline_complex(&text).unwrap();
714 assert_eq!(back_re.to_bits(), re.to_bits(), "{text}");
715 assert_eq!(back_im.to_bits(), im.to_bits(), "{text}");
716 }
717 }
718
719 #[test]
720 fn inline_compound_records_stay_grouped() {
721 let doc = parse_document(
722 "a:\n data: [[1, 2.5], [3, 4.5]]\n shape: [2]\n datatype:\n \
723 - {name: n, datatype: int32}\n - {name: x, datatype: float64}\n",
724 )
725 .unwrap();
726 let root = doc.root().unwrap();
727 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
728 let els = decode_inline(&doc, &nd, &[2]).unwrap();
729 assert_eq!(
730 els,
731 vec![
732 Element::Record(vec![Element::Int(1), Element::Float(2.5)]),
733 Element::Record(vec![Element::Int(3), Element::Float(4.5)]),
734 ]
735 );
736 }
737
738 #[test]
739 fn inline_data_must_match_the_declared_shape() {
740 let doc =
741 parse_document("a:\n data: [1, 2, 3]\n datatype: int32\n shape: [4]\n").unwrap();
742 let root = doc.root().unwrap();
743 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
744 let err = decode_inline(&doc, &nd, &[4]).unwrap_err();
745 assert!(err.message().contains("shape calls for 4"), "{err}");
746 }
747
748 #[test]
751 fn a_block_array_survives_a_trip_through_inline_form() {
752 let nd =
753 ndarray("a:\n source: 0\n shape: [5]\n datatype: float64\n byteorder: little\n");
754 let values = [1.5f64, -2.25, 0.0, f64::MAX, -0.125];
755 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
756 let original = decode_all(&nd, &[5], &bytes).unwrap();
757
758 let mut doc = parse_document(
759 "a:\n source: 0\n shape: [5]\n datatype: float64\n byteorder: little\n",
760 )
761 .unwrap();
762 let root = doc.root().unwrap();
763 let node = doc.mapping_get(root, "a").unwrap();
764 inline_ndarray(&mut doc, node, &original, &[5]).unwrap();
765
766 let inlined = Ndarray::parse(&doc, node).unwrap();
767 let read_back = decode_inline(&doc, &inlined, &[5]).unwrap();
768 assert_eq!(read_back, original);
769 }
770
771 #[test]
772 fn decodes_little_endian_integers() {
773 let nd = ndarray("a:\n source: 0\n shape: [4]\n datatype: int32\n byteorder: little\n");
774 let mut bytes = Vec::new();
775 for v in [1i32, -1, 256, i32::MIN] {
776 bytes.extend_from_slice(&v.to_le_bytes());
777 }
778 let els = decode_all(&nd, &[4], &bytes).unwrap();
779 assert_eq!(
780 els,
781 vec![
782 Element::Int(1),
783 Element::Int(-1),
784 Element::Int(256),
785 Element::Int(i64::from(i32::MIN)),
786 ]
787 );
788 }
789
790 #[test]
791 fn decodes_big_endian_integers() {
792 let nd = ndarray("a:\n source: 0\n shape: [3]\n datatype: int16\n byteorder: big\n");
793 let mut bytes = Vec::new();
794 for v in [1i16, -2, 1000] {
795 bytes.extend_from_slice(&v.to_be_bytes());
796 }
797 let els = decode_all(&nd, &[3], &bytes).unwrap();
798 assert_eq!(els, vec![Element::Int(1), Element::Int(-2), Element::Int(1000)]);
799 }
800
801 #[test]
802 fn byte_order_actually_changes_the_value() {
803 let bytes = [0x01u8, 0x00];
804 let le =
805 ndarray("a:\n source: 0\n shape: [1]\n datatype: uint16\n byteorder: little\n");
806 let be = ndarray("a:\n source: 0\n shape: [1]\n datatype: uint16\n byteorder: big\n");
807 assert_eq!(decode_all(&le, &[1], &bytes).unwrap(), vec![Element::Uint(1)]);
808 assert_eq!(decode_all(&be, &[1], &bytes).unwrap(), vec![Element::Uint(256)]);
809 }
810
811 #[test]
812 fn decodes_floats_of_every_width() {
813 let nd =
814 ndarray("a:\n source: 0\n shape: [2]\n datatype: float64\n byteorder: little\n");
815 let mut bytes = Vec::new();
816 bytes.extend_from_slice(&1.5f64.to_le_bytes());
817 bytes.extend_from_slice(&(-0.25f64).to_le_bytes());
818 assert_eq!(
819 decode_all(&nd, &[2], &bytes).unwrap(),
820 vec![Element::Float(1.5), Element::Float(-0.25)]
821 );
822
823 let nd =
824 ndarray("a:\n source: 0\n shape: [1]\n datatype: float32\n byteorder: little\n");
825 assert_eq!(
826 decode_all(&nd, &[1], &2.5f32.to_le_bytes()).unwrap(),
827 vec![Element::Float(2.5)]
828 );
829
830 let nd =
831 ndarray("a:\n source: 0\n shape: [1]\n datatype: float16\n byteorder: little\n");
832 let h = half::f16::from_f32(0.5);
833 assert_eq!(
834 decode_all(&nd, &[1], &h.to_bits().to_le_bytes()).unwrap(),
835 vec![Element::Float(0.5)]
836 );
837 }
838
839 #[test]
840 fn decodes_bools_and_text() {
841 let nd = ndarray("a:\n source: 0\n shape: [2]\n datatype: bool8\n byteorder: little\n");
842 assert_eq!(
843 decode_all(&nd, &[2], &[0u8, 1]).unwrap(),
844 vec![Element::Bool(false), Element::Bool(true)]
845 );
846
847 let nd = ndarray(
849 "a:\n source: 0\n shape: [2]\n datatype: ['ascii', 4]\n byteorder: little\n",
850 );
851 let bytes = b"M31\0Cas\0";
852 assert_eq!(
853 decode_all(&nd, &[2], bytes).unwrap(),
854 vec![Element::Text("M31".into()), Element::Text("Cas".into())]
855 );
856 }
857
858 #[test]
859 fn decodes_ucs4_text() {
860 let nd = ndarray(
861 "a:\n source: 0\n shape: [1]\n datatype: ['ucs4', 3]\n byteorder: little\n",
862 );
863 let mut bytes = Vec::new();
864 for cp in ['a' as u32, 0x00E9 , 0] {
865 bytes.extend_from_slice(&cp.to_le_bytes());
866 }
867 assert_eq!(decode_all(&nd, &[1], &bytes).unwrap(), vec![Element::Text("aé".into())]);
868 }
869
870 #[test]
871 fn honours_offset() {
872 let nd = ndarray(
873 "a:\n source: 0\n shape: [2]\n datatype: uint8\n byteorder: little\n offset: 3\n",
874 );
875 let bytes = [9u8, 9, 9, 1, 2];
876 assert_eq!(
877 decode_all(&nd, &[2], &bytes).unwrap(),
878 vec![Element::Uint(1), Element::Uint(2)]
879 );
880 }
881
882 #[test]
883 fn honours_strides_for_a_fortran_order_array() {
884 let nd = ndarray(
886 "a:\n source: 0\n shape: [2, 3]\n datatype: uint8\n byteorder: little\n \
887 strides: [1, 2]\n",
888 );
889 let bytes = [1u8, 4, 2, 5, 3, 6];
891 let els = decode_all(&nd, &[2, 3], &bytes).unwrap();
892 let values: Vec<u64> = els
893 .iter()
894 .map(|e| match e {
895 Element::Uint(v) => *v,
896 _ => unreachable!(),
897 })
898 .collect();
899 assert_eq!(values, vec![1, 2, 3, 4, 5, 6]);
901 }
902
903 #[test]
904 fn honours_strides_for_a_tile_view() {
905 let nd = ndarray(
907 "a:\n source: 0\n shape: [2, 2]\n datatype: uint8\n byteorder: little\n \
908 strides: [4, 1]\n offset: 5\n",
909 );
910 let bytes: Vec<u8> = (0..16).collect();
911 let els = decode_all(&nd, &[2, 2], &bytes).unwrap();
912 let values: Vec<u64> = els
913 .iter()
914 .map(|e| match e {
915 Element::Uint(v) => *v,
916 _ => unreachable!(),
917 })
918 .collect();
919 assert_eq!(values, vec![5, 6, 9, 10]);
920 }
921
922 #[test]
923 fn decodes_compound_records() {
924 let nd = ndarray(
925 "a:\n source: 0\n shape: [2]\n byteorder: little\n \
926 datatype:\n - name: id\n datatype: uint16\n \
927 - name: value\n datatype: float32\n",
928 );
929 let mut bytes = Vec::new();
930 for (id, value) in [(1u16, 1.5f32), (2, -2.5)] {
931 bytes.extend_from_slice(&id.to_le_bytes());
932 bytes.extend_from_slice(&value.to_le_bytes());
933 }
934 let els = decode_all(&nd, &[2], &bytes).unwrap();
935 assert_eq!(
936 els,
937 vec![
938 Element::Record(vec![Element::Uint(1), Element::Float(1.5)]),
939 Element::Record(vec![Element::Uint(2), Element::Float(-2.5)]),
940 ]
941 );
942 }
943
944 #[test]
945 fn truncated_data_is_an_error_not_a_panic() {
946 let nd = ndarray("a:\n source: 0\n shape: [4]\n datatype: int64\n byteorder: little\n");
947 assert!(decode_all(&nd, &[4], &[0u8; 8]).is_err());
948 }
949
950 #[test]
951 fn nesting_reproduces_the_shape() {
952 let mut doc = Document::new();
953 let els: Vec<Element> = (0..6).map(Element::Uint).collect();
954 let node = nest(&mut doc, &els, &[2, 3]);
955 doc.set_root(node);
956
957 assert_eq!(doc.container_len(node), Some(2));
958 let first = doc.sequence_get(node, 0).unwrap();
959 assert_eq!(doc.container_len(first), Some(3));
960 assert_eq!(doc.resolved(doc.sequence_get(first, 2).unwrap()).as_str(), Some("2"));
961 }
962
963 #[test]
964 fn float_formatting_uses_yaml_spellings() {
965 assert_eq!(format_float(f64::NAN), ".nan");
966 assert_eq!(format_float(f64::INFINITY), ".inf");
967 assert_eq!(format_float(f64::NEG_INFINITY), "-.inf");
968 assert_eq!(format_float(1.0), "1.0");
970 assert_eq!(format_float(1.5), "1.5");
971 }
972
973 #[test]
974 fn inlining_replaces_source_with_data() {
975 let mut doc = parse_document(
976 "a:\n source: 0\n shape: [4]\n datatype: uint8\n byteorder: little\n offset: 0\n",
977 )
978 .unwrap();
979 let root = doc.root().unwrap();
980 let nd_id = doc.mapping_get(root, "a").unwrap();
981
982 let els: Vec<Element> = (0..4).map(Element::Uint).collect();
983 inline_ndarray(&mut doc, nd_id, &els, &[4]).unwrap();
984
985 assert!(doc.mapping_get(nd_id, "source").is_none(), "source must be removed");
986 assert!(doc.mapping_get(nd_id, "byteorder").is_none(), "byteorder is meaningless inline");
987 assert!(doc.mapping_get(nd_id, "offset").is_none(), "offset is meaningless inline");
988
989 let data = doc.mapping_get(nd_id, "data").expect("data must be added");
990 assert_eq!(doc.container_len(data), Some(4));
991 assert!(doc.mapping_get(nd_id, "datatype").is_some());
993 }
994}