Skip to main content

saturnus_objects/
object.rs

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