Skip to main content

saturnus_objects/
object.rs

1//! Typed calculator objects (the JSON model of saturnus's tools) and the
2//! exact decoder for objects in the calculator's nibble format, whether
3//! from a binary transfer or read straight from memory.
4//!
5//! Body layouts: wiki: protocols/hp-object-format ("Object bodies"), from
6//! RPLMAN chapter 3 and objects observed on the 48SX and 49G.
7//!
8//! - Reals keep their exact 12-digit mantissa ([`Real`]); JSON shows them as
9//!   numbers (12 significant digits survive an `f64`) unless the exponent
10//!   is beyond an `f64`'s range, then as text such as `"1.5E-400"`.
11//! - Built-in constants inside composites are 5-nibble ROM pointers; a
12//!   [`Memory`] reads the object they point to (the emulator's ROM). A
13//!   pointer to a command (a program, code or a primitive in ROM) is not
14//!   followed: it becomes [`Object::Command`] with its address, and with
15//!   its name when the reader has the ROM's [`NameTable`].
16//! - With a [`NameTable`], programs, algebraics and unit expressions get
17//!   the calculator's text ([`crate::decompile`]); without one their
18//!   `source` stays unset and a host may fill it from the calculator's own
19//!   text (an ASCII transfer: [`crate::transfer::fill_sources`]).
20
21use std::cell::{Cell, RefCell};
22use std::collections::HashMap;
23use std::fmt;
24
25use anyhow::{Context, Result, bail};
26use serde::de::{self, Deserializer, Visitor};
27use serde::ser::Serializer;
28use serde::{Deserialize, Serialize};
29
30use crate::charset;
31use crate::decompile::{self, Element, Settings, UnitOp};
32use crate::names::NameTable;
33use crate::prolog::{MAX_DEPTH, ObjectType, SEMI, object_size, read_field};
34
35/// Longest hex dump of an unknown object, in nibbles.
36const MAX_HEX_NIBBLES: usize = 4096;
37/// Largest object read from memory, in nibbles: the whole address space.
38const MAX_MEMORY_OBJECT: usize = 1 << 20;
39/// Most objects one decode may produce. ROM pointers can share a target,
40/// so without a bound a few hundred nibbles of crafted or corrupt memory
41/// (lists of two pointers to the next such list) expand to 2^64 objects.
42/// The largest object a calculator holds is under 512 K nibbles (the
43/// 49G's 256 KB of RAM; a 48GX card port holds at most 128 KB), so it has
44/// at most about 105 000 elements (5-nibble ROM pointers, the smallest);
45/// 2^18 is two and a half times that. The work to reach the bound grows
46/// with it (every object is built before the next is charged): 2^18 keeps
47/// a refused decode near 50 ms in a debug build.
48pub const MAX_DECODED_OBJECTS: usize = 1 << 18;
49/// Most nibbles one decode may read from memory, each object once: four
50/// times the 49G's whole RAM (512 K nibbles), so nothing a calculator
51/// holds is refused.
52pub const MAX_DECODED_NIBBLES: usize = 1 << 21;
53/// Most nibbles one decode may hand out again for pointers that repeat an
54/// object already read: DUP copies the pointer, not the object, so a real
55/// stack can hold one large object at many levels, and each level is a
56/// copy in the result. 16 M nibbles (8 MB of object data) covers forty
57/// copies of a 400 K-nibble GROB; past it a crafted stack of thousands of
58/// pointers to one large object is refused instead of producing gigabytes.
59pub const MAX_CLONED_NIBBLES: usize = 1 << 24;
60/// Most dimensions of an array: the calculator's real and complex arrays
61/// are vectors or matrices (RPLMAN chapter 3). More is refused, so no
62/// object's header sets how deep the rows nest.
63const MAX_ARRAY_DIMS: usize = 2;
64/// Exponents an `f64` carries without overflow or loss of digits.
65const F64_EXPONENTS: std::ops::RangeInclusive<i32> = -307..=307;
66
67/// A real number exactly as the calculator stores it: sign, 12 decimal
68/// digits with the point after the first, exponent -499..=499.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct Real {
71    /// Negative.
72    pub negative: bool,
73    /// Mantissa digits, most significant first; the first is nonzero unless
74    /// the number is 0.
75    pub digits: [u8; 12],
76    /// Decimal exponent.
77    pub exponent: i32,
78}
79
80impl Real {
81    /// Zero.
82    pub const ZERO: Real = Real {
83        negative: false,
84        digits: [0; 12],
85        exponent: 0,
86    };
87
88    /// Decode a 16-nibble real body: 3-nibble tens-complement exponent, 12
89    /// mantissa digits from the least significant up, sign (0 or 9).
90    pub fn from_body(body: &[u8]) -> Result<Real> {
91        let body = body
92            .get(..16)
93            .context("real body shorter than 16 nibbles")?;
94        if body.iter().any(|&d| d > 9) {
95            bail!("real body is not BCD: {}", hex(body));
96        }
97        let e = i32::from(body[0]) + 10 * i32::from(body[1]) + 100 * i32::from(body[2]);
98        let exponent = if e >= 500 { e - 1000 } else { e };
99        let mut digits = [0u8; 12];
100        for (i, d) in digits.iter_mut().enumerate() {
101            *d = body[14 - i];
102        }
103        let negative = match body[15] {
104            0 => false,
105            9 => true,
106            s => bail!("real sign nibble {s} is neither 0 nor 9"),
107        };
108        Ok(Real {
109            negative,
110            digits,
111            exponent,
112        })
113    }
114
115    /// The 16-nibble body.
116    pub fn to_body(&self) -> [u8; 16] {
117        let mut body = [0u8; 16];
118        let e = u32::try_from(self.exponent.rem_euclid(1000)).unwrap_or(0);
119        body[0] = (e % 10) as u8;
120        body[1] = (e / 10 % 10) as u8;
121        body[2] = (e / 100) as u8;
122        for (i, d) in self.digits.iter().enumerate() {
123            body[14 - i] = *d;
124        }
125        body[15] = if self.negative { 9 } else { 0 };
126        body
127    }
128
129    fn is_zero(&self) -> bool {
130        self.digits.iter().all(|&d| d == 0)
131    }
132
133    /// The nearest `f64` (exact to 12 digits within `F64_EXPONENTS`).
134    pub fn to_f64(&self) -> f64 {
135        format!("{}e{}", self.mantissa_text(), self.exponent)
136            .parse()
137            .unwrap_or(f64::NAN)
138    }
139
140    /// `d.ddd` with trailing zeros dropped (`5.`, `4.79425538604`), signed.
141    fn mantissa_text(&self) -> String {
142        let mut s = String::new();
143        if self.negative {
144            s.push('-');
145        }
146        s.push(char::from(b'0' + self.digits[0]));
147        s.push('.');
148        let last = self.digits.iter().rposition(|&d| d != 0).unwrap_or(0);
149        for d in &self.digits[1..=last] {
150            s.push(char::from(b'0' + d));
151        }
152        s
153    }
154
155    /// RPL source that compiles to exactly this real: `5.`, `-1.5E-300`,
156    /// `4.79425538604E-1`.
157    pub fn to_source(&self) -> String {
158        if self.exponent == 0 {
159            self.mantissa_text()
160        } else {
161            format!("{}E{}", self.mantissa_text(), self.exponent)
162        }
163    }
164
165    /// Parse decimal text (`0.5`, `-1.5E-300`, `12`, `.479425538604`),
166    /// rounding to 12 significant digits (half away from zero).
167    pub fn parse(text: &str) -> Result<Real> {
168        let bad = || anyhow::anyhow!("not a real number: {text:?}");
169        let t = text.trim();
170        let (negative, t) = match t.strip_prefix('-') {
171            Some(rest) => (true, rest),
172            None => (false, t.strip_prefix('+').unwrap_or(t)),
173        };
174        let (mant, exp) = match t.find(['e', 'E']) {
175            Some(i) => (&t[..i], t[i + 1..].parse::<i32>().map_err(|_| bad())?),
176            None => (t, 0),
177        };
178        let (int, frac) = mant.split_once('.').unwrap_or((mant, ""));
179        if (int.is_empty() && frac.is_empty())
180            || !int.bytes().chain(frac.bytes()).all(|b| b.is_ascii_digit())
181        {
182            return Err(bad());
183        }
184        let all: Vec<u8> = int.bytes().chain(frac.bytes()).map(|b| b - b'0').collect();
185        let Some(first) = all.iter().position(|&d| d != 0) else {
186            return Ok(Real::ZERO);
187        };
188        // Value = 0.ALL * 10^(int.len() + exp); the first nonzero digit at
189        // index `first` has weight 10^(int.len() - 1 - first + exp).
190        let int_len = i32::try_from(int.len()).map_err(|_| bad())?;
191        let first_i = i32::try_from(first).map_err(|_| bad())?;
192        let mut exponent = int_len - 1 - first_i + exp;
193        let sig = &all[first..];
194        let mut digits = [0u8; 12];
195        for (d, s) in digits.iter_mut().zip(sig) {
196            *d = *s;
197        }
198        if sig.get(12).is_some_and(|&d| d >= 5) {
199            // Round up, carrying.
200            let mut i = 12;
201            loop {
202                if i == 0 {
203                    digits = [0; 12];
204                    digits[0] = 1;
205                    exponent += 1;
206                    break;
207                }
208                i -= 1;
209                if digits[i] == 9 {
210                    digits[i] = 0;
211                } else {
212                    digits[i] += 1;
213                    break;
214                }
215            }
216        }
217        if !(-499..=499).contains(&exponent) {
218            bail!("{text:?} is outside the calculator's range (exponents -499 to 499)");
219        }
220        Ok(Real {
221            negative,
222            digits,
223            exponent,
224        })
225    }
226
227    /// The real nearest to `v`, rounded to 12 digits.
228    pub fn from_f64(v: f64) -> Result<Real> {
229        if !v.is_finite() {
230            bail!("{v} is not a finite number");
231        }
232        Real::parse(&format!("{v:e}"))
233    }
234}
235
236impl fmt::Display for Real {
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        f.write_str(&self.to_source())
239    }
240}
241
242impl Serialize for Real {
243    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
244        if self.is_zero() || F64_EXPONENTS.contains(&self.exponent) {
245            s.serialize_f64(self.to_f64())
246        } else {
247            s.serialize_str(&self.to_source())
248        }
249    }
250}
251
252impl<'de> Deserialize<'de> for Real {
253    fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Real, D::Error> {
254        struct V;
255        impl Visitor<'_> for V {
256            type Value = Real;
257            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258                f.write_str("a number or decimal text such as \"1.5E-400\"")
259            }
260            fn visit_f64<E: de::Error>(self, v: f64) -> std::result::Result<Real, E> {
261                Real::from_f64(v).map_err(E::custom)
262            }
263            fn visit_i64<E: de::Error>(self, v: i64) -> std::result::Result<Real, E> {
264                Real::parse(&v.to_string()).map_err(E::custom)
265            }
266            fn visit_u64<E: de::Error>(self, v: u64) -> std::result::Result<Real, E> {
267                Real::parse(&v.to_string()).map_err(E::custom)
268            }
269            fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<Real, E> {
270                Real::parse(v).map_err(E::custom)
271            }
272        }
273        d.deserialize_any(V)
274    }
275}
276
277/// A 49G exact integer: sign and decimal digits, any length.
278#[derive(Clone, Debug, PartialEq, Eq)]
279pub struct Integer {
280    /// Negative (never for zero).
281    pub negative: bool,
282    /// Decimal digits, most significant first, no leading zeros; `0` for
283    /// zero.
284    pub digits: String,
285}
286
287impl Integer {
288    /// Parse `-123`, `456`.
289    pub fn parse(text: &str) -> Result<Integer> {
290        let t = text.trim();
291        let (negative, d) = match t.strip_prefix('-') {
292            Some(rest) => (true, rest),
293            None => (false, t.strip_prefix('+').unwrap_or(t)),
294        };
295        if d.is_empty() || !d.bytes().all(|b| b.is_ascii_digit()) {
296            bail!("not an integer: {text:?}");
297        }
298        let digits = d.trim_start_matches('0');
299        let digits = if digits.is_empty() { "0" } else { digits };
300        Ok(Integer {
301            negative: negative && digits != "0",
302            digits: digits.to_string(),
303        })
304    }
305
306    /// Decode the nibbles after the length field: digits from the least
307    /// significant up, then the sign nibble (0 or 9). Zero is one 0 nibble.
308    pub fn from_nibbles(n: &[u8]) -> Result<Integer> {
309        let Some((&sign, digits)) = n.split_last() else {
310            bail!("empty integer");
311        };
312        if n.iter().any(|&d| d > 9) {
313            bail!("integer body is not BCD: {}", hex(n));
314        }
315        let text: String = digits.iter().rev().map(|d| char::from(b'0' + d)).collect();
316        let mut i = Integer::parse(if text.is_empty() { "0" } else { &text })?;
317        i.negative = sign == 9 && i.digits != "0";
318        Ok(i)
319    }
320
321    /// The nibbles after the length field (see [`Integer::from_nibbles`]).
322    pub fn to_nibbles(&self) -> Vec<u8> {
323        if self.digits == "0" {
324            return vec![0];
325        }
326        let mut n: Vec<u8> = self.digits.bytes().rev().map(|b| b - b'0').collect();
327        n.push(if self.negative { 9 } else { 0 });
328        n
329    }
330
331    /// RPL source: `-123`.
332    pub fn to_source(&self) -> String {
333        if self.negative {
334            format!("-{}", self.digits)
335        } else {
336            self.digits.clone()
337        }
338    }
339}
340
341impl Serialize for Integer {
342    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
343        // Up to 15 digits fit an f64 exactly, so every JSON reader agrees.
344        match self.to_source().parse::<i64>() {
345            Ok(v) if self.digits.len() <= 15 => s.serialize_i64(v),
346            _ => s.serialize_str(&self.to_source()),
347        }
348    }
349}
350
351impl<'de> Deserialize<'de> for Integer {
352    fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Integer, D::Error> {
353        struct V;
354        impl Visitor<'_> for V {
355            type Value = Integer;
356            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357                f.write_str("an integer or a string of decimal digits")
358            }
359            fn visit_i64<E: de::Error>(self, v: i64) -> std::result::Result<Integer, E> {
360                Integer::parse(&v.to_string()).map_err(E::custom)
361            }
362            fn visit_u64<E: de::Error>(self, v: u64) -> std::result::Result<Integer, E> {
363                Integer::parse(&v.to_string()).map_err(E::custom)
364            }
365            fn visit_f64<E: de::Error>(self, v: f64) -> std::result::Result<Integer, E> {
366                if v.fract() != 0.0 || !v.is_finite() || v.abs() > 9.0e15 {
367                    return Err(E::custom(format!("{v} is not an exact integer")));
368                }
369                Integer::parse(&format!("{v:.0}")).map_err(E::custom)
370            }
371            fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<Integer, E> {
372                Integer::parse(v).map_err(E::custom)
373            }
374        }
375        d.deserialize_any(V)
376    }
377}
378
379/// The display base of binary integers (flags -11 and -12).
380#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
381#[serde(rename_all = "lowercase")]
382pub enum Base {
383    /// `h`.
384    Hex,
385    /// `d`.
386    Dec,
387    /// `o`.
388    Oct,
389    /// `b`.
390    Bin,
391}
392
393impl Base {
394    /// The base from the suffix of a displayed binary integer (`# 2Ah`).
395    pub fn from_display(text: &str) -> Option<Base> {
396        let t = text.trim();
397        if !t.starts_with('#') {
398            return None;
399        }
400        match t.chars().last()? {
401            'h' => Some(Base::Hex),
402            'd' => Some(Base::Dec),
403            'o' => Some(Base::Oct),
404            'b' => Some(Base::Bin),
405            _ => None,
406        }
407    }
408
409    /// `value` as the calculator shows it, e.g. `# 2Ah`.
410    pub fn format(self, value: u64) -> String {
411        match self {
412            Base::Hex => format!("# {value:X}h"),
413            Base::Dec => format!("# {value}d"),
414            Base::Oct => format!("# {value:o}o"),
415            Base::Bin => format!("# {value:b}b"),
416        }
417    }
418}
419
420/// One array element or a row of them.
421#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
422#[serde(untagged)]
423pub enum ArrayItem {
424    /// A row (one level of a multi-dimensional array).
425    Row(Vec<ArrayItem>),
426    /// An element.
427    Item(Box<Object>),
428}
429
430/// A calculator object as the semantic tools return and accept it.
431#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
432#[serde(tag = "type", rename_all = "snake_case")]
433pub enum Object {
434    /// A real number, exact to its 12 digits.
435    Real {
436        /// The value (a JSON number, or text beyond an f64's exponent range).
437        value: Real,
438    },
439    /// A 49G exact integer.
440    Integer {
441        /// The value (a JSON number up to 15 digits, else decimal text).
442        value: Integer,
443    },
444    /// A complex number.
445    Complex {
446        /// Real part.
447        re: Real,
448        /// Imaginary part.
449        im: Real,
450    },
451    /// A string (HP characters as Unicode).
452    String {
453        /// The text.
454        value: String,
455    },
456    /// A global name.
457    Name {
458        /// The name.
459        value: String,
460    },
461    /// A local (temporary) name.
462    LocalName {
463        /// The name.
464        value: String,
465    },
466    /// A character object.
467    Character {
468        /// The character.
469        value: String,
470    },
471    /// A binary integer (`# 2Ah`).
472    Binary {
473        /// The value (up to 64 bits).
474        value: u64,
475        /// The calculator's display base, when known.
476        #[serde(default, skip_serializing_if = "Option::is_none")]
477        base: Option<Base>,
478        /// The value as the calculator shows it in that base.
479        #[serde(default, skip_serializing_if = "Option::is_none")]
480        text: Option<String>,
481    },
482    /// A list.
483    List {
484        /// The elements.
485        items: Vec<Object>,
486    },
487    /// A tagged object `:tag:object`.
488    Tagged {
489        /// The tag.
490        tag: String,
491        /// The tagged object.
492        object: Box<Object>,
493    },
494    /// A unit object `value_unit`.
495    Unit {
496        /// The number, exact.
497        value: Real,
498        /// The unit expression, e.g. `m/s^2`.
499        #[serde(default, skip_serializing_if = "Option::is_none")]
500        unit: Option<String>,
501    },
502    /// A real or complex array (vector or matrix).
503    Array {
504        /// Dimensions, e.g. `[2, 3]` for 2 rows of 3.
505        dims: Vec<usize>,
506        /// Elements, nested by dimension (rows of elements for a matrix).
507        items: Vec<ArrayItem>,
508    },
509    /// A program.
510    Program {
511        /// Its source, e.g. `« 1 2 + »`.
512        #[serde(default, skip_serializing_if = "Option::is_none")]
513        source: Option<String>,
514    },
515    /// An algebraic expression.
516    Algebraic {
517        /// Its source with quotes, e.g. `'X^2'`.
518        #[serde(default, skip_serializing_if = "Option::is_none")]
519        source: Option<String>,
520    },
521    /// A built-in command (a ROM pointer to a program, code or primitive,
522    /// or an XLIB name).
523    Command {
524        /// Its name from the ROM's tables, e.g. `+`; absent when they have
525        /// none.
526        #[serde(default, skip_serializing_if = "Option::is_none")]
527        name: Option<String>,
528        /// The ROM address it points to (not for XLIB names).
529        #[serde(default, skip_serializing_if = "Option::is_none")]
530        address: Option<u32>,
531        /// Its library number, when known (always for an XLIB name; an
532        /// XLIB name without `name` is a command of a library the ROM's
533        /// tables do not hold, which the calculator shows `XLIB l n`).
534        #[serde(default, skip_serializing_if = "Option::is_none")]
535        library: Option<u16>,
536        /// Its command number in the library, with `library`.
537        #[serde(default, skip_serializing_if = "Option::is_none")]
538        command: Option<u16>,
539        /// Its text from an ASCII transfer, when known.
540        #[serde(default, skip_serializing_if = "Option::is_none")]
541        source: Option<String>,
542    },
543    /// Any other object: its prolog, type name and nibbles.
544    Unknown {
545        /// Prolog address, e.g. `02B1E`.
546        prolog: String,
547        /// Type name, e.g. `Graphic`, when the prolog is known.
548        #[serde(default, skip_serializing_if = "Option::is_none")]
549        kind: Option<String>,
550        /// Size in nibbles.
551        nibbles: usize,
552        /// The object's nibbles in memory order (prolog first), at most 4096.
553        hex: String,
554        /// Whether `hex` was cut.
555        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
556        truncated: bool,
557        /// The calculator's text for it, when known.
558        #[serde(default, skip_serializing_if = "Option::is_none")]
559        source: Option<String>,
560        /// A graphic's picture ([`Graphic`]), for a GROB of a sane size.
561        #[serde(default, skip_serializing_if = "Option::is_none")]
562        graphic: Option<Graphic>,
563    },
564}
565
566/// A GROB's picture: `width` by `height` pixels, `rows` the pixels packed
567/// one bit each as hex digits (two per byte), every row starting on a
568/// byte, the leftmost pixel in the most significant bit, 1 dark: the
569/// page's frame layout, so a picture is drawn like the screen.
570#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
571pub struct Graphic {
572    /// Pixels per row.
573    pub width: u32,
574    /// Rows.
575    pub height: u32,
576    /// The packed rows, hex.
577    pub rows: String,
578}
579
580/// The largest GROB pictured, in pixels: a 2048 by 2048 one.
581const MAX_GRAPHIC_PIXELS: u64 = 2048 * 2048;
582
583impl Graphic {
584    /// The picture of `width` by `height` pixels whose rows are `packed`
585    /// as the page's frame packs them (one bit a pixel, every row starting
586    /// on a byte, the leftmost pixel in the most significant bit, 1 dark);
587    /// bits beyond the width are dropped. An error when the size is 0, too
588    /// large, or `packed` is not that long.
589    pub fn from_packed(width: u32, height: u32, packed: &[u8]) -> Result<Graphic> {
590        if width == 0 || height == 0 || u64::from(width) * u64::from(height) > MAX_GRAPHIC_PIXELS {
591            bail!("a picture of {width} by {height} pixels cannot be a GROB here");
592        }
593        let row_bytes = usize::try_from(width.div_ceil(8))?;
594        let h = usize::try_from(height)?;
595        if packed.len() != row_bytes * h {
596            bail!(
597                "{} bytes are not {width} by {height} pixels ({} bytes)",
598                packed.len(),
599                row_bytes * h
600            );
601        }
602        // The last byte of a row keeps only the pixels inside the width.
603        let tail = match width % 8 {
604            0 => 0xFF,
605            n => 0xFFu8 << (8 - n),
606        };
607        let mut rows = String::with_capacity(packed.len() * 2);
608        for row in packed.chunks(row_bytes) {
609            for (i, &b) in row.iter().enumerate() {
610                let b = if i + 1 == row_bytes { b & tail } else { b };
611                rows.push_str(&format!("{b:02X}"));
612            }
613        }
614        Ok(Graphic {
615            width,
616            height,
617            rows,
618        })
619    }
620
621    /// The GROB of this picture, all its nibbles, prolog first: the
622    /// inverse of [`graphic`] (the same layout, wiki:
623    /// protocols/hp-object-format, "GROB layout").
624    pub fn to_grob(&self) -> Result<Vec<u8>> {
625        let (width, height) = (self.width, self.height);
626        if width == 0 || height == 0 || u64::from(width) * u64::from(height) > MAX_GRAPHIC_PIXELS {
627            bail!("a picture of {width} by {height} pixels cannot be a GROB here");
628        }
629        let row_nibbles = usize::try_from(width.div_ceil(8) * 2)?;
630        let (w, h) = (usize::try_from(width)?, usize::try_from(height)?);
631        let body = row_nibbles * h;
632        if self.rows.len() != body || !self.rows.is_ascii() {
633            bail!("the rows of a {width} by {height} picture are {body} hex digits");
634        }
635        let put =
636            |out: &mut Vec<u8>, v: u64| out.extend((0..5).map(|i| ((v >> (4 * i)) & 0xF) as u8));
637        let mut n = Vec::with_capacity(20 + body);
638        put(&mut n, u64::from(ObjectType::Graphic.prolog()));
639        // The length counts itself, the height, the width and the body.
640        put(&mut n, 15 + body as u64);
641        put(&mut n, u64::from(height));
642        put(&mut n, u64::from(width));
643        for y in 0..h {
644            let hex = &self.rows[y * row_nibbles..(y + 1) * row_nibbles];
645            let mut row = vec![0u8; row_nibbles];
646            for b in 0..row_nibbles / 2 {
647                let byte = u8::from_str_radix(&hex[b * 2..b * 2 + 2], 16)
648                    .with_context(|| format!("row {y} is not hex"))?;
649                for bit in 0..8 {
650                    let x = b * 8 + bit;
651                    if x < w && byte & (0x80 >> bit) != 0 {
652                        row[x / 4] |= 1 << (x % 4);
653                    }
654                }
655            }
656            n.extend(row);
657        }
658        Ok(n)
659    }
660}
661
662/// The picture of the GROB `n` (all its nibbles, prolog first), or `None`
663/// when it is not a GROB, has no pixels (0 wide or high, as `#0 #0
664/// BLANK` makes), is too large, or its fields do not add up. The
665/// layout (wiki: protocols/hp-object-format, "GROB layout"): prolog
666/// #02B1E, length, height, width (5 nibbles each, low nibble first), then
667/// the rows, each padded to a whole number of bytes; within a nibble the
668/// least significant bit is the leftmost pixel.
669pub fn graphic(n: &[u8]) -> Option<Graphic> {
670    if field(n, 0, 5).ok()? != ObjectType::Graphic.prolog() {
671        return None;
672    }
673    let length = usize::try_from(field(n, 5, 5).ok()?).ok()?;
674    let height = field(n, 10, 5).ok()?;
675    let width = field(n, 15, 5).ok()?;
676    if width == 0 || height == 0 || u64::from(width) * u64::from(height) > MAX_GRAPHIC_PIXELS {
677        return None;
678    }
679    let row_nibbles = usize::try_from(width.div_ceil(8) * 2).ok()?;
680    let body = row_nibbles * usize::try_from(height).ok()?;
681    // The length counts itself, the height, the width and the body.
682    if length != 15 + body || n.len() < 20 + body {
683        return None;
684    }
685    let row_bytes = row_nibbles / 2;
686    let w = usize::try_from(width).ok()?;
687    let mut rows = String::with_capacity(body);
688    for y in 0..usize::try_from(height).ok()? {
689        let row = &n[20 + y * row_nibbles..20 + (y + 1) * row_nibbles];
690        for b in 0..row_bytes {
691            let mut byte = 0u8;
692            for bit in 0..8 {
693                let x = b * 8 + bit;
694                let on = (row[x / 4] >> (x % 4)) & 1 == 1;
695                if on && x < w {
696                    byte |= 0x80 >> bit;
697                }
698            }
699            rows.push_str(&format!("{byte:02X}"));
700        }
701    }
702    Some(Graphic {
703        width,
704        height,
705        rows,
706    })
707}
708
709/// Read access to the calculator's memory, for ROM pointers.
710pub trait Memory {
711    /// The nibble at `addr`, if readable.
712    fn nibble(&self, addr: u32) -> Option<u8>;
713}
714
715/// No memory: ROM pointers stay [`Object::Command`].
716#[derive(Clone, Copy, Debug, Default)]
717pub struct NoMemory;
718
719impl Memory for NoMemory {
720    fn nibble(&self, _addr: u32) -> Option<u8> {
721        None
722    }
723}
724
725fn hex(nibbles: &[u8]) -> String {
726    nibbles
727        .iter()
728        .map(|&n| char::from_digit(u32::from(n & 0xF), 16).unwrap_or('?'))
729        .collect::<String>()
730        .to_uppercase()
731}
732
733fn field(n: &[u8], at: usize, width: usize) -> Result<u32> {
734    read_field(n, at, width).with_context(|| format!("object truncated at nibble {at}"))
735}
736
737fn usize_field(n: &[u8], at: usize, width: usize) -> Result<usize> {
738    Ok(usize::try_from(field(n, at, width)?)?)
739}
740
741/// HP-character text of `count` bytes stored as nibble pairs at `at`.
742fn chars(n: &[u8], at: usize, count: usize) -> Result<String> {
743    let bytes = (0..count)
744        .map(|i| field(n, at + 2 * i, 2).map(|b| b as u8))
745        .collect::<Result<Vec<u8>>>()?;
746    Ok(charset::decode(&bytes))
747}
748
749/// Decode the object at the start of `nibbles`. An object the walk does
750/// not understand becomes [`Object::Unknown`] with all of `nibbles`.
751pub fn decode(nibbles: &[u8], mem: &dyn Memory) -> Result<Object> {
752    Reader::new(mem).decode(nibbles)
753}
754
755/// Decode the object at `addr` in `mem` (a stack level, a variable): an
756/// object with a known prolog, else a primitive or ROM word
757/// ([`Object::Command`] with its address).
758pub fn decode_at(addr: u32, mem: &dyn Memory) -> Result<Object> {
759    Reader::new(mem).decode_at(addr)
760}
761
762/// Decodes several objects in memory (the levels of a stack) under one
763/// budget of [`MAX_DECODED_OBJECTS`] and [`MAX_DECODED_NIBBLES`]: the
764/// caller gets one bounded result, however many levels point at the same
765/// large object.
766pub struct Reader<'a> {
767    decoder: Decoder<'a>,
768}
769
770impl fmt::Debug for Reader<'_> {
771    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
772        f.debug_struct("Reader")
773            .field("budget", &self.decoder.budget.get())
774            .field("nibbles", &self.decoder.nibbles.get())
775            .field("names", &self.decoder.names.is_some())
776            .finish_non_exhaustive()
777    }
778}
779
780impl<'a> Reader<'a> {
781    /// A reader over `mem` with a fresh budget, without command names
782    /// (commands stay unnamed, programs and algebraics without text).
783    pub fn new(mem: &'a dyn Memory) -> Self {
784        Self {
785            decoder: Decoder::new(mem, None, Settings::default()),
786        }
787    }
788
789    /// A reader that names commands from `names` and writes the text of
790    /// programs, algebraics and units with `settings`.
791    pub fn with_names(mem: &'a dyn Memory, names: &'a NameTable, settings: Settings) -> Self {
792        Self {
793            decoder: Decoder::new(mem, Some(names), settings),
794        }
795    }
796
797    /// The object at `addr`, as [`decode_at`], charged to this reader's
798    /// budget. With names, an address that is a named command's object is
799    /// that command.
800    pub fn decode_at(&self, addr: u32) -> Result<Object> {
801        let d = &self.decoder;
802        if let Some(info) = d.names.and_then(|t| t.command_at(addr, d.mem)) {
803            d.charge(1)?;
804            return Ok(Object::Command {
805                name: info.name.map(str::to_string),
806                address: Some(addr),
807                library: Some(info.library),
808                command: Some(info.number),
809                source: None,
810            });
811        }
812        d.rom_object(addr, 0)
813    }
814
815    /// The object at the start of `nibbles`, as [`decode`].
816    pub fn decode(&self, nibbles: &[u8]) -> Result<Object> {
817        match self.decoder.object(nibbles, 0, 0) {
818            Ok((obj, _)) => Ok(obj),
819            // Past the budget the object is not unknown, it is too big.
820            Err(e) if self.decoder.exhausted.get() => Err(e),
821            Err(_) => {
822                let prolog = field(nibbles, 0, 5)?;
823                Ok(unknown(prolog, nibbles))
824            }
825        }
826    }
827}
828
829fn unknown(prolog: u32, nibbles: &[u8]) -> Object {
830    let shown = nibbles.len().min(MAX_HEX_NIBBLES);
831    Object::Unknown {
832        prolog: format!("{prolog:05X}"),
833        kind: ObjectType::from_prolog(prolog).map(|t| t.name().to_string()),
834        nibbles: nibbles.len(),
835        hex: hex(&nibbles[..shown]),
836        truncated: shown < nibbles.len(),
837        source: None,
838        graphic: graphic(nibbles),
839    }
840}
841
842struct Decoder<'a> {
843    mem: &'a dyn Memory,
844    /// Command names; without them commands stay unnamed and programs,
845    /// algebraics and units get no text.
846    names: Option<&'a NameTable>,
847    /// Display settings of the text.
848    settings: Settings,
849    /// Objects this decode may still produce.
850    budget: Cell<usize>,
851    /// Nibbles this decode may still read (see [`MAX_DECODED_NIBBLES`]).
852    nibbles: Cell<usize>,
853    /// Nibbles this decode may still clone (see [`MAX_CLONED_NIBBLES`]).
854    cloned: Cell<usize>,
855    /// Objects already read from memory by (address, depth), with the
856    /// objects and nibbles they cost: a repeated pointer is cloned, not
857    /// read again, and charged again before the clone, so the clones are
858    /// bounded like the reads.
859    seen: RefCell<HashMap<(u32, usize), (Object, Cost)>>,
860    /// Whether a budget ran out.
861    exhausted: Cell<bool>,
862}
863
864impl<'a> Decoder<'a> {
865    fn new(mem: &'a dyn Memory, names: Option<&'a NameTable>, settings: Settings) -> Self {
866        Self {
867            mem,
868            names,
869            settings,
870            budget: Cell::new(MAX_DECODED_OBJECTS),
871            nibbles: Cell::new(MAX_DECODED_NIBBLES),
872            cloned: Cell::new(MAX_CLONED_NIBBLES),
873            seen: RefCell::new(HashMap::new()),
874            exhausted: Cell::new(false),
875        }
876    }
877
878    /// Take `count` objects from the budget.
879    fn charge(&self, count: usize) -> Result<()> {
880        let Some(left) = self.budget.get().checked_sub(count) else {
881            self.exhausted.set(true);
882            bail!(
883                "more than {MAX_DECODED_OBJECTS} objects (MAX_DECODED_OBJECTS) to decode: \
884                 pointers that repeat, memory corrupt or not set up"
885            );
886        };
887        self.budget.set(left);
888        Ok(())
889    }
890
891    /// Take `count` nibbles from the budget.
892    fn charge_nibbles(&self, count: usize) -> Result<()> {
893        let Some(left) = self.nibbles.get().checked_sub(count) else {
894            self.exhausted.set(true);
895            bail!(
896                "more than {MAX_DECODED_NIBBLES} nibbles (MAX_DECODED_NIBBLES) to decode: \
897                 objects that repeat, memory corrupt or not set up"
898            );
899        };
900        self.nibbles.set(left);
901        Ok(())
902    }
903
904    /// Take `count` nibbles from the clone budget.
905    fn charge_cloned(&self, count: usize) -> Result<()> {
906        let Some(left) = self.cloned.get().checked_sub(count) else {
907            self.exhausted.set(true);
908            bail!(
909                "more than {MAX_CLONED_NIBBLES} nibbles (MAX_CLONED_NIBBLES) of repeated \
910                 objects to decode: pointers that repeat, memory corrupt or not set up"
911            );
912        };
913        self.cloned.set(left);
914        Ok(())
915    }
916
917    /// What is left of the budgets; `nibbles` counts reads and clones
918    /// together, so an object's cost includes the clones inside it.
919    fn left(&self) -> Cost {
920        Cost {
921            objects: self.budget.get(),
922            nibbles: self.nibbles.get() + self.cloned.get(),
923        }
924    }
925
926    /// The object with a prolog at `at` and its size in nibbles.
927    fn object(&self, n: &[u8], at: usize, depth: usize) -> Result<(Object, usize)> {
928        if depth > MAX_DEPTH {
929            bail!("objects nested deeper than {MAX_DEPTH} levels");
930        }
931        self.charge(1)?;
932        let prolog = field(n, at, 5)?;
933        let ty = ObjectType::from_prolog(prolog)
934            .with_context(|| format!("unknown prolog {prolog:05X} at nibble {at}"))?;
935        let size = object_size(n, at)?;
936        let body = at + 5;
937        let obj = match ty {
938            ObjectType::Real => Object::Real {
939                value: Real::from_body(&n[body..])?,
940            },
941            ObjectType::Complex => Object::Complex {
942                re: Real::from_body(&n[body..])?,
943                im: Real::from_body(&n[body + 16..])?,
944            },
945            ObjectType::String => {
946                let len = usize_field(n, body, 5)?;
947                let count = len.checked_sub(5).context("string length too small")?;
948                if count % 2 != 0 {
949                    bail!("string of an odd number of nibbles");
950                }
951                Object::String {
952                    value: chars(n, body + 5, count / 2)?,
953                }
954            }
955            ObjectType::BinaryInteger => {
956                let len = usize_field(n, body, 5)?;
957                let count = len.checked_sub(5).context("binary length too small")?;
958                if count > 16 {
959                    return Ok((unknown(prolog, &n[at..at + size]), size));
960                }
961                let value = n[body + 5..body + 5 + count]
962                    .iter()
963                    .rev()
964                    .fold(0u64, |acc, &d| (acc << 4) | u64::from(d));
965                Object::Binary {
966                    value,
967                    base: None,
968                    text: None,
969                }
970            }
971            ObjectType::Integer => {
972                let len = usize_field(n, body, 5)?;
973                let count = len.checked_sub(5).context("integer length too small")?;
974                Object::Integer {
975                    value: Integer::from_nibbles(&n[body + 5..body + 5 + count])?,
976                }
977            }
978            ObjectType::GlobalName | ObjectType::LocalName => {
979                let count = usize_field(n, body, 2)?;
980                let value = chars(n, body + 2, count)?;
981                if ty == ObjectType::GlobalName {
982                    Object::Name { value }
983                } else {
984                    Object::LocalName { value }
985                }
986            }
987            ObjectType::Character => Object::Character {
988                value: chars(n, body, 1)?,
989            },
990            ObjectType::List => Object::List {
991                items: self
992                    .elements(n, body, depth)?
993                    .into_iter()
994                    .map(element_object)
995                    .collect(),
996            },
997            ObjectType::Tagged => {
998                let count = usize_field(n, body, 2)?;
999                let tag = chars(n, body + 2, count)?;
1000                let (object, _) = self.element(n, body + 2 + 2 * count, depth)?;
1001                Object::Tagged {
1002                    tag,
1003                    object: Box::new(element_object(object)),
1004                }
1005            }
1006            ObjectType::Unit => {
1007                let mut elements = self.elements(n, body, depth)?.into_iter();
1008                let Some(Element::Object(Object::Real { value })) = elements.next() else {
1009                    bail!("unit does not start with a real");
1010                };
1011                let rest: Vec<Element> = elements.collect();
1012                let unit = self
1013                    .names
1014                    .and_then(|_| decompile::unit(&rest, &self.settings));
1015                Object::Unit { value, unit }
1016            }
1017            ObjectType::Array => self.array(n, at, size)?,
1018            ObjectType::SymbolicMatrix => self.symbolic_matrix(n, body, depth)?,
1019            ObjectType::Program => {
1020                let elements = self.elements(n, body, depth)?;
1021                Object::Program {
1022                    source: self
1023                        .names
1024                        .map(|_| decompile::program(&elements, &self.settings)),
1025                }
1026            }
1027            ObjectType::Algebraic => self.symbolic(n, body, depth)?.0,
1028            ObjectType::XlibName => element_object(self.xlib(n, body)?),
1029            _ => unknown(prolog, &n[at..at + size]),
1030        };
1031        Ok((obj, size))
1032    }
1033
1034    /// The algebraic whose body starts at `body`, and its elements.
1035    fn symbolic(&self, n: &[u8], body: usize, depth: usize) -> Result<(Object, Vec<Element>)> {
1036        let elements = self.elements(n, body, depth)?;
1037        let object = Object::Algebraic {
1038            source: self
1039                .names
1040                .and_then(|_| decompile::algebraic(&elements, &self.settings)),
1041        };
1042        Ok((object, elements))
1043    }
1044
1045    /// Composite elements from `at` up to SEMI.
1046    fn elements(&self, n: &[u8], mut at: usize, depth: usize) -> Result<Vec<Element>> {
1047        let mut items = Vec::new();
1048        while field(n, at, 5)? != SEMI {
1049            let (obj, size) = self.element(n, at, depth)?;
1050            items.push(obj);
1051            at += size;
1052        }
1053        Ok(items)
1054    }
1055
1056    /// An embedded object or a 5-nibble ROM pointer at `at`.
1057    fn element(&self, n: &[u8], at: usize, depth: usize) -> Result<(Element, usize)> {
1058        let p = field(n, at, 5)?;
1059        match ObjectType::from_prolog(p) {
1060            Some(ObjectType::XlibName) => {
1061                self.charge(1)?;
1062                Ok((self.xlib(n, at + 5)?, 11))
1063            }
1064            Some(ObjectType::SystemBinary) => {
1065                self.charge(1)?;
1066                Ok((Element::SystemBinary(field(n, at + 5, 5)?), 10))
1067            }
1068            Some(ObjectType::Algebraic) if depth < MAX_DEPTH => {
1069                self.charge(1)?;
1070                let size = object_size(n, at)?;
1071                let (object, elements) = self.symbolic(n, at + 5, depth + 1)?;
1072                Ok((Element::Symbolic { object, elements }, size))
1073            }
1074            Some(_) => {
1075                let (obj, size) = self.object(n, at, depth + 1)?;
1076                Ok((Element::Object(obj), size))
1077            }
1078            None => Ok((self.pointer(p, depth + 1)?, 5)),
1079        }
1080    }
1081
1082    /// The command an XLIB body (library, command) at `at` names.
1083    fn xlib(&self, n: &[u8], at: usize) -> Result<Element> {
1084        let lib = field(n, at, 3)?;
1085        let cmd = field(n, at + 3, 3)?;
1086        let (lib, cmd) = (lib as u16, cmd as u16);
1087        let info = self.names.and_then(|t| t.xlib(lib, cmd));
1088        Ok(Element::Command {
1089            name: info.and_then(|i| i.name).map(str::to_string),
1090            address: None,
1091            xlib: Some((lib, cmd)),
1092            arity: info.and_then(|i| i.arity),
1093            silent: false,
1094        })
1095    }
1096
1097    /// The element a ROM pointer stands for: a command (named when the
1098    /// table knows it), a unit operator, a system binary, or the data
1099    /// object it points to.
1100    fn pointer(&self, p: u32, depth: usize) -> Result<Element> {
1101        if let Some(t) = self.names {
1102            if let Some(info) = t.command_at(p, self.mem) {
1103                self.charge(1)?;
1104                return Ok(Element::Command {
1105                    name: info.name.map(str::to_string),
1106                    address: Some(p),
1107                    xlib: Some((info.library, info.number)),
1108                    arity: info.arity,
1109                    silent: info.silent,
1110                });
1111            }
1112            if let Some(m) = t.unit_markers() {
1113                let op = [
1114                    (m.times, UnitOp::Times),
1115                    (m.divide, UnitOp::Divide),
1116                    (m.power, UnitOp::Power),
1117                    (m.prefix, UnitOp::Prefix),
1118                    (m.end, UnitOp::End),
1119                ]
1120                .into_iter()
1121                .find_map(|(a, op)| (a == p).then_some(op));
1122                if let Some(op) = op {
1123                    self.charge(1)?;
1124                    return Ok(Element::Unit(op));
1125                }
1126            }
1127        }
1128        let read = |at: u32, width: u32| -> Option<u32> {
1129            (0..width).rev().try_fold(0u32, |v, i| {
1130                Some((v << 4) | u32::from(self.mem.nibble(at.wrapping_add(i))?))
1131            })
1132        };
1133        let command = Element::Command {
1134            name: None,
1135            address: Some(p),
1136            xlib: None,
1137            arity: None,
1138            silent: false,
1139        };
1140        match read(p, 5).and_then(ObjectType::from_prolog) {
1141            // Unreadable, a primitive (its prolog is its own code), code
1142            // or a program in ROM: a command, never decoded.
1143            None | Some(ObjectType::Program | ObjectType::Code) => {
1144                self.charge(1)?;
1145                Ok(command)
1146            }
1147            Some(ObjectType::SystemBinary) => {
1148                self.charge(1)?;
1149                Ok(read(p + 5, 5).map_or(command, Element::SystemBinary))
1150            }
1151            Some(ObjectType::XlibName) => {
1152                self.charge(1)?;
1153                let body: Option<Vec<u8>> = (0..6).map(|i| self.mem.nibble(p + 5 + i)).collect();
1154                match body {
1155                    Some(b) => self.xlib(&b, 0),
1156                    None => Ok(command),
1157                }
1158            }
1159            Some(_) => Ok(Element::Object(self.rom_object(p, depth)?)),
1160        }
1161    }
1162
1163    /// The object at `addr` in memory (a ROM pointer's target, a stack
1164    /// level), or a command.
1165    fn rom_object(&self, addr: u32, depth: usize) -> Result<Object> {
1166        if let Some((obj, cost)) = self.seen.borrow().get(&(addr, depth)) {
1167            self.charge(cost.objects)?;
1168            self.charge_cloned(cost.nibbles)?;
1169            return Ok(obj.clone());
1170        }
1171        let before = self.left();
1172        let obj = self.read_object(addr, depth)?;
1173        let after = self.left();
1174        let cost = Cost {
1175            objects: before.objects - after.objects,
1176            nibbles: before.nibbles - after.nibbles,
1177        };
1178        self.seen
1179            .borrow_mut()
1180            .insert((addr, depth), (obj.clone(), cost));
1181        Ok(obj)
1182    }
1183
1184    /// [`Decoder::rom_object`] without the cache.
1185    fn read_object(&self, addr: u32, depth: usize) -> Result<Object> {
1186        // As many nibbles as are readable, up to `len`.
1187        let read = |len: usize| -> Vec<u8> {
1188            (0..len)
1189                .map_while(|i| {
1190                    u32::try_from(i)
1191                        .ok()
1192                        .and_then(|i| self.mem.nibble(addr.wrapping_add(i)))
1193                })
1194                .collect()
1195        };
1196        let head = read(5);
1197        if read_field(&head, 0, 5)
1198            .and_then(ObjectType::from_prolog)
1199            .is_none()
1200        {
1201            // Unreadable, a primitive (its prolog is its own code) or a
1202            // word in ROM.
1203            self.charge(1)?;
1204            return Ok(Object::Command {
1205                name: None,
1206                address: Some(addr),
1207                library: None,
1208                command: None,
1209                source: None,
1210            });
1211        }
1212        let mut len = 64;
1213        loop {
1214            let nib = read(len);
1215            match object_size(&nib, 0) {
1216                Ok(size) => {
1217                    self.charge_nibbles(size)?;
1218                    return Ok(self.object(&nib, 0, depth)?.0);
1219                }
1220                Err(_) if nib.len() == len && len < MAX_MEMORY_OBJECT => len *= 8,
1221                Err(e) => bail!("object at #{addr:05X}: {e}"),
1222            }
1223        }
1224    }
1225
1226    /// A 49G symbolic matrix: its elements, or its rows (each a symbolic
1227    /// vector), as an array.
1228    fn symbolic_matrix(&self, n: &[u8], body: usize, depth: usize) -> Result<Object> {
1229        let items: Vec<Object> = self
1230            .elements(n, body, depth)?
1231            .into_iter()
1232            .map(element_object)
1233            .collect();
1234        let rows = !items.is_empty()
1235            && items
1236                .iter()
1237                .all(|o| matches!(o, Object::Array { dims, .. } if dims.len() == 1));
1238        if !rows {
1239            return Ok(Object::Array {
1240                dims: vec![items.len()],
1241                items: items
1242                    .into_iter()
1243                    .map(|o| ArrayItem::Item(Box::new(o)))
1244                    .collect(),
1245            });
1246        }
1247        let cols = match items.first() {
1248            Some(Object::Array { dims, .. }) => dims[0],
1249            _ => 0,
1250        };
1251        Ok(Object::Array {
1252            dims: vec![items.len(), cols],
1253            items: items
1254                .into_iter()
1255                .map(|o| match o {
1256                    Object::Array { items, .. } => ArrayItem::Row(items),
1257                    o => ArrayItem::Item(Box::new(o)),
1258                })
1259                .collect(),
1260        })
1261    }
1262
1263    /// A real or complex array.
1264    fn array(&self, n: &[u8], at: usize, size: usize) -> Result<Object> {
1265        let body = at + 5;
1266        let elem = field(n, body + 5, 5)?;
1267        let ndims = usize_field(n, body + 10, 5)?;
1268        if ndims > MAX_ARRAY_DIMS {
1269            bail!("array of {ndims} dimensions (at most {MAX_ARRAY_DIMS})");
1270        }
1271        let bad = || anyhow::anyhow!("array dimensions do not match its length");
1272        // Every count below comes from the object: check it against the
1273        // object's size before using it (no allocation from a bad count).
1274        let start = ndims
1275            .checked_mul(5)
1276            .and_then(|d| d.checked_add(body + 15))
1277            .filter(|&s| s <= at + size)
1278            .ok_or_else(bad)?;
1279        let dims = (0..ndims)
1280            .map(|i| usize_field(n, body + 15 + 5 * i, 5))
1281            .collect::<Result<Vec<usize>>>()?;
1282        let width = match ObjectType::from_prolog(elem) {
1283            Some(ObjectType::Real) => 16,
1284            Some(ObjectType::Complex) => 32,
1285            _ => return Ok(unknown(field(n, at, 5)?, &n[at..at + size])),
1286        };
1287        // Rows at each level and the element count must fit the object
1288        // (an empty dimension keeps the elements at 0 but not the rows).
1289        let mut count: usize = 1;
1290        for d in &dims {
1291            count = count
1292                .checked_mul(*d)
1293                .filter(|&c| c <= size)
1294                .ok_or_else(bad)?;
1295        }
1296        let end = count
1297            .checked_mul(width)
1298            .and_then(|b| b.checked_add(start))
1299            .ok_or_else(bad)?;
1300        if ndims == 0 || end > at + size {
1301            return Err(bad());
1302        }
1303        self.charge(count)?;
1304        let mut flat = Vec::with_capacity(count);
1305        for i in 0..count {
1306            let p = start + i * width;
1307            flat.push(if width == 16 {
1308                Object::Real {
1309                    value: Real::from_body(&n[p..])?,
1310                }
1311            } else {
1312                Object::Complex {
1313                    re: Real::from_body(&n[p..])?,
1314                    im: Real::from_body(&n[p + 16..])?,
1315                }
1316            });
1317        }
1318        Ok(Object::Array {
1319            items: nest(&dims, &mut flat.into_iter()),
1320            dims,
1321        })
1322    }
1323}
1324
1325/// Objects and nibbles, taken from or left in a decode's budgets.
1326#[derive(Clone, Copy, Debug)]
1327struct Cost {
1328    objects: usize,
1329    nibbles: usize,
1330}
1331
1332/// An element of a list or tagged object as an object.
1333fn element_object(e: Element) -> Object {
1334    match e {
1335        Element::Object(o) | Element::Symbolic { object: o, .. } => o,
1336        Element::Command {
1337            name,
1338            address,
1339            xlib,
1340            ..
1341        } => Object::Command {
1342            name,
1343            address,
1344            library: xlib.map(|x| x.0),
1345            command: xlib.map(|x| x.1),
1346            source: None,
1347        },
1348        Element::SystemBinary(v) => Object::Unknown {
1349            prolog: format!("{:05X}", ObjectType::SystemBinary.prolog()),
1350            kind: Some(ObjectType::SystemBinary.name().to_string()),
1351            nibbles: 10,
1352            hex: format!("{:05X}", ObjectType::SystemBinary.prolog())
1353                .chars()
1354                .rev()
1355                .chain(format!("{v:05X}").chars().rev())
1356                .collect(),
1357            truncated: false,
1358            source: None,
1359            graphic: None,
1360        },
1361        // A unit operator is an empty list in ROM.
1362        Element::Unit(_) => Object::List { items: Vec::new() },
1363    }
1364}
1365
1366/// Elements in lexicographic index order, nested by dimension (at most
1367/// [`MAX_ARRAY_DIMS`] levels deep).
1368fn nest(dims: &[usize], flat: &mut impl Iterator<Item = Object>) -> Vec<ArrayItem> {
1369    match dims {
1370        [] => Vec::new(),
1371        [n] => flat
1372            .take(*n)
1373            .map(|o| ArrayItem::Item(Box::new(o)))
1374            .collect(),
1375        [n, rest @ ..] => (0..*n).map(|_| ArrayItem::Row(nest(rest, flat))).collect(),
1376    }
1377}
1378
1379impl Object {
1380    /// Whether this object or one inside it needs text from the ASCII
1381    /// transfer (the transfer layer fills them in).
1382    pub fn needs_source(&self) -> bool {
1383        match self {
1384            Object::Program { source } | Object::Algebraic { source } => source.is_none(),
1385            Object::Command { name, source, .. } => name.is_none() && source.is_none(),
1386            Object::Unit { unit, .. } => unit.is_none(),
1387            Object::Unknown { source, .. } => source.is_none(),
1388            Object::List { items } => items.iter().any(Object::needs_source),
1389            Object::Tagged { object, .. } => object.needs_source(),
1390            _ => false,
1391        }
1392    }
1393
1394    /// Set the base (and display text) of every binary integer inside.
1395    pub fn set_base(&mut self, b: Base) {
1396        match self {
1397            Object::Binary { value, base, text } => {
1398                *base = Some(b);
1399                *text = Some(b.format(*value));
1400            }
1401            Object::List { items } => items.iter_mut().for_each(|o| o.set_base(b)),
1402            Object::Tagged { object, .. } => object.set_base(b),
1403            _ => {}
1404        }
1405    }
1406
1407    /// Whether a binary integer is inside.
1408    pub fn has_binary(&self) -> bool {
1409        match self {
1410            Object::Binary { .. } => true,
1411            Object::List { items } => items.iter().any(Object::has_binary),
1412            Object::Tagged { object, .. } => object.has_binary(),
1413            _ => false,
1414        }
1415    }
1416}
1417#[cfg(test)]
1418mod tests {
1419    use super::*;
1420    use serde_json::json;
1421
1422    /// Nibbles from a hex string in memory order.
1423    fn nib(s: &str) -> Vec<u8> {
1424        s.chars().map(|c| c.to_digit(16).unwrap() as u8).collect()
1425    }
1426
1427    fn dec(s: &str) -> Object {
1428        decode(&nib(s), &NoMemory).unwrap()
1429    }
1430
1431    fn real(s: &str) -> Real {
1432        Real::parse(s).unwrap()
1433    }
1434
1435    /// An unnamed command at `address`.
1436    fn cmd(address: u32) -> Object {
1437        Object::Command {
1438            name: None,
1439            address: Some(address),
1440            library: None,
1441            command: None,
1442            source: None,
1443        }
1444    }
1445
1446    fn put_field(out: &mut Vec<u8>, value: u64, width: usize) {
1447        out.extend((0..width).map(|i| ((value >> (4 * i)) & 0xF) as u8));
1448    }
1449
1450    // Byte-level fixtures: GETs from the 48SX ROM J and the 49G (2009 ROM)
1451    // in saturnus, 2026-10-05; wiki: protocols/hp-object-format.
1452
1453    #[test]
1454    fn reals_decode_exactly() {
1455        // 8.72653549837E-3 (SIN 0.5 in degrees).
1456        let o = dec("339207997389453562780");
1457        assert_eq!(
1458            o,
1459            Object::Real {
1460                value: real("8.72653549837E-3")
1461            }
1462        );
1463        assert_eq!(
1464            serde_json::to_value(&o).unwrap(),
1465            json!({"type": "real", "value": 0.00872653549837})
1466        );
1467        // -1.5E-300 and 0.
1468        let o = dec("339200070000000000519");
1469        let Object::Real { value } = o else { panic!() };
1470        assert_eq!(value.to_source(), "-1.5E-300");
1471        assert!(value.negative);
1472        assert_eq!(
1473            dec("339200000000000000000"),
1474            Object::Real { value: Real::ZERO }
1475        );
1476    }
1477
1478    #[test]
1479    fn real_text_round_trips_and_rounds() {
1480        for s in [
1481            "4.79425538604E-1",
1482            "5.",
1483            "-1.5E-300",
1484            "9.99999999999E499",
1485            "1.E-499",
1486            "0.",
1487        ] {
1488            let r = real(s);
1489            assert_eq!(r.to_source(), s);
1490            assert_eq!(Real::from_body(&r.to_body()).unwrap(), r);
1491        }
1492        assert_eq!(real("0.479425538604").to_source(), "4.79425538604E-1");
1493        assert_eq!(real(".5").to_source(), "5.E-1");
1494        assert_eq!(real("123").to_source(), "1.23E2");
1495        // 13 digits round half away from zero, with carry.
1496        assert_eq!(real("1.234567890125").to_source(), "1.23456789013");
1497        assert_eq!(real("9.999999999995").to_source(), "1.E1");
1498        assert_eq!(real("-0.000").to_source(), "0.");
1499        assert!(Real::parse("1E500").is_err());
1500        assert!(Real::parse("1.2.3").is_err());
1501        assert!(Real::parse("").is_err());
1502        assert!(Real::parse("e5").is_err());
1503        assert_eq!(
1504            Real::from_f64(0.479425538604).unwrap(),
1505            real("4.79425538604E-1")
1506        );
1507    }
1508
1509    #[test]
1510    fn reals_beyond_f64_serialize_as_text() {
1511        let r = Object::Real {
1512            value: real("1.5E-400"),
1513        };
1514        assert_eq!(
1515            serde_json::to_value(&r).unwrap(),
1516            json!({"type": "real", "value": "1.5E-400"})
1517        );
1518        let back: Object =
1519            serde_json::from_value(json!({"type": "real", "value": "1.5E-400"})).unwrap();
1520        assert_eq!(back, r);
1521        let five: Object = serde_json::from_value(json!({"type": "real", "value": 5})).unwrap();
1522        assert_eq!(five, Object::Real { value: real("5") });
1523    }
1524
1525    #[test]
1526    fn complex_string_binary() {
1527        assert_eq!(
1528            dec("7792000000000000000100000000000000029"),
1529            Object::Complex {
1530                re: real("1"),
1531                im: real("-2")
1532            }
1533        );
1534        // "Hi «x»": « and » are HP bytes 0xAB and 0xBB.
1535        assert_eq!(
1536            dec("C2A2011000849602BA87BB"),
1537            Object::String {
1538                value: "Hi «x»".into()
1539            }
1540        );
1541        let mut b = dec("E4A2051000FF00000000000000");
1542        assert_eq!(
1543            b,
1544            Object::Binary {
1545                value: 255,
1546                base: None,
1547                text: None
1548            }
1549        );
1550        b.set_base(Base::Hex);
1551        assert_eq!(
1552            serde_json::to_value(&b).unwrap(),
1553            json!({"type": "binary", "value": 255, "base": "hex", "text": "# FFh"})
1554        );
1555        assert_eq!(Base::from_display("# 255d"), Some(Base::Dec));
1556        assert_eq!(Base::from_display("42."), None);
1557    }
1558
1559    #[test]
1560    fn integers_49g() {
1561        assert_eq!(
1562            dec("416207000050"),
1563            Object::Integer {
1564                value: Integer::parse("5").unwrap()
1565            }
1566        );
1567        let big = dec("416209100098765432109876543219");
1568        assert_eq!(
1569            big,
1570            Object::Integer {
1571                value: Integer::parse("-1234567890123456789").unwrap()
1572            }
1573        );
1574        assert_eq!(
1575            serde_json::to_value(&big).unwrap(),
1576            json!({"type": "integer", "value": "-1234567890123456789"})
1577        );
1578        assert_eq!(
1579            dec("416206000000"),
1580            Object::Integer {
1581                value: Integer::parse("0").unwrap()
1582            }
1583        );
1584        assert_eq!(
1585            serde_json::to_value(dec("416207000050")).unwrap(),
1586            json!({"type": "integer", "value": 5})
1587        );
1588    }
1589
1590    #[test]
1591    fn names_tagged_and_lists_with_rom_pointers() {
1592        // 48SX { 1 2. "s" X { 5 } # 2Ah }: 1, 2 and 5 are ROM pointers.
1593        let list = dec(
1594            "47A209C2A2ED2A2C2A20700003784E20108547A20D13A2B2130E4A2051000A200000000000000B2130",
1595        );
1596        let Object::List { items } = &list else {
1597            panic!("{list:?}")
1598        };
1599        assert_eq!(items.len(), 6);
1600        assert_eq!(items[0], cmd(0x2A2C9));
1601        assert_eq!(items[2], Object::String { value: "s".into() });
1602        assert_eq!(items[3], Object::Name { value: "X".into() });
1603        assert_eq!(
1604            items[4],
1605            Object::List {
1606                items: vec![cmd(0x2A31D)]
1607            }
1608        );
1609        assert_eq!(
1610            items[5],
1611            Object::Binary {
1612                value: 42,
1613                base: None,
1614                text: None
1615            }
1616        );
1617        // { :T:5 } with the 5 as a pointer.
1618        let t = dec("47A20CFA201045D13A2B2130");
1619        assert_eq!(
1620            t,
1621            Object::List {
1622                items: vec![Object::Tagged {
1623                    tag: "T".into(),
1624                    object: Box::new(cmd(0x2A31D))
1625                }]
1626            }
1627        );
1628    }
1629
1630    /// A memory holding the real 1 at #2A2C9 and 5 at #2A31D (48SX ROM J).
1631    struct Rom;
1632    impl Memory for Rom {
1633        fn nibble(&self, addr: u32) -> Option<u8> {
1634            let (base, obj) = match addr {
1635                0x2A2C9..=0x2A2DD => (0x2A2C9, "339200000000000000010"),
1636                0x2A31D..=0x2A331 => (0x2A31D, "339200000000000000050"),
1637                // A primitive: the "prolog" points past itself.
1638                0x10000..=0x10004 => (0x10000, "50001"),
1639                _ => return None,
1640            };
1641            nib(obj).get((addr - base) as usize).copied()
1642        }
1643    }
1644
1645    #[test]
1646    fn rom_pointers_resolve_through_memory() {
1647        let list = decode(&nib("47A209C2A2D13A200001B2130"), &Rom).unwrap();
1648        assert_eq!(
1649            list,
1650            Object::List {
1651                items: vec![
1652                    Object::Real { value: real("1") },
1653                    Object::Real { value: real("5") },
1654                    cmd(0x10000),
1655                ]
1656            }
1657        );
1658    }
1659
1660    #[test]
1661    fn arrays() {
1662        let m = dec(
1663            "8E920950003392020000200002000000000000000000100000000000000020000000000000003000000000000000400",
1664        );
1665        let item = |s: &str| ArrayItem::Item(Box::new(Object::Real { value: real(s) }));
1666        assert_eq!(
1667            m,
1668            Object::Array {
1669                dims: vec![2, 2],
1670                items: vec![
1671                    ArrayItem::Row(vec![item("1"), item("2")]),
1672                    ArrayItem::Row(vec![item("3"), item("4")])
1673                ],
1674            }
1675        );
1676        assert_eq!(
1677            serde_json::to_value(&m).unwrap(),
1678            json!({"type": "array", "dims": [2, 2], "items": [
1679                [{"type": "real", "value": 1.0}, {"type": "real", "value": 2.0}],
1680                [{"type": "real", "value": 3.0}, {"type": "real", "value": 4.0}]]})
1681        );
1682        let v = dec(
1683            "8E9204500077920100002000000000000000000100000000000000020000000000000003000000000000000400",
1684        );
1685        let Object::Array { dims, items } = v else {
1686            panic!()
1687        };
1688        assert_eq!(dims, vec![2]);
1689        assert_eq!(
1690            items[1],
1691            ArrayItem::Item(Box::new(Object::Complex {
1692                re: real("3"),
1693                im: real("4")
1694            }))
1695        );
1696    }
1697
1698    #[test]
1699    fn array_dimensions_cannot_overflow() {
1700        // Length #00023, reals, 4 dimensions 0x80000 x 0x80000 x 0x80000
1701        // x 8: the product is 2^60 and times 16 wraps to 0.
1702        let mut o = nib("8E920");
1703        put_field(&mut o, 0x23, 5);
1704        put_field(&mut o, 0x02933, 5);
1705        put_field(&mut o, 4, 5);
1706        for d in [0x80000u64, 0x80000, 0x80000, 8] {
1707            put_field(&mut o, d, 5);
1708        }
1709        assert_eq!(o.len(), 40);
1710        let Ok(Object::Unknown { prolog, .. }) = decode(&o, &NoMemory) else {
1711            panic!("decoded");
1712        };
1713        assert_eq!(prolog, "029E8");
1714        // Empty arrays stay decodable; huge row counts with a zero do not.
1715        let mut e = nib("8E920");
1716        put_field(&mut e, 25, 5);
1717        put_field(&mut e, 0x02933, 5);
1718        put_field(&mut e, 2, 5);
1719        put_field(&mut e, 3, 5);
1720        put_field(&mut e, 0, 5);
1721        assert_eq!(
1722            decode(&e, &NoMemory).unwrap(),
1723            Object::Array {
1724                dims: vec![3, 0],
1725                items: vec![
1726                    ArrayItem::Row(vec![]),
1727                    ArrayItem::Row(vec![]),
1728                    ArrayItem::Row(vec![])
1729                ],
1730            }
1731        );
1732        let mut z = nib("8E920");
1733        put_field(&mut z, 30, 5);
1734        put_field(&mut z, 0x02933, 5);
1735        put_field(&mut z, 3, 5);
1736        for d in [0x80000u64, 0x80000, 0] {
1737            put_field(&mut z, d, 5);
1738        }
1739        assert!(matches!(
1740            decode(&z, &NoMemory).unwrap(),
1741            Object::Unknown { .. }
1742        ));
1743    }
1744
1745    /// An array of `ndims` dimensions of 1 holding one real.
1746    fn dims_array(ndims: usize) -> Vec<u8> {
1747        let mut o = nib("8E920");
1748        put_field(&mut o, (15 + 5 * ndims + 16) as u64, 5);
1749        put_field(&mut o, 0x02933, 5);
1750        put_field(&mut o, ndims as u64, 5);
1751        for _ in 0..ndims {
1752            put_field(&mut o, 1, 5);
1753        }
1754        o.extend(nib("0000000000000010"));
1755        o
1756    }
1757
1758    #[test]
1759    fn arrays_have_at_most_two_dimensions() {
1760        let item = ArrayItem::Item(Box::new(Object::Real { value: real("1") }));
1761        assert_eq!(
1762            dec(&hex(&dims_array(2))),
1763            Object::Array {
1764                dims: vec![1, 1],
1765                items: vec![ArrayItem::Row(vec![item])],
1766            }
1767        );
1768        // 12000 dimensions nested the rows 12000 deep (a stack overflow
1769        // that aborted the process); now any third one is refused.
1770        for ndims in [3, 12_000] {
1771            let Ok(Object::Unknown { prolog, .. }) = decode(&dims_array(ndims), &NoMemory) else {
1772                panic!("{ndims} dimensions decoded");
1773            };
1774            assert_eq!(prolog, "029E8");
1775            let mut list = nib("47A20");
1776            list.extend(dims_array(ndims));
1777            list.extend(nib("B2130"));
1778            let e = Reader::new(&NoMemory)
1779                .decoder
1780                .object(&list, 0, 0)
1781                .unwrap_err();
1782            assert!(e.to_string().contains("dimensions"), "{e:#}");
1783        }
1784    }
1785
1786    /// A GROB's nibbles as the calculator stores them (wiki:
1787    /// protocols/hp-object-format): the prolog, the length, the height,
1788    /// the width, low nibble first, then each row padded to whole bytes,
1789    /// the leftmost pixel in a nibble's least significant bit.
1790    fn grob(rows: &[&str]) -> Vec<u8> {
1791        let height = rows.len();
1792        let width = rows.first().map_or(0, |r| r.len());
1793        let row_nibbles = width.div_ceil(8) * 2;
1794        let mut n = Vec::new();
1795        let put = |v: usize, n: &mut Vec<u8>| {
1796            for i in 0..5 {
1797                n.push(((v >> (4 * i)) & 0xF) as u8);
1798            }
1799        };
1800        put(0x02B1E, &mut n);
1801        put(15 + row_nibbles * height, &mut n);
1802        put(height, &mut n);
1803        put(width, &mut n);
1804        for r in rows {
1805            let mut row = vec![0u8; row_nibbles];
1806            for (x, c) in r.chars().enumerate() {
1807                if c == '#' {
1808                    row[x / 4] |= 1 << (x % 4);
1809                }
1810            }
1811            n.extend(row);
1812        }
1813        n
1814    }
1815
1816    #[test]
1817    fn a_grob_is_pictured_row_by_row_leftmost_pixel_first() {
1818        let g = graphic(&grob(&["#"])).unwrap();
1819        assert_eq!((g.width, g.height, g.rows.as_str()), (1, 1, "80"));
1820        // An odd width: 5 pixels in one byte per row.
1821        let g = graphic(&grob(&["#.#.#", ".#.#.", "....#"])).unwrap();
1822        assert_eq!((g.width, g.height, g.rows.as_str()), (5, 3, "A85008"));
1823        // Nine pixels: two bytes a row, the ninth in the second's top bit.
1824        let g = graphic(&grob(&["........#", "#........"])).unwrap();
1825        assert_eq!(g.rows, "00808000");
1826        // The screen's size: 34 nibbles a row, length #0088F (the FAQ's).
1827        let mut row = ".".repeat(131);
1828        row.replace_range(130..131, "#");
1829        let screen: Vec<&str> = std::iter::repeat_n(row.as_str(), 64).collect();
1830        let n = grob(&screen);
1831        assert_eq!(n.len(), 2196);
1832        assert_eq!(field(&n, 5, 5).unwrap(), 0x88F);
1833        let g = graphic(&n).unwrap();
1834        assert_eq!((g.width, g.height), (131, 64));
1835        assert_eq!(&g.rows[..34], "0000000000000000000000000000000020");
1836        // As an object: an unknown one with its picture.
1837        let Object::Unknown {
1838            kind,
1839            graphic: Some(p),
1840            ..
1841        } = decode(&grob(&["##"]), &NoMemory).unwrap()
1842        else {
1843            panic!("not a pictured unknown");
1844        };
1845        assert_eq!((kind.as_deref(), p.rows.as_str()), (Some("Graphic"), "C0"));
1846    }
1847
1848    #[test]
1849    fn a_picture_encodes_as_the_grob_it_came_from() {
1850        for rows in [
1851            vec!["#"],
1852            vec!["#.#.#", ".#.#.", "....#"],
1853            vec!["........#", "#........"],
1854            vec!["#.......", "........"],
1855        ] {
1856            let n = grob(&rows);
1857            let g = graphic(&n).unwrap();
1858            assert_eq!(g.to_grob().unwrap(), n, "{rows:?}");
1859            assert_eq!(graphic(&g.to_grob().unwrap()), Some(g));
1860        }
1861        // The screen: 131 by 64, 2196 nibbles, length #0088F.
1862        let mut packed = vec![0u8; 17 * 64];
1863        packed[16] = 0x20;
1864        packed[17 * 63] = 0x80;
1865        let g = Graphic::from_packed(131, 64, &packed).unwrap();
1866        let n = g.to_grob().unwrap();
1867        assert_eq!(n.len(), 2196);
1868        assert_eq!(field(&n, 5, 5).unwrap(), 0x88F);
1869        assert_eq!(
1870            (field(&n, 10, 5).unwrap(), field(&n, 15, 5).unwrap()),
1871            (64, 131)
1872        );
1873        assert_eq!(n[20 + 32], 0x4, "pixel 130 of row 0");
1874        assert_eq!(n[20 + 34 * 63], 0x1, "pixel 0 of row 63");
1875        assert_eq!(graphic(&n), Some(g));
1876        // The 42S's 131 by 16.
1877        let g = Graphic::from_packed(131, 16, &[0xFF; 17 * 16]).unwrap();
1878        let n = g.to_grob().unwrap();
1879        assert_eq!(n.len(), 20 + 34 * 16);
1880        assert_eq!(graphic(&n), Some(g));
1881    }
1882
1883    #[test]
1884    fn packed_rows_lose_the_bits_beyond_the_width() {
1885        let g = Graphic::from_packed(5, 2, &[0xFF, 0x07]).unwrap();
1886        assert_eq!(g.rows, "F800");
1887        assert_eq!(graphic(&g.to_grob().unwrap()), Some(g));
1888        let g = Graphic::from_packed(16, 1, &[0xAB, 0xCD]).unwrap();
1889        assert_eq!(g.rows, "ABCD");
1890    }
1891
1892    #[test]
1893    fn a_picture_of_no_or_the_wrong_size_is_refused() {
1894        assert!(Graphic::from_packed(0, 1, &[]).is_err());
1895        assert!(Graphic::from_packed(1, 0, &[]).is_err());
1896        assert!(Graphic::from_packed(131, 64, &[0; 17 * 63]).is_err());
1897        assert!(Graphic::from_packed(4096, 4097, &[]).is_err());
1898        let bad = |rows: &str| {
1899            Graphic {
1900                width: 4,
1901                height: 1,
1902                rows: rows.into(),
1903            }
1904            .to_grob()
1905            .is_err()
1906        };
1907        assert!(bad("F"), "short");
1908        assert!(bad("F00"), "long");
1909        assert!(bad("G0"), "not hex");
1910        assert!(bad("é"), "not ASCII");
1911    }
1912
1913    #[test]
1914    fn a_grob_that_does_not_add_up_has_no_picture() {
1915        let mut n = grob(&["#.", ".#"]);
1916        n[5] ^= 1;
1917        assert_eq!(graphic(&n), None, "a wrong length");
1918        let n = grob(&["#.", ".#"]);
1919        assert_eq!(graphic(&n[..n.len() - 1]), None, "cut short");
1920        assert_eq!(graphic(&[0xE, 0x1, 0xB, 0x2, 0x1]), None, "no fields");
1921        // No pixels (`#0 #0 BLANK`, `#0 #5 BLANK`): an unknown with its
1922        // nibbles, not a picture the page cannot draw.
1923        assert_eq!(graphic(&grob(&[])), None, "0 by 0");
1924        assert_eq!(graphic(&grob(&[""; 5])), None, "0 wide, 5 high");
1925        assert!(matches!(
1926            decode(&grob(&[]), &NoMemory),
1927            Ok(Object::Unknown { graphic: None, .. })
1928        ));
1929        // Wider than the cap allows: no picture, still an object.
1930        let mut big = vec![0xE, 0x1, 0xB, 0x2, 0x0];
1931        for v in [0u32, 0x1000, 0x1001] {
1932            big.extend((0..5).map(|i| ((v >> (4 * i)) & 0xF) as u8));
1933        }
1934        assert_eq!(graphic(&big), None, "4096 by 4097 pixels");
1935        let mut trailing = grob(&["#"]);
1936        trailing.push(0);
1937        assert_eq!(
1938            graphic(&trailing).map(|g| g.rows),
1939            Some("80".into()),
1940            "a trailing nibble is not the GROB's"
1941        );
1942    }
1943}