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 => self.secondary(n, body, depth)?.0,
1056            ObjectType::Algebraic => self.symbolic(n, body, depth)?.0,
1057            ObjectType::XlibName => element_object(self.xlib(n, body)?),
1058            _ => unknown(prolog, &n[at..at + size]),
1059        };
1060        Ok((obj, size))
1061    }
1062
1063    /// The algebraic whose body starts at `body`, and its elements.
1064    fn symbolic(&self, n: &[u8], body: usize, depth: usize) -> Result<(Object, Vec<Element>)> {
1065        let elements = self.elements(n, body, depth)?;
1066        let object = Object::Algebraic {
1067            source: self
1068                .names
1069                .and_then(|_| decompile::algebraic(&elements, &self.settings)),
1070        };
1071        Ok((object, elements))
1072    }
1073
1074    /// The program whose body starts at `body`, and its elements.
1075    fn secondary(&self, n: &[u8], body: usize, depth: usize) -> Result<(Object, Vec<Element>)> {
1076        let elements = self.elements(n, body, depth)?;
1077        let object = Object::Program {
1078            source: self
1079                .names
1080                .map(|_| decompile::program(&elements, &self.settings)),
1081        };
1082        Ok((object, elements))
1083    }
1084
1085    /// Composite elements from `at` up to SEMI.
1086    fn elements(&self, n: &[u8], mut at: usize, depth: usize) -> Result<Vec<Element>> {
1087        let mut items = Vec::new();
1088        while field(n, at, 5)? != SEMI {
1089            let (obj, size) = self.element(n, at, depth)?;
1090            items.push(obj);
1091            at += size;
1092        }
1093        Ok(items)
1094    }
1095
1096    /// An embedded object or a 5-nibble ROM pointer at `at`.
1097    fn element(&self, n: &[u8], at: usize, depth: usize) -> Result<(Element, usize)> {
1098        let p = field(n, at, 5)?;
1099        match ObjectType::from_prolog(p) {
1100            Some(ObjectType::XlibName) => {
1101                self.charge(1)?;
1102                Ok((self.xlib(n, at + 5)?, 11))
1103            }
1104            Some(ObjectType::SystemBinary) => {
1105                self.charge(1)?;
1106                Ok((Element::SystemBinary(field(n, at + 5, 5)?), 10))
1107            }
1108            Some(ObjectType::Algebraic) if depth < MAX_DEPTH => {
1109                self.charge(1)?;
1110                let size = object_size(n, at)?;
1111                let (object, elements) = self.symbolic(n, at + 5, depth + 1)?;
1112                Ok((Element::Symbolic { object, elements }, size))
1113            }
1114            Some(ObjectType::Program) if depth < MAX_DEPTH => {
1115                self.charge(1)?;
1116                let size = object_size(n, at)?;
1117                let (object, elements) = self.secondary(n, at + 5, depth + 1)?;
1118                Ok((Element::Secondary { object, elements }, size))
1119            }
1120            Some(_) => {
1121                let (obj, size) = self.object(n, at, depth + 1)?;
1122                Ok((Element::Object(obj), size))
1123            }
1124            None => Ok((self.pointer(p, depth + 1)?, 5)),
1125        }
1126    }
1127
1128    /// The command an XLIB body (library, command) at `at` names.
1129    fn xlib(&self, n: &[u8], at: usize) -> Result<Element> {
1130        let lib = field(n, at, 3)?;
1131        let cmd = field(n, at + 3, 3)?;
1132        let (lib, cmd) = (lib as u16, cmd as u16);
1133        let info = self.names.and_then(|t| t.xlib(lib, cmd));
1134        Ok(Element::Command {
1135            name: info.and_then(|i| i.name).map(str::to_string),
1136            address: None,
1137            xlib: Some((lib, cmd)),
1138            arity: info.and_then(|i| i.arity),
1139            silent: false,
1140            syntax: None,
1141        })
1142    }
1143
1144    /// The element a ROM pointer stands for: a command (named when the
1145    /// table knows it), a unit operator, a system binary, or the data
1146    /// object it points to.
1147    fn pointer(&self, p: u32, depth: usize) -> Result<Element> {
1148        if let Some(t) = self.names {
1149            if let Some(info) = t.command_at(p, self.mem) {
1150                self.charge(1)?;
1151                return Ok(Element::Command {
1152                    name: info.name.map(str::to_string),
1153                    address: Some(p),
1154                    xlib: Some((info.library, info.number)),
1155                    arity: info.arity,
1156                    silent: info.silent,
1157                    syntax: info.syntax,
1158                });
1159            }
1160            if let Some(m) = t.unit_markers() {
1161                let op = [
1162                    (m.times, UnitOp::Times),
1163                    (m.divide, UnitOp::Divide),
1164                    (m.power, UnitOp::Power),
1165                    (m.prefix, UnitOp::Prefix),
1166                    (m.end, UnitOp::End),
1167                ]
1168                .into_iter()
1169                .find_map(|(a, op)| (a == p).then_some(op));
1170                if let Some(op) = op {
1171                    self.charge(1)?;
1172                    return Ok(Element::Unit(op));
1173                }
1174            }
1175        }
1176        let read = |at: u32, width: u32| -> Option<u32> {
1177            (0..width).rev().try_fold(0u32, |v, i| {
1178                Some((v << 4) | u32::from(self.mem.nibble(at.wrapping_add(i))?))
1179            })
1180        };
1181        let command = Element::Command {
1182            name: None,
1183            address: Some(p),
1184            xlib: None,
1185            arity: None,
1186            silent: false,
1187            syntax: None,
1188        };
1189        match read(p, 5).and_then(ObjectType::from_prolog) {
1190            // Unreadable, a primitive (its prolog is its own code), code
1191            // or a program in ROM: a command, never decoded.
1192            None | Some(ObjectType::Program | ObjectType::Code) => {
1193                self.charge(1)?;
1194                Ok(command)
1195            }
1196            Some(ObjectType::SystemBinary) => {
1197                self.charge(1)?;
1198                Ok(read(p + 5, 5).map_or(command, Element::SystemBinary))
1199            }
1200            Some(ObjectType::XlibName) => {
1201                self.charge(1)?;
1202                let body: Option<Vec<u8>> = (0..6).map(|i| self.mem.nibble(p + 5 + i)).collect();
1203                match body {
1204                    Some(b) => self.xlib(&b, 0),
1205                    None => Ok(command),
1206                }
1207            }
1208            Some(_) => Ok(Element::Object(self.rom_object(p, depth)?)),
1209        }
1210    }
1211
1212    /// The object at `addr` in memory (a ROM pointer's target, a stack
1213    /// level), or a command.
1214    fn rom_object(&self, addr: u32, depth: usize) -> Result<Object> {
1215        if let Some((obj, cost)) = self.seen.borrow().get(&(addr, depth)) {
1216            self.charge(cost.objects)?;
1217            self.charge_cloned(cost.nibbles)?;
1218            return Ok(obj.clone());
1219        }
1220        let before = self.left();
1221        let obj = self.read_object(addr, depth)?;
1222        let after = self.left();
1223        let cost = Cost {
1224            objects: before.objects - after.objects,
1225            nibbles: before.nibbles - after.nibbles,
1226        };
1227        self.seen
1228            .borrow_mut()
1229            .insert((addr, depth), (obj.clone(), cost));
1230        Ok(obj)
1231    }
1232
1233    /// [`Decoder::rom_object`] without the cache.
1234    fn read_object(&self, addr: u32, depth: usize) -> Result<Object> {
1235        // As many nibbles as are readable, up to `len`.
1236        let read = |len: usize| -> Vec<u8> {
1237            (0..len)
1238                .map_while(|i| {
1239                    u32::try_from(i)
1240                        .ok()
1241                        .and_then(|i| self.mem.nibble(addr.wrapping_add(i)))
1242                })
1243                .collect()
1244        };
1245        let head = read(5);
1246        if read_field(&head, 0, 5)
1247            .and_then(ObjectType::from_prolog)
1248            .is_none()
1249        {
1250            // Unreadable, a primitive (its prolog is its own code) or a
1251            // word in ROM.
1252            self.charge(1)?;
1253            return Ok(Object::Command {
1254                name: None,
1255                address: Some(addr),
1256                library: None,
1257                command: None,
1258                source: None,
1259            });
1260        }
1261        let mut len = 64;
1262        loop {
1263            let nib = read(len);
1264            match object_size(&nib, 0) {
1265                Ok(size) => {
1266                    self.charge_nibbles(size)?;
1267                    return match self.object(&nib, 0, depth) {
1268                        Ok((obj, _)) => Ok(obj),
1269                        Err(e) if e.is_budget() => Err(e),
1270                        Err(e) => Err(e.in_memory(addr)),
1271                    };
1272                }
1273                Err(_) if nib.len() == len && len < MAX_MEMORY_OBJECT => len *= 8,
1274                Err(e) => return Err(e.in_memory(addr)),
1275            }
1276        }
1277    }
1278
1279    /// A 49G symbolic matrix: its elements, or its rows (each a symbolic
1280    /// vector), as an array.
1281    fn symbolic_matrix(&self, n: &[u8], body: usize, depth: usize) -> Result<Object> {
1282        let items: Vec<Object> = self
1283            .elements(n, body, depth)?
1284            .into_iter()
1285            .map(element_object)
1286            .collect();
1287        let rows = !items.is_empty()
1288            && items
1289                .iter()
1290                .all(|o| matches!(o, Object::Array { dims, .. } if dims.len() == 1));
1291        if !rows {
1292            return Ok(Object::Array {
1293                dims: vec![items.len()],
1294                items: items
1295                    .into_iter()
1296                    .map(|o| ArrayItem::Item(Box::new(o)))
1297                    .collect(),
1298            });
1299        }
1300        let cols = match items.first() {
1301            Some(Object::Array { dims, .. }) => dims[0],
1302            _ => 0,
1303        };
1304        Ok(Object::Array {
1305            dims: vec![items.len(), cols],
1306            items: items
1307                .into_iter()
1308                .map(|o| match o {
1309                    Object::Array { items, .. } => ArrayItem::Row(items),
1310                    o => ArrayItem::Item(Box::new(o)),
1311                })
1312                .collect(),
1313        })
1314    }
1315
1316    /// A real or complex array.
1317    fn array(&self, n: &[u8], at: usize, size: usize) -> Result<Object> {
1318        let body = at + 5;
1319        let elem = field(n, body + 5, 5)?;
1320        let ndims = usize_field(n, body + 10, 5)?;
1321        if ndims > MAX_ARRAY_DIMS {
1322            return Err(DecodeError::new(body + 10, Reason::ArrayDimensions(ndims)));
1323        }
1324        let bad = || DecodeError::new(at, Reason::ArrayLength);
1325        // Every count below comes from the object: check it against the
1326        // object's size before using it (no allocation from a bad count).
1327        let start = ndims
1328            .checked_mul(5)
1329            .and_then(|d| d.checked_add(body + 15))
1330            .filter(|&s| s <= at + size)
1331            .ok_or_else(bad)?;
1332        let dims = (0..ndims)
1333            .map(|i| usize_field(n, body + 15 + 5 * i, 5))
1334            .collect::<Result<Vec<usize>>>()?;
1335        let width = match ObjectType::from_prolog(elem) {
1336            Some(ObjectType::Real) => 16,
1337            Some(ObjectType::Complex) => 32,
1338            _ => return Ok(unknown(field(n, at, 5)?, &n[at..at + size])),
1339        };
1340        // Rows at each level and the element count must fit the object
1341        // (an empty dimension keeps the elements at 0 but not the rows).
1342        let mut count: usize = 1;
1343        for d in &dims {
1344            count = count
1345                .checked_mul(*d)
1346                .filter(|&c| c <= size)
1347                .ok_or_else(bad)?;
1348        }
1349        let end = count
1350            .checked_mul(width)
1351            .and_then(|b| b.checked_add(start))
1352            .ok_or_else(bad)?;
1353        if ndims == 0 || end > at + size {
1354            return Err(bad());
1355        }
1356        self.charge(count)?;
1357        let mut flat = Vec::with_capacity(count);
1358        for i in 0..count {
1359            let p = start + i * width;
1360            flat.push(if width == 16 {
1361                Object::Real { value: real(n, p)? }
1362            } else {
1363                Object::Complex {
1364                    re: real(n, p)?,
1365                    im: real(n, p + 16)?,
1366                }
1367            });
1368        }
1369        Ok(Object::Array {
1370            items: nest(&dims, &mut flat.into_iter()),
1371            dims,
1372        })
1373    }
1374}
1375
1376/// Objects and nibbles, taken from or left in a decode's budgets.
1377#[derive(Clone, Copy, Debug)]
1378struct Cost {
1379    objects: usize,
1380    nibbles: usize,
1381}
1382
1383/// An element of a list or tagged object as an object.
1384fn element_object(e: Element) -> Object {
1385    match e {
1386        Element::Object(o)
1387        | Element::Symbolic { object: o, .. }
1388        | Element::Secondary { object: o, .. } => o,
1389        Element::Command {
1390            name,
1391            address,
1392            xlib,
1393            ..
1394        } => Object::Command {
1395            name,
1396            address,
1397            library: xlib.map(|x| x.0),
1398            command: xlib.map(|x| x.1),
1399            source: None,
1400        },
1401        Element::SystemBinary(v) => Object::Unknown {
1402            prolog: format!("{:05X}", ObjectType::SystemBinary.prolog()),
1403            kind: Some(ObjectType::SystemBinary.name().to_string()),
1404            nibbles: 10,
1405            hex: format!("{:05X}", ObjectType::SystemBinary.prolog())
1406                .chars()
1407                .rev()
1408                .chain(format!("{v:05X}").chars().rev())
1409                .collect(),
1410            truncated: false,
1411            source: None,
1412            graphic: None,
1413        },
1414        // A unit operator is an empty list in ROM.
1415        Element::Unit(_) => Object::List { items: Vec::new() },
1416    }
1417}
1418
1419/// Elements in lexicographic index order, nested by dimension (at most
1420/// [`MAX_ARRAY_DIMS`] levels deep).
1421fn nest(dims: &[usize], flat: &mut impl Iterator<Item = Object>) -> Vec<ArrayItem> {
1422    match dims {
1423        [] => Vec::new(),
1424        [n] => flat
1425            .take(*n)
1426            .map(|o| ArrayItem::Item(Box::new(o)))
1427            .collect(),
1428        [n, rest @ ..] => (0..*n).map(|_| ArrayItem::Row(nest(rest, flat))).collect(),
1429    }
1430}
1431
1432impl Object {
1433    /// Whether this object or one inside it needs text from the ASCII
1434    /// transfer (the transfer layer fills them in).
1435    pub fn needs_source(&self) -> bool {
1436        match self {
1437            Object::Program { source } | Object::Algebraic { source } => source.is_none(),
1438            Object::Command { name, source, .. } => name.is_none() && source.is_none(),
1439            Object::Unit { unit, .. } => unit.is_none(),
1440            Object::Unknown { source, .. } => source.is_none(),
1441            Object::List { items } => items.iter().any(Object::needs_source),
1442            Object::Tagged { object, .. } => object.needs_source(),
1443            _ => false,
1444        }
1445    }
1446
1447    /// Set the base (and display text) of every binary integer inside.
1448    pub fn set_base(&mut self, b: Base) {
1449        match self {
1450            Object::Binary { value, base, text } => {
1451                *base = Some(b);
1452                *text = Some(b.format(*value));
1453            }
1454            Object::List { items } => items.iter_mut().for_each(|o| o.set_base(b)),
1455            Object::Tagged { object, .. } => object.set_base(b),
1456            _ => {}
1457        }
1458    }
1459
1460    /// Whether a binary integer is inside.
1461    pub fn has_binary(&self) -> bool {
1462        match self {
1463            Object::Binary { .. } => true,
1464            Object::List { items } => items.iter().any(Object::has_binary),
1465            Object::Tagged { object, .. } => object.has_binary(),
1466            _ => false,
1467        }
1468    }
1469}
1470#[cfg(test)]
1471mod tests {
1472    use super::*;
1473    use serde_json::json;
1474
1475    /// Nibbles from a hex string in memory order.
1476    fn nib(s: &str) -> Vec<u8> {
1477        s.chars().map(|c| c.to_digit(16).unwrap() as u8).collect()
1478    }
1479
1480    fn dec(s: &str) -> Object {
1481        decode(&nib(s), &NoMemory).unwrap()
1482    }
1483
1484    fn real(s: &str) -> Real {
1485        Real::parse(s).unwrap()
1486    }
1487
1488    /// An unnamed command at `address`.
1489    fn cmd(address: u32) -> Object {
1490        Object::Command {
1491            name: None,
1492            address: Some(address),
1493            library: None,
1494            command: None,
1495            source: None,
1496        }
1497    }
1498
1499    fn put_field(out: &mut Vec<u8>, value: u64, width: usize) {
1500        out.extend((0..width).map(|i| ((value >> (4 * i)) & 0xF) as u8));
1501    }
1502
1503    // Byte-level fixtures: GETs from the 48SX ROM J and the 49G (2009 ROM)
1504    // in saturnus, 2026-10-05; wiki: protocols/hp-object-format.
1505
1506    #[test]
1507    fn reals_decode_exactly() {
1508        // 8.72653549837E-3 (SIN 0.5 in degrees).
1509        let o = dec("339207997389453562780");
1510        assert_eq!(
1511            o,
1512            Object::Real {
1513                value: real("8.72653549837E-3")
1514            }
1515        );
1516        assert_eq!(
1517            serde_json::to_value(&o).unwrap(),
1518            json!({"type": "real", "value": 0.00872653549837})
1519        );
1520        // -1.5E-300 and 0.
1521        let o = dec("339200070000000000519");
1522        let Object::Real { value } = o else { panic!() };
1523        assert_eq!(value.to_source(), "-1.5E-300");
1524        assert!(value.negative);
1525        assert_eq!(
1526            dec("339200000000000000000"),
1527            Object::Real { value: Real::ZERO }
1528        );
1529    }
1530
1531    #[test]
1532    fn real_text_round_trips_and_rounds() {
1533        for s in [
1534            "4.79425538604E-1",
1535            "5.",
1536            "-1.5E-300",
1537            "9.99999999999E499",
1538            "1.E-499",
1539            "0.",
1540        ] {
1541            let r = real(s);
1542            assert_eq!(r.to_source(), s);
1543            assert_eq!(Real::from_body(&r.to_body()).unwrap(), r);
1544        }
1545        assert_eq!(real("0.479425538604").to_source(), "4.79425538604E-1");
1546        assert_eq!(real(".5").to_source(), "5.E-1");
1547        assert_eq!(real("123").to_source(), "1.23E2");
1548        // 13 digits round half away from zero, with carry.
1549        assert_eq!(real("1.234567890125").to_source(), "1.23456789013");
1550        assert_eq!(real("9.999999999995").to_source(), "1.E1");
1551        assert_eq!(real("-0.000").to_source(), "0.");
1552        assert!(Real::parse("1E500").is_err());
1553        assert!(Real::parse("1.2.3").is_err());
1554        assert!(Real::parse("").is_err());
1555        assert!(Real::parse("e5").is_err());
1556        assert_eq!(
1557            Real::from_f64(0.479425538604).unwrap(),
1558            real("4.79425538604E-1")
1559        );
1560    }
1561
1562    #[test]
1563    fn reals_beyond_f64_serialize_as_text() {
1564        let r = Object::Real {
1565            value: real("1.5E-400"),
1566        };
1567        assert_eq!(
1568            serde_json::to_value(&r).unwrap(),
1569            json!({"type": "real", "value": "1.5E-400"})
1570        );
1571        let back: Object =
1572            serde_json::from_value(json!({"type": "real", "value": "1.5E-400"})).unwrap();
1573        assert_eq!(back, r);
1574        let five: Object = serde_json::from_value(json!({"type": "real", "value": 5})).unwrap();
1575        assert_eq!(five, Object::Real { value: real("5") });
1576    }
1577
1578    #[test]
1579    fn complex_string_binary() {
1580        assert_eq!(
1581            dec("7792000000000000000100000000000000029"),
1582            Object::Complex {
1583                re: real("1"),
1584                im: real("-2")
1585            }
1586        );
1587        // "Hi «x»": « and » are HP bytes 0xAB and 0xBB.
1588        assert_eq!(
1589            dec("C2A2011000849602BA87BB"),
1590            Object::String {
1591                value: "Hi «x»".into()
1592            }
1593        );
1594        let mut b = dec("E4A2051000FF00000000000000");
1595        assert_eq!(
1596            b,
1597            Object::Binary {
1598                value: 255,
1599                base: None,
1600                text: None
1601            }
1602        );
1603        b.set_base(Base::Hex);
1604        assert_eq!(
1605            serde_json::to_value(&b).unwrap(),
1606            json!({"type": "binary", "value": 255, "base": "hex", "text": "# FFh"})
1607        );
1608        assert_eq!(Base::from_display("# 255d"), Some(Base::Dec));
1609        assert_eq!(Base::from_display("42."), None);
1610    }
1611
1612    #[test]
1613    fn integers_49g() {
1614        assert_eq!(
1615            dec("416207000050"),
1616            Object::Integer {
1617                value: Integer::parse("5").unwrap()
1618            }
1619        );
1620        let big = dec("416209100098765432109876543219");
1621        assert_eq!(
1622            big,
1623            Object::Integer {
1624                value: Integer::parse("-1234567890123456789").unwrap()
1625            }
1626        );
1627        assert_eq!(
1628            serde_json::to_value(&big).unwrap(),
1629            json!({"type": "integer", "value": "-1234567890123456789"})
1630        );
1631        assert_eq!(
1632            dec("416206000000"),
1633            Object::Integer {
1634                value: Integer::parse("0").unwrap()
1635            }
1636        );
1637        assert_eq!(
1638            serde_json::to_value(dec("416207000050")).unwrap(),
1639            json!({"type": "integer", "value": 5})
1640        );
1641    }
1642
1643    #[test]
1644    fn names_tagged_and_lists_with_rom_pointers() {
1645        // 48SX { 1 2. "s" X { 5 } # 2Ah }: 1, 2 and 5 are ROM pointers.
1646        let list = dec(
1647            "47A209C2A2ED2A2C2A20700003784E20108547A20D13A2B2130E4A2051000A200000000000000B2130",
1648        );
1649        let Object::List { items } = &list else {
1650            panic!("{list:?}")
1651        };
1652        assert_eq!(items.len(), 6);
1653        assert_eq!(items[0], cmd(0x2A2C9));
1654        assert_eq!(items[2], Object::String { value: "s".into() });
1655        assert_eq!(items[3], Object::Name { value: "X".into() });
1656        assert_eq!(
1657            items[4],
1658            Object::List {
1659                items: vec![cmd(0x2A31D)]
1660            }
1661        );
1662        assert_eq!(
1663            items[5],
1664            Object::Binary {
1665                value: 42,
1666                base: None,
1667                text: None
1668            }
1669        );
1670        // { :T:5 } with the 5 as a pointer.
1671        let t = dec("47A20CFA201045D13A2B2130");
1672        assert_eq!(
1673            t,
1674            Object::List {
1675                items: vec![Object::Tagged {
1676                    tag: "T".into(),
1677                    object: Box::new(cmd(0x2A31D))
1678                }]
1679            }
1680        );
1681    }
1682
1683    /// A memory holding the real 1 at #2A2C9 and 5 at #2A31D (48SX ROM J).
1684    struct Rom;
1685    impl Memory for Rom {
1686        fn nibble(&self, addr: u32) -> Option<u8> {
1687            let (base, obj) = match addr {
1688                0x2A2C9..=0x2A2DD => (0x2A2C9, "339200000000000000010"),
1689                0x2A31D..=0x2A331 => (0x2A31D, "339200000000000000050"),
1690                // A primitive: the "prolog" points past itself.
1691                0x10000..=0x10004 => (0x10000, "50001"),
1692                _ => return None,
1693            };
1694            nib(obj).get((addr - base) as usize).copied()
1695        }
1696    }
1697
1698    #[test]
1699    fn rom_pointers_resolve_through_memory() {
1700        let list = decode(&nib("47A209C2A2D13A200001B2130"), &Rom).unwrap();
1701        assert_eq!(
1702            list,
1703            Object::List {
1704                items: vec![
1705                    Object::Real { value: real("1") },
1706                    Object::Real { value: real("5") },
1707                    cmd(0x10000),
1708                ]
1709            }
1710        );
1711    }
1712
1713    #[test]
1714    fn arrays() {
1715        let m = dec(
1716            "8E920950003392020000200002000000000000000000100000000000000020000000000000003000000000000000400",
1717        );
1718        let item = |s: &str| ArrayItem::Item(Box::new(Object::Real { value: real(s) }));
1719        assert_eq!(
1720            m,
1721            Object::Array {
1722                dims: vec![2, 2],
1723                items: vec![
1724                    ArrayItem::Row(vec![item("1"), item("2")]),
1725                    ArrayItem::Row(vec![item("3"), item("4")])
1726                ],
1727            }
1728        );
1729        assert_eq!(
1730            serde_json::to_value(&m).unwrap(),
1731            json!({"type": "array", "dims": [2, 2], "items": [
1732                [{"type": "real", "value": 1.0}, {"type": "real", "value": 2.0}],
1733                [{"type": "real", "value": 3.0}, {"type": "real", "value": 4.0}]]})
1734        );
1735        let v = dec(
1736            "8E9204500077920100002000000000000000000100000000000000020000000000000003000000000000000400",
1737        );
1738        let Object::Array { dims, items } = v else {
1739            panic!()
1740        };
1741        assert_eq!(dims, vec![2]);
1742        assert_eq!(
1743            items[1],
1744            ArrayItem::Item(Box::new(Object::Complex {
1745                re: real("3"),
1746                im: real("4")
1747            }))
1748        );
1749    }
1750
1751    #[test]
1752    fn array_dimensions_cannot_overflow() {
1753        // Length #00023, reals, 4 dimensions 0x80000 x 0x80000 x 0x80000
1754        // x 8: the product is 2^60 and times 16 wraps to 0.
1755        let mut o = nib("8E920");
1756        put_field(&mut o, 0x23, 5);
1757        put_field(&mut o, 0x02933, 5);
1758        put_field(&mut o, 4, 5);
1759        for d in [0x80000u64, 0x80000, 0x80000, 8] {
1760            put_field(&mut o, d, 5);
1761        }
1762        assert_eq!(o.len(), 40);
1763        let Ok(Object::Unknown { prolog, .. }) = decode(&o, &NoMemory) else {
1764            panic!("decoded");
1765        };
1766        assert_eq!(prolog, "029E8");
1767        // Empty arrays stay decodable; huge row counts with a zero do not.
1768        let mut e = nib("8E920");
1769        put_field(&mut e, 25, 5);
1770        put_field(&mut e, 0x02933, 5);
1771        put_field(&mut e, 2, 5);
1772        put_field(&mut e, 3, 5);
1773        put_field(&mut e, 0, 5);
1774        assert_eq!(
1775            decode(&e, &NoMemory).unwrap(),
1776            Object::Array {
1777                dims: vec![3, 0],
1778                items: vec![
1779                    ArrayItem::Row(vec![]),
1780                    ArrayItem::Row(vec![]),
1781                    ArrayItem::Row(vec![])
1782                ],
1783            }
1784        );
1785        let mut z = nib("8E920");
1786        put_field(&mut z, 30, 5);
1787        put_field(&mut z, 0x02933, 5);
1788        put_field(&mut z, 3, 5);
1789        for d in [0x80000u64, 0x80000, 0] {
1790            put_field(&mut z, d, 5);
1791        }
1792        assert!(matches!(
1793            decode(&z, &NoMemory).unwrap(),
1794            Object::Unknown { .. }
1795        ));
1796    }
1797
1798    /// An array of `ndims` dimensions of 1 holding one real.
1799    fn dims_array(ndims: usize) -> Vec<u8> {
1800        let mut o = nib("8E920");
1801        put_field(&mut o, (15 + 5 * ndims + 16) as u64, 5);
1802        put_field(&mut o, 0x02933, 5);
1803        put_field(&mut o, ndims as u64, 5);
1804        for _ in 0..ndims {
1805            put_field(&mut o, 1, 5);
1806        }
1807        o.extend(nib("0000000000000010"));
1808        o
1809    }
1810
1811    #[test]
1812    fn arrays_have_at_most_two_dimensions() {
1813        let item = ArrayItem::Item(Box::new(Object::Real { value: real("1") }));
1814        assert_eq!(
1815            dec(&hex(&dims_array(2))),
1816            Object::Array {
1817                dims: vec![1, 1],
1818                items: vec![ArrayItem::Row(vec![item])],
1819            }
1820        );
1821        // 12000 dimensions nested the rows 12000 deep (a stack overflow
1822        // that aborted the process); now any third one is refused.
1823        for ndims in [3, 12_000] {
1824            let Ok(Object::Unknown { prolog, .. }) = decode(&dims_array(ndims), &NoMemory) else {
1825                panic!("{ndims} dimensions decoded");
1826            };
1827            assert_eq!(prolog, "029E8");
1828            let mut list = nib("47A20");
1829            list.extend(dims_array(ndims));
1830            list.extend(nib("B2130"));
1831            let e = Reader::new(&NoMemory)
1832                .decoder
1833                .object(&list, 0, 0)
1834                .unwrap_err();
1835            assert!(e.to_string().contains("dimensions"), "{e:#}");
1836        }
1837    }
1838
1839    /// A GROB's nibbles as the calculator stores them (wiki:
1840    /// protocols/hp-object-format): the prolog, the length, the height,
1841    /// the width, low nibble first, then each row padded to whole bytes,
1842    /// the leftmost pixel in a nibble's least significant bit.
1843    fn grob(rows: &[&str]) -> Vec<u8> {
1844        let height = rows.len();
1845        let width = rows.first().map_or(0, |r| r.len());
1846        let row_nibbles = width.div_ceil(8) * 2;
1847        let mut n = Vec::new();
1848        let put = |v: usize, n: &mut Vec<u8>| {
1849            for i in 0..5 {
1850                n.push(((v >> (4 * i)) & 0xF) as u8);
1851            }
1852        };
1853        put(0x02B1E, &mut n);
1854        put(15 + row_nibbles * height, &mut n);
1855        put(height, &mut n);
1856        put(width, &mut n);
1857        for r in rows {
1858            let mut row = vec![0u8; row_nibbles];
1859            for (x, c) in r.chars().enumerate() {
1860                if c == '#' {
1861                    row[x / 4] |= 1 << (x % 4);
1862                }
1863            }
1864            n.extend(row);
1865        }
1866        n
1867    }
1868
1869    #[test]
1870    fn a_grob_is_pictured_row_by_row_leftmost_pixel_first() {
1871        let g = graphic(&grob(&["#"])).unwrap();
1872        assert_eq!((g.width, g.height, g.rows.as_str()), (1, 1, "80"));
1873        // An odd width: 5 pixels in one byte per row.
1874        let g = graphic(&grob(&["#.#.#", ".#.#.", "....#"])).unwrap();
1875        assert_eq!((g.width, g.height, g.rows.as_str()), (5, 3, "A85008"));
1876        // Nine pixels: two bytes a row, the ninth in the second's top bit.
1877        let g = graphic(&grob(&["........#", "#........"])).unwrap();
1878        assert_eq!(g.rows, "00808000");
1879        // The screen's size: 34 nibbles a row, length #0088F (the FAQ's).
1880        let mut row = ".".repeat(131);
1881        row.replace_range(130..131, "#");
1882        let screen: Vec<&str> = std::iter::repeat_n(row.as_str(), 64).collect();
1883        let n = grob(&screen);
1884        assert_eq!(n.len(), 2196);
1885        assert_eq!(field(&n, 5, 5).unwrap(), 0x88F);
1886        let g = graphic(&n).unwrap();
1887        assert_eq!((g.width, g.height), (131, 64));
1888        assert_eq!(&g.rows[..34], "0000000000000000000000000000000020");
1889        // As an object: an unknown one with its picture.
1890        let Object::Unknown {
1891            kind,
1892            graphic: Some(p),
1893            ..
1894        } = decode(&grob(&["##"]), &NoMemory).unwrap()
1895        else {
1896            panic!("not a pictured unknown");
1897        };
1898        assert_eq!((kind.as_deref(), p.rows.as_str()), (Some("Graphic"), "C0"));
1899    }
1900
1901    #[test]
1902    fn a_picture_encodes_as_the_grob_it_came_from() {
1903        for rows in [
1904            vec!["#"],
1905            vec!["#.#.#", ".#.#.", "....#"],
1906            vec!["........#", "#........"],
1907            vec!["#.......", "........"],
1908        ] {
1909            let n = grob(&rows);
1910            let g = graphic(&n).unwrap();
1911            assert_eq!(g.to_grob().unwrap(), n, "{rows:?}");
1912            assert_eq!(graphic(&g.to_grob().unwrap()), Some(g));
1913        }
1914        // The screen: 131 by 64, 2196 nibbles, length #0088F.
1915        let mut packed = vec![0u8; 17 * 64];
1916        packed[16] = 0x20;
1917        packed[17 * 63] = 0x80;
1918        let g = Graphic::from_packed(131, 64, &packed).unwrap();
1919        let n = g.to_grob().unwrap();
1920        assert_eq!(n.len(), 2196);
1921        assert_eq!(field(&n, 5, 5).unwrap(), 0x88F);
1922        assert_eq!(
1923            (field(&n, 10, 5).unwrap(), field(&n, 15, 5).unwrap()),
1924            (64, 131)
1925        );
1926        assert_eq!(n[20 + 32], 0x4, "pixel 130 of row 0");
1927        assert_eq!(n[20 + 34 * 63], 0x1, "pixel 0 of row 63");
1928        assert_eq!(graphic(&n), Some(g));
1929        // The 42S's 131 by 16.
1930        let g = Graphic::from_packed(131, 16, &[0xFF; 17 * 16]).unwrap();
1931        let n = g.to_grob().unwrap();
1932        assert_eq!(n.len(), 20 + 34 * 16);
1933        assert_eq!(graphic(&n), Some(g));
1934    }
1935
1936    #[test]
1937    fn packed_rows_lose_the_bits_beyond_the_width() {
1938        let g = Graphic::from_packed(5, 2, &[0xFF, 0x07]).unwrap();
1939        assert_eq!(g.rows, "F800");
1940        assert_eq!(graphic(&g.to_grob().unwrap()), Some(g));
1941        let g = Graphic::from_packed(16, 1, &[0xAB, 0xCD]).unwrap();
1942        assert_eq!(g.rows, "ABCD");
1943    }
1944
1945    #[test]
1946    fn a_picture_of_no_or_the_wrong_size_is_refused() {
1947        assert!(Graphic::from_packed(0, 1, &[]).is_err());
1948        assert!(Graphic::from_packed(1, 0, &[]).is_err());
1949        assert!(Graphic::from_packed(131, 64, &[0; 17 * 63]).is_err());
1950        assert!(Graphic::from_packed(4096, 4097, &[]).is_err());
1951        let bad = |rows: &str| {
1952            Graphic {
1953                width: 4,
1954                height: 1,
1955                rows: rows.into(),
1956            }
1957            .to_grob()
1958            .is_err()
1959        };
1960        assert!(bad("F"), "short");
1961        assert!(bad("F00"), "long");
1962        assert!(bad("G0"), "not hex");
1963        assert!(bad("é"), "not ASCII");
1964    }
1965
1966    #[test]
1967    fn a_grob_that_does_not_add_up_has_no_picture() {
1968        let mut n = grob(&["#.", ".#"]);
1969        n[5] ^= 1;
1970        assert_eq!(graphic(&n), None, "a wrong length");
1971        let n = grob(&["#.", ".#"]);
1972        assert_eq!(graphic(&n[..n.len() - 1]), None, "cut short");
1973        assert_eq!(graphic(&[0xE, 0x1, 0xB, 0x2, 0x1]), None, "no fields");
1974        // No pixels (`#0 #0 BLANK`, `#0 #5 BLANK`): an unknown with its
1975        // nibbles, not a picture the page cannot draw.
1976        assert_eq!(graphic(&grob(&[])), None, "0 by 0");
1977        assert_eq!(graphic(&grob(&[""; 5])), None, "0 wide, 5 high");
1978        assert!(matches!(
1979            decode(&grob(&[]), &NoMemory),
1980            Ok(Object::Unknown { graphic: None, .. })
1981        ));
1982        // Wider than the cap allows: no picture, still an object.
1983        let mut big = vec![0xE, 0x1, 0xB, 0x2, 0x0];
1984        for v in [0u32, 0x1000, 0x1001] {
1985            big.extend((0..5).map(|i| ((v >> (4 * i)) & 0xF) as u8));
1986        }
1987        assert_eq!(graphic(&big), None, "4096 by 4097 pixels");
1988        let mut trailing = grob(&["#"]);
1989        trailing.push(0);
1990        assert_eq!(
1991            graphic(&trailing).map(|g| g.rows),
1992            Some("80".into()),
1993            "a trailing nibble is not the GROB's"
1994        );
1995    }
1996}