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