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
153pub fn decode_all(nd: &Ndarray, shape: &[u64], bytes: &[u8]) -> Result<Vec<Element>> {
158 let item = nd.datatype.item_size();
159 if item == 0 {
160 return Err(err!(InvalidArgument, "cannot decode elements of zero width"));
161 }
162
163 let count = crate::core::ndarray::element_count(shape)?;
164
165 let needed = count
173 .checked_mul(item)
174 .and_then(|n| n.checked_add(nd.offset))
175 .ok_or_else(|| err!(OverLimit, "array's extent does not fit in 64 bits"))?;
176 if needed > bytes.len() as u64 {
177 return Err(err!(
178 UnexpectedEof,
179 "array of {count} elements needs {needed} bytes but the block holds {}",
180 bytes.len()
181 ));
182 }
183
184 let count = usize::try_from(count)
185 .map_err(|_| err!(OverLimit, "array has too many elements for this platform"))?;
186
187 let strides = match &nd.strides {
188 Some(s) if s.len() == shape.len() => s.clone(),
189 Some(s) => {
190 return Err(err!(
191 InvalidArgument,
192 "strides have {} entries but the shape has {}",
193 s.len(),
194 shape.len()
195 ));
196 }
197 None => Ndarray::c_strides(shape, item)
198 .ok_or_else(|| err!(OverLimit, "shape {shape:?} is too large to stride"))?,
199 };
200
201 let base = usize::try_from(nd.offset)
202 .map_err(|_| err!(InvalidArgument, "ndarray offset overflows this platform"))?;
203
204 let mut out = Vec::with_capacity(count);
205 let mut index = vec![0u64; shape.len()];
206
207 for _ in 0..count {
208 let mut pos = base as i64;
210 for (dim, idx) in index.iter().enumerate() {
211 pos += strides[dim] * (*idx as i64);
212 }
213 let pos = usize::try_from(pos)
214 .map_err(|_| err!(InvalidArgument, "strides address a negative offset"))?;
215
216 let slice = bytes.get(pos..).ok_or_else(|| {
217 err!(UnexpectedEof, "element at byte {pos} is past the end of the block")
218 })?;
219 out.push(decode_one(&nd.datatype, slice, nd.byteorder)?);
220
221 for dim in (0..shape.len()).rev() {
223 index[dim] += 1;
224 if index[dim] < shape[dim] {
225 break;
226 }
227 index[dim] = 0;
228 }
229 }
230 Ok(out)
231}
232
233pub fn decode_inline(doc: &Document, array: &Ndarray, shape: &[u64]) -> Result<Vec<Element>> {
247 let Source::Inline(root) = array.source else {
248 return Err(err!(InvalidArgument, "this array's data is not inline"));
249 };
250
251 let expected = crate::core::ndarray::element_count(shape)?;
252 let mut out = Vec::new();
256 collect_inline(doc, root, &array.datatype, shape, &mut out)?;
257
258 if out.len() as u64 != expected {
259 return Err(err!(
260 InvalidArgument,
261 "inline data holds {} elements but the shape calls for {expected}",
262 out.len()
263 ));
264 }
265 Ok(out)
266}
267
268fn collect_inline(
270 doc: &Document,
271 node: NodeId,
272 datatype: &Datatype,
273 shape: &[u64],
274 out: &mut Vec<Element>,
275) -> Result<()> {
276 let resolved = doc.resolve(node);
277
278 let Some((dim, rest)) = shape.split_first() else {
279 out.push(leaf_element(doc, resolved, datatype)?);
281 return Ok(());
282 };
283
284 let items = doc.sequence_items(resolved).map(<[_]>::to_vec).ok_or_else(|| {
285 err!(InvalidArgument, "inline array data is not nested {} deep", shape.len())
286 })?;
287 if items.len() as u64 != *dim {
288 return Err(err!(
289 InvalidArgument,
290 "inline dimension holds {} entries but the shape calls for {dim}",
291 items.len()
292 ));
293 }
294 for item in items {
295 collect_inline(doc, item, datatype, rest, out)?;
296 }
297 Ok(())
298}
299
300fn leaf_element(doc: &Document, node: NodeId, datatype: &Datatype) -> Result<Element> {
302 if !datatype.fields.is_empty() {
303 let items = doc.sequence_items(node).map(<[_]>::to_vec).ok_or_else(|| {
304 err!(InvalidArgument, "a compound element must be a sequence of its fields")
305 })?;
306 if items.len() != datatype.fields.len() {
307 return Err(err!(
308 InvalidArgument,
309 "a compound element holds {} values but the datatype has {} fields",
310 items.len(),
311 datatype.fields.len()
312 ));
313 }
314 let mut record = Vec::with_capacity(items.len());
315 for (item, field) in items.iter().zip(datatype.fields.iter()) {
316 record.push(leaf_element(doc, doc.resolve(*item), &field.datatype)?);
317 }
318 return Ok(Element::Record(record));
319 }
320
321 let text = doc
322 .resolved(node)
323 .as_str()
324 .ok_or_else(|| err!(InvalidArgument, "inline array data holds a non-scalar leaf"))?;
325 scalar_element(text, datatype.scalar)
326}
327
328fn scalar_element(text: &str, scalar: ScalarType) -> Result<Element> {
330 use ScalarType as S;
331
332 if matches!(text, "null" | "~" | "") {
335 return Ok(match scalar {
336 S::Float16 | S::Float32 | S::Float64 => Element::Float(0.0),
337 S::Complex64 | S::Complex128 => Element::Complex(0.0, 0.0),
338 S::Bool8 => Element::Bool(false),
339 S::Ascii | S::Ucs4 => Element::Text(String::new()),
340 S::Uint8 | S::Uint16 | S::Uint32 | S::Uint64 => Element::Uint(0),
341 _ => Element::Int(0),
342 });
343 }
344
345 let bad = |what: &str| err!(InvalidArgument, "inline {what} value {text:?} does not parse");
346 match scalar {
347 S::Uint8 | S::Uint16 | S::Uint32 | S::Uint64 => {
348 Ok(Element::Uint(text.parse::<u64>().map_err(|_| bad("unsigned"))?))
349 }
350 S::Int8 | S::Int16 | S::Int32 | S::Int64 => {
351 Ok(Element::Int(text.parse::<i64>().map_err(|_| bad("integer"))?))
352 }
353 S::Float16 | S::Float32 | S::Float64 => Ok(Element::Float(parse_inline_float(text)?)),
354 S::Complex64 | S::Complex128 => {
355 let (re, im) = parse_inline_complex(text)?;
356 Ok(Element::Complex(re, im))
357 }
358 S::Bool8 => Ok(Element::Bool(matches!(text, "true" | "True" | "1"))),
359 S::Ascii | S::Ucs4 => Ok(Element::Text(text.to_string())),
360 S::Unknown | S::Structured => {
361 Err(err!(InvalidArgument, "inline data needs a known scalar datatype"))
362 }
363 }
364}
365
366fn parse_inline_float(text: &str) -> Result<f64> {
368 match text {
369 ".nan" | ".NaN" | ".NAN" | "nan" => return Ok(f64::NAN),
370 ".inf" | ".Inf" | ".INF" | "inf" => return Ok(f64::INFINITY),
371 "-.inf" | "-.Inf" | "-.INF" | "-inf" => return Ok(f64::NEG_INFINITY),
372 _ => {}
373 }
374 text.parse::<f64>()
375 .map_err(|_| err!(InvalidArgument, "inline float value {text:?} does not parse"))
376}
377
378fn parse_inline_complex(text: &str) -> Result<(f64, f64)> {
383 let body = text.trim();
384 let body = body.strip_prefix('(').map_or(body, |rest| rest.strip_suffix(')').unwrap_or(rest));
385
386 let imaginary_unit = |c: char| matches!(c, 'i' | 'I' | 'j' | 'J');
387 let Some(unit) = body.char_indices().rev().find(|(_, c)| imaginary_unit(*c)) else {
388 return Ok((parse_inline_float(body)?, 0.0));
390 };
391 if unit.0 + unit.1.len_utf8() != body.len() {
393 return Err(err!(InvalidArgument, "inline complex value {text:?} does not parse"));
394 }
395 let without_unit = &body[..unit.0];
396
397 let split = without_unit
400 .char_indices()
401 .rev()
402 .find(|(index, c)| {
403 (*c == '+' || *c == '-')
404 && *index > 0
405 && !matches!(without_unit.as_bytes()[index - 1], b'e' | b'E')
406 })
407 .map(|(index, _)| index);
408
409 match split {
410 None => Ok((0.0, parse_inline_float(without_unit)?)),
411 Some(index) => {
412 let (real, imaginary) = without_unit.split_at(index);
413 let imaginary = match imaginary {
415 "+" => "1",
416 "-" => "-1",
417 other => other,
418 };
419 Ok((parse_inline_float(real)?, parse_inline_float(imaginary)?))
420 }
421 }
422}
423
424const COMPLEX_TAG: &str = "tag:stsci.edu:asdf/core/complex-1.0.0";
426
427pub fn format_float(value: f64) -> String {
431 if value.is_nan() {
432 return ".nan".to_string();
433 }
434 if value.is_infinite() {
435 return if value.is_sign_negative() { "-.inf".into() } else { ".inf".into() };
436 }
437 let mut s = format!("{value}");
440 if !s.contains('.') && !s.contains('e') && !s.contains("inf") && !s.contains("nan") {
441 s.push_str(".0");
442 }
443 s
444}
445
446fn element_to_node(doc: &mut Document, element: &Element) -> NodeId {
448 match element {
449 Element::Int(v) => doc.add_scalar(v.to_string()),
450 Element::Uint(v) => doc.add_scalar(v.to_string()),
451 Element::Bool(v) => doc.add_scalar(if *v { "true" } else { "false" }),
452 Element::Float(v) => doc.add_scalar(format_float(*v)),
453 Element::Text(s) => doc.add_scalar_styled(s.clone(), ScalarStyle::SingleQuoted),
456 Element::Complex(re, im) => {
457 let node = Node::scalar(crate::core::pyrepr::repr_complex(*re, *im))
461 .with_tag(asdf_yaml::Tag::parse(COMPLEX_TAG));
462 doc.add(node)
463 }
464 Element::Record(fields) => {
465 let items: Vec<NodeId> = fields.iter().map(|f| element_to_node(doc, f)).collect();
466 doc.add_sequence(items)
467 }
468 }
469}
470
471pub fn nest(doc: &mut Document, elements: &[Element], shape: &[u64]) -> NodeId {
473 fn build(
474 doc: &mut Document,
475 elements: &[Element],
476 shape: &[u64],
477 cursor: &mut usize,
478 ) -> NodeId {
479 match shape.split_first() {
480 None => {
481 let node = element_to_node(doc, &elements[*cursor]);
482 *cursor += 1;
483 node
484 }
485 Some((dim, rest)) => {
486 let mut items = Vec::with_capacity(*dim as usize);
487 for _ in 0..*dim {
488 items.push(build(doc, elements, rest, cursor));
489 }
490 let id = doc.add_sequence(items);
491 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(id).data {
494 *style = CollectionStyle::Flow;
495 }
496 id
497 }
498 }
499 }
500
501 let mut cursor = 0;
502 build(doc, elements, shape, &mut cursor)
503}
504
505pub fn inline_ndarray(
511 doc: &mut Document,
512 id: NodeId,
513 elements: &[Element],
514 shape: &[u64],
515) -> Result<()> {
516 let data = nest(doc, elements, shape);
517 let target = doc.resolve(id);
518
519 if !doc.node(target).is_mapping() {
520 return Ok(());
522 }
523
524 doc.mapping_remove(target, "source");
525 for key in ["byteorder", "offset", "strides"] {
526 doc.mapping_remove(target, key);
527 }
528 if let Some(dt) = doc.mapping_get(target, "datatype")
531 && let Some(fields) = doc.sequence_items(dt).map(<[_]>::to_vec)
532 {
533 for field in fields {
534 let field = doc.resolve(field);
535 if doc.node(field).is_mapping() {
536 doc.mapping_remove(field, "byteorder");
537 }
538 }
539 }
540 doc.mapping_set(target, "data", data);
541
542 let dims: Vec<NodeId> = shape.iter().map(|d| doc.add_scalar(d.to_string())).collect();
544 let shape_node = doc.add_sequence(dims);
545 if let NodeData::Sequence { style, .. } = &mut doc.node_mut(shape_node).data {
546 *style = CollectionStyle::Flow;
547 }
548 doc.mapping_set(target, "shape", shape_node);
549 Ok(())
550}
551
552pub fn element_node(doc: &mut Document, element: &Element) -> NodeId {
554 element_to_node(doc, element)
555}
556
557pub fn tagged(doc: &mut Document, node: Node, tag: asdf_yaml::Tag) -> NodeId {
559 doc.add(node.with_tag(tag))
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565 use asdf_yaml::parse_document;
566
567 fn ndarray(yaml: &str) -> Ndarray {
568 let doc = parse_document(yaml).unwrap();
569 let root = doc.root().unwrap();
570 Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap()
571 }
572
573 #[test]
574 fn inline_integers_decode_from_the_tree() {
575 let doc = parse_document(
576 "a:\n data: [[1, 2, 3], [4, 5, 6]]\n datatype: int32\n shape: [2, 3]\n",
577 )
578 .unwrap();
579 let root = doc.root().unwrap();
580 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
581 let shape = nd.resolved_shape(None).unwrap();
582 assert_eq!(shape, vec![2, 3]);
583
584 let els = decode_inline(&doc, &nd, &shape).unwrap();
585 assert_eq!(
586 els,
587 (1..=6).map(Element::Int).collect::<Vec<_>>(),
588 "row-major order, flattened"
589 );
590 }
591
592 #[test]
593 fn inline_floats_accept_yamls_non_finite_spellings() {
594 let doc = parse_document(
595 "a:\n data: [1.5, .inf, -.inf, .nan]\n datatype: float64\n shape: [4]\n",
596 )
597 .unwrap();
598 let root = doc.root().unwrap();
599 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
600 let els = decode_inline(&doc, &nd, &[4]).unwrap();
601
602 assert_eq!(els[0], Element::Float(1.5));
603 assert_eq!(els[1], Element::Float(f64::INFINITY));
604 assert_eq!(els[2], Element::Float(f64::NEG_INFINITY));
605 let Element::Float(nan) = els[3] else { panic!("{:?}", els[3]) };
606 assert!(nan.is_nan());
607 }
608
609 #[test]
611 fn inline_complex_accepts_every_spelling_the_schema_allows() {
612 let cases = [
613 ("0j", (0.0, 0.0)),
614 ("(1+2j)", (1.0, 2.0)),
615 ("1+2j", (1.0, 2.0)),
616 ("(1-2j)", (1.0, -2.0)),
617 ("-1j", (0.0, -1.0)),
618 ("(-0+0j)", (-0.0, 0.0)),
619 ("3", (3.0, 0.0)),
620 ("2i", (0.0, 2.0)),
621 ("(1.5e-3+2.5e+4j)", (1.5e-3, 2.5e4)),
622 ("(1+j)", (1.0, 1.0)),
624 ("(1-j)", (1.0, -1.0)),
625 ];
626 for (text, (re, im)) in cases {
627 let got = parse_inline_complex(text).unwrap_or_else(|e| panic!("{text}: {e}"));
628 assert_eq!(got.0, re, "real part of {text}");
629 assert_eq!(got.1, im, "imaginary part of {text}");
630 }
631
632 let (re, im) = parse_inline_complex("(nan-infj)").unwrap();
634 assert!(re.is_nan());
635 assert_eq!(im, f64::NEG_INFINITY);
636 }
637
638 #[test]
641 fn complex_spellings_round_trip_through_the_parser() {
642 let values = [
643 (0.0, 0.0),
644 (-0.0, 0.0),
645 (1.0, 2.0),
646 (1.0, -2.0),
647 (0.0, -1.0),
648 (1.5e-3, 2.5e4),
649 (f64::MAX, f64::MIN_POSITIVE),
650 ];
651 for (re, im) in values {
652 let text = crate::core::pyrepr::repr_complex(re, im);
653 let (back_re, back_im) = parse_inline_complex(&text).unwrap();
654 assert_eq!(back_re.to_bits(), re.to_bits(), "{text}");
655 assert_eq!(back_im.to_bits(), im.to_bits(), "{text}");
656 }
657 }
658
659 #[test]
660 fn inline_compound_records_stay_grouped() {
661 let doc = parse_document(
662 "a:\n data: [[1, 2.5], [3, 4.5]]\n shape: [2]\n datatype:\n \
663 - {name: n, datatype: int32}\n - {name: x, datatype: float64}\n",
664 )
665 .unwrap();
666 let root = doc.root().unwrap();
667 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
668 let els = decode_inline(&doc, &nd, &[2]).unwrap();
669 assert_eq!(
670 els,
671 vec![
672 Element::Record(vec![Element::Int(1), Element::Float(2.5)]),
673 Element::Record(vec![Element::Int(3), Element::Float(4.5)]),
674 ]
675 );
676 }
677
678 #[test]
679 fn inline_data_must_match_the_declared_shape() {
680 let doc =
681 parse_document("a:\n data: [1, 2, 3]\n datatype: int32\n shape: [4]\n").unwrap();
682 let root = doc.root().unwrap();
683 let nd = Ndarray::parse(&doc, doc.mapping_get(root, "a").unwrap()).unwrap();
684 let err = decode_inline(&doc, &nd, &[4]).unwrap_err();
685 assert!(err.message().contains("shape calls for 4"), "{err}");
686 }
687
688 #[test]
691 fn a_block_array_survives_a_trip_through_inline_form() {
692 let nd =
693 ndarray("a:\n source: 0\n shape: [5]\n datatype: float64\n byteorder: little\n");
694 let values = [1.5f64, -2.25, 0.0, f64::MAX, -0.125];
695 let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();
696 let original = decode_all(&nd, &[5], &bytes).unwrap();
697
698 let mut doc = parse_document(
699 "a:\n source: 0\n shape: [5]\n datatype: float64\n byteorder: little\n",
700 )
701 .unwrap();
702 let root = doc.root().unwrap();
703 let node = doc.mapping_get(root, "a").unwrap();
704 inline_ndarray(&mut doc, node, &original, &[5]).unwrap();
705
706 let inlined = Ndarray::parse(&doc, node).unwrap();
707 let read_back = decode_inline(&doc, &inlined, &[5]).unwrap();
708 assert_eq!(read_back, original);
709 }
710
711 #[test]
712 fn decodes_little_endian_integers() {
713 let nd = ndarray("a:\n source: 0\n shape: [4]\n datatype: int32\n byteorder: little\n");
714 let mut bytes = Vec::new();
715 for v in [1i32, -1, 256, i32::MIN] {
716 bytes.extend_from_slice(&v.to_le_bytes());
717 }
718 let els = decode_all(&nd, &[4], &bytes).unwrap();
719 assert_eq!(
720 els,
721 vec![
722 Element::Int(1),
723 Element::Int(-1),
724 Element::Int(256),
725 Element::Int(i64::from(i32::MIN)),
726 ]
727 );
728 }
729
730 #[test]
731 fn decodes_big_endian_integers() {
732 let nd = ndarray("a:\n source: 0\n shape: [3]\n datatype: int16\n byteorder: big\n");
733 let mut bytes = Vec::new();
734 for v in [1i16, -2, 1000] {
735 bytes.extend_from_slice(&v.to_be_bytes());
736 }
737 let els = decode_all(&nd, &[3], &bytes).unwrap();
738 assert_eq!(els, vec![Element::Int(1), Element::Int(-2), Element::Int(1000)]);
739 }
740
741 #[test]
742 fn byte_order_actually_changes_the_value() {
743 let bytes = [0x01u8, 0x00];
744 let le =
745 ndarray("a:\n source: 0\n shape: [1]\n datatype: uint16\n byteorder: little\n");
746 let be = ndarray("a:\n source: 0\n shape: [1]\n datatype: uint16\n byteorder: big\n");
747 assert_eq!(decode_all(&le, &[1], &bytes).unwrap(), vec![Element::Uint(1)]);
748 assert_eq!(decode_all(&be, &[1], &bytes).unwrap(), vec![Element::Uint(256)]);
749 }
750
751 #[test]
752 fn decodes_floats_of_every_width() {
753 let nd =
754 ndarray("a:\n source: 0\n shape: [2]\n datatype: float64\n byteorder: little\n");
755 let mut bytes = Vec::new();
756 bytes.extend_from_slice(&1.5f64.to_le_bytes());
757 bytes.extend_from_slice(&(-0.25f64).to_le_bytes());
758 assert_eq!(
759 decode_all(&nd, &[2], &bytes).unwrap(),
760 vec![Element::Float(1.5), Element::Float(-0.25)]
761 );
762
763 let nd =
764 ndarray("a:\n source: 0\n shape: [1]\n datatype: float32\n byteorder: little\n");
765 assert_eq!(
766 decode_all(&nd, &[1], &2.5f32.to_le_bytes()).unwrap(),
767 vec![Element::Float(2.5)]
768 );
769
770 let nd =
771 ndarray("a:\n source: 0\n shape: [1]\n datatype: float16\n byteorder: little\n");
772 let h = half::f16::from_f32(0.5);
773 assert_eq!(
774 decode_all(&nd, &[1], &h.to_bits().to_le_bytes()).unwrap(),
775 vec![Element::Float(0.5)]
776 );
777 }
778
779 #[test]
780 fn decodes_bools_and_text() {
781 let nd = ndarray("a:\n source: 0\n shape: [2]\n datatype: bool8\n byteorder: little\n");
782 assert_eq!(
783 decode_all(&nd, &[2], &[0u8, 1]).unwrap(),
784 vec![Element::Bool(false), Element::Bool(true)]
785 );
786
787 let nd = ndarray(
789 "a:\n source: 0\n shape: [2]\n datatype: ['ascii', 4]\n byteorder: little\n",
790 );
791 let bytes = b"M31\0Cas\0";
792 assert_eq!(
793 decode_all(&nd, &[2], bytes).unwrap(),
794 vec![Element::Text("M31".into()), Element::Text("Cas".into())]
795 );
796 }
797
798 #[test]
799 fn decodes_ucs4_text() {
800 let nd = ndarray(
801 "a:\n source: 0\n shape: [1]\n datatype: ['ucs4', 3]\n byteorder: little\n",
802 );
803 let mut bytes = Vec::new();
804 for cp in ['a' as u32, 0x00E9 , 0] {
805 bytes.extend_from_slice(&cp.to_le_bytes());
806 }
807 assert_eq!(decode_all(&nd, &[1], &bytes).unwrap(), vec![Element::Text("aé".into())]);
808 }
809
810 #[test]
811 fn honours_offset() {
812 let nd = ndarray(
813 "a:\n source: 0\n shape: [2]\n datatype: uint8\n byteorder: little\n offset: 3\n",
814 );
815 let bytes = [9u8, 9, 9, 1, 2];
816 assert_eq!(
817 decode_all(&nd, &[2], &bytes).unwrap(),
818 vec![Element::Uint(1), Element::Uint(2)]
819 );
820 }
821
822 #[test]
823 fn honours_strides_for_a_fortran_order_array() {
824 let nd = ndarray(
826 "a:\n source: 0\n shape: [2, 3]\n datatype: uint8\n byteorder: little\n \
827 strides: [1, 2]\n",
828 );
829 let bytes = [1u8, 4, 2, 5, 3, 6];
831 let els = decode_all(&nd, &[2, 3], &bytes).unwrap();
832 let values: Vec<u64> = els
833 .iter()
834 .map(|e| match e {
835 Element::Uint(v) => *v,
836 _ => unreachable!(),
837 })
838 .collect();
839 assert_eq!(values, vec![1, 2, 3, 4, 5, 6]);
841 }
842
843 #[test]
844 fn honours_strides_for_a_tile_view() {
845 let nd = ndarray(
847 "a:\n source: 0\n shape: [2, 2]\n datatype: uint8\n byteorder: little\n \
848 strides: [4, 1]\n offset: 5\n",
849 );
850 let bytes: Vec<u8> = (0..16).collect();
851 let els = decode_all(&nd, &[2, 2], &bytes).unwrap();
852 let values: Vec<u64> = els
853 .iter()
854 .map(|e| match e {
855 Element::Uint(v) => *v,
856 _ => unreachable!(),
857 })
858 .collect();
859 assert_eq!(values, vec![5, 6, 9, 10]);
860 }
861
862 #[test]
863 fn decodes_compound_records() {
864 let nd = ndarray(
865 "a:\n source: 0\n shape: [2]\n byteorder: little\n \
866 datatype:\n - name: id\n datatype: uint16\n \
867 - name: value\n datatype: float32\n",
868 );
869 let mut bytes = Vec::new();
870 for (id, value) in [(1u16, 1.5f32), (2, -2.5)] {
871 bytes.extend_from_slice(&id.to_le_bytes());
872 bytes.extend_from_slice(&value.to_le_bytes());
873 }
874 let els = decode_all(&nd, &[2], &bytes).unwrap();
875 assert_eq!(
876 els,
877 vec![
878 Element::Record(vec![Element::Uint(1), Element::Float(1.5)]),
879 Element::Record(vec![Element::Uint(2), Element::Float(-2.5)]),
880 ]
881 );
882 }
883
884 #[test]
885 fn truncated_data_is_an_error_not_a_panic() {
886 let nd = ndarray("a:\n source: 0\n shape: [4]\n datatype: int64\n byteorder: little\n");
887 assert!(decode_all(&nd, &[4], &[0u8; 8]).is_err());
888 }
889
890 #[test]
891 fn nesting_reproduces_the_shape() {
892 let mut doc = Document::new();
893 let els: Vec<Element> = (0..6).map(Element::Uint).collect();
894 let node = nest(&mut doc, &els, &[2, 3]);
895 doc.set_root(node);
896
897 assert_eq!(doc.container_len(node), Some(2));
898 let first = doc.sequence_get(node, 0).unwrap();
899 assert_eq!(doc.container_len(first), Some(3));
900 assert_eq!(doc.resolved(doc.sequence_get(first, 2).unwrap()).as_str(), Some("2"));
901 }
902
903 #[test]
904 fn float_formatting_uses_yaml_spellings() {
905 assert_eq!(format_float(f64::NAN), ".nan");
906 assert_eq!(format_float(f64::INFINITY), ".inf");
907 assert_eq!(format_float(f64::NEG_INFINITY), "-.inf");
908 assert_eq!(format_float(1.0), "1.0");
910 assert_eq!(format_float(1.5), "1.5");
911 }
912
913 #[test]
914 fn inlining_replaces_source_with_data() {
915 let mut doc = parse_document(
916 "a:\n source: 0\n shape: [4]\n datatype: uint8\n byteorder: little\n offset: 0\n",
917 )
918 .unwrap();
919 let root = doc.root().unwrap();
920 let nd_id = doc.mapping_get(root, "a").unwrap();
921
922 let els: Vec<Element> = (0..4).map(Element::Uint).collect();
923 inline_ndarray(&mut doc, nd_id, &els, &[4]).unwrap();
924
925 assert!(doc.mapping_get(nd_id, "source").is_none(), "source must be removed");
926 assert!(doc.mapping_get(nd_id, "byteorder").is_none(), "byteorder is meaningless inline");
927 assert!(doc.mapping_get(nd_id, "offset").is_none(), "offset is meaningless inline");
928
929 let data = doc.mapping_get(nd_id, "data").expect("data must be added");
930 assert_eq!(doc.container_len(data), Some(4));
931 assert!(doc.mapping_get(nd_id, "datatype").is_some());
933 }
934}