Skip to main content

saturnus_objects/
decompile.rs

1//! The calculator's text for objects: numbers in the display mode, data
2//! objects as the stack shows them, programs, algebraics (infix) and unit
3//! expressions rebuilt from their elements.
4//!
5//! The rules (number formats, spacing, the operators' precedence and
6//! parentheses) are those the ROM's own decompiler shows, checked against
7//! it by the ROM-gated oracle test (`saturnus-kermit` e2e,
8//! `decompiler_matches_the_rom`); wiki: protocols/rpl-libraries for how
9//! commands are named.
10//!
11//! Text is capped at [`MAX_TEXT`] characters; longer text ends in
12//! [`TRUNCATED`]. The texts [`described`] adds are capped together at
13//! [`MAX_DESCRIBED_TEXT`].
14
15use crate::error::Error;
16
17type Result<T, E = Error> = std::result::Result<T, E>;
18use saturnus_core::Model;
19
20use crate::names::Syntax;
21use crate::object::{ArrayItem, Base, Object, Real};
22use crate::ram::Flags;
23
24/// Longest text produced for one object, in characters.
25pub const MAX_TEXT: usize = 1 << 16;
26/// Appended to text cut at [`MAX_TEXT`].
27pub const TRUNCATED: &str = "…";
28/// Most characters of `text` fields one [`described`] call (one stack
29/// read) adds. Every object inside another carries its own text, so a
30/// string nested 64 levels deep is written 64 times: the decode budget
31/// bounds the objects, this bounds the copies of their text. 4 M
32/// characters, a few MB of JSON; the 49G's whole RAM (256 KB) holds at
33/// most 256 K characters of strings.
34pub const MAX_DESCRIBED_TEXT: usize = 1 << 22;
35
36/// How reals are shown (flags -49 and -50, digits in -45 to -48).
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum NumberFormat {
39    /// Standard: up to 12 digits, no trailing zeros.
40    Std,
41    /// Fixed, with this many decimals.
42    Fix(u8),
43    /// Scientific, with this many decimals in the mantissa.
44    Sci(u8),
45    /// Engineering, with this many digits after the first.
46    Eng(u8),
47}
48
49/// The display settings the text depends on.
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct Settings {
52    /// Real number format.
53    pub format: NumberFormat,
54    /// Comma as the fraction mark (flag -51); the separator in complex
55    /// numbers becomes `;`.
56    pub comma: bool,
57    /// Binary integer base (flags -11, -12).
58    pub base: Base,
59    /// Binary integer word size in bits, 1-64 (flags -5 to -10).
60    pub word_size: u32,
61    /// Show every parenthesis in algebraics (flag -53).
62    pub all_parens: bool,
63    /// Reals with an integer value keep their point (`5.`), as on the 49G,
64    /// whose exact integers show without one (not inside unit objects).
65    pub real_point: bool,
66    /// `^` groups to the right (`A^B^C` is `A^(B^C)`), as on the 49G; on
67    /// the 48 it groups to the left.
68    pub power_right: bool,
69    /// FIX groups the integer digits by three (`1,234.500`; `1.234,500`
70    /// with a comma fraction mark) inside lists, programs, arrays and
71    /// tagged objects too, as the 49G does; both models group a real that
72    /// is a whole stack level. Never inside complex numbers, algebraics or
73    /// units.
74    pub group_digits: bool,
75    /// A tagged object inside a composite shows its tag between colons
76    /// (`:T: 5`, the 48) or with a trailing one only (`T: 5`, the 49G, and
77    /// both models for a whole stack level).
78    pub tag_colon: bool,
79    /// The 49G's algebraic mode (flag -95): the stack shows the history
80    /// echo of an evaluated command line without the backquotes of its
81    /// expression (`:CF(-95)`, in RPN mode `` :`CF(-95)` ``).
82    pub algebraic: bool,
83}
84
85impl Default for Settings {
86    /// The calculator's defaults: STD, period, decimal binaries, 64 bits.
87    fn default() -> Self {
88        Settings {
89            format: NumberFormat::Std,
90            comma: false,
91            base: Base::Dec,
92            word_size: 64,
93            all_parens: false,
94            real_point: false,
95            power_right: false,
96            group_digits: false,
97            tag_colon: true,
98            algebraic: false,
99        }
100    }
101}
102
103impl Settings {
104    /// The defaults of `model`.
105    pub fn standard(model: Model) -> Settings {
106        let hp49 = model == Model::Hp49g;
107        Settings {
108            real_point: hp49,
109            power_right: hp49,
110            tag_colon: !hp49,
111            ..Settings::default()
112        }
113    }
114
115    /// The settings `flags` select on `model`.
116    pub fn from_flags(flags: &Flags, model: Model) -> Settings {
117        let set = |n: i32| flags.get(n).unwrap_or(false);
118        // Bit k of a multi-flag value is flag -(first + k).
119        let value = |first: i32, bits: i32| {
120            (0..bits).fold(0u32, |v, k| v | (u32::from(set(-(first + k))) << k))
121        };
122        let digits = u8::try_from(value(45, 4)).unwrap_or(0);
123        let format = match (set(-49), set(-50)) {
124            (false, false) => NumberFormat::Std,
125            (true, false) => NumberFormat::Fix(digits),
126            (false, true) => NumberFormat::Sci(digits),
127            (true, true) => NumberFormat::Eng(digits),
128        };
129        let hp49 = model == Model::Hp49g;
130        Settings {
131            format,
132            comma: set(-51),
133            base: flags.base(),
134            word_size: value(5, 6) + 1,
135            all_parens: set(-53),
136            real_point: hp49,
137            power_right: hp49,
138            group_digits: hp49,
139            tag_colon: !hp49,
140            algebraic: hp49 && set(-95),
141        }
142    }
143
144    /// These settings for text that is edited and compiled again: every
145    /// digit of a real (STD), binary integers at 64 bits, no digit
146    /// grouping and tags between colons, so that the text compiles back
147    /// to the same object; the fraction mark, base and parentheses stay.
148    pub fn for_editing(self) -> Settings {
149        Settings {
150            format: NumberFormat::Std,
151            word_size: 64,
152            group_digits: false,
153            tag_colon: true,
154            algebraic: false,
155            ..self
156        }
157    }
158
159    /// `value` as a binary integer in the base and word size.
160    pub fn binary(&self, value: u64) -> String {
161        let mask = if self.word_size >= 64 {
162            u64::MAX
163        } else {
164            (1u64 << self.word_size) - 1
165        };
166        self.base.format(value & mask)
167    }
168
169    /// `r` as the calculator shows it.
170    pub fn real(&self, r: &Real) -> String {
171        let s = match self.format {
172            NumberFormat::Std => std_text(r, self.real_point),
173            NumberFormat::Fix(d) if self.group_digits => {
174                group(&fix_text(r, usize::from(d)), self.comma)
175            }
176            NumberFormat::Fix(d) => fix_text(r, usize::from(d)),
177            NumberFormat::Sci(d) => sci_text(r, usize::from(d), 1),
178            NumberFormat::Eng(d) => sci_text(r, usize::from(d), 3),
179        };
180        if self.comma {
181            s.replace('.', ",").replace('\u{1}', ".")
182        } else {
183            s
184        }
185    }
186
187    /// A unit object's number or power: an integer value as an integer
188    /// (`2_m^2` even in FIX), anything else in the display mode; never a
189    /// trailing point or digit groups (oracle-checked: `1.50E0_m^2.50E0`
190    /// in 2 SCI).
191    pub fn unit_number(&self, r: &Real) -> String {
192        let integer = r.exponent >= 0
193            && r.exponent < 12
194            && r.digits[(r.exponent as usize + 1).min(12)..]
195                .iter()
196                .all(|&d| d == 0);
197        let format = if integer || r.digits.iter().all(|&d| d == 0) {
198            NumberFormat::Std
199        } else {
200            self.format
201        };
202        Settings {
203            format,
204            real_point: false,
205            group_digits: false,
206            ..*self
207        }
208        .real(r)
209    }
210
211    /// The settings inside a complex number or an algebraic: no digit
212    /// groups.
213    fn inner(&self) -> Settings {
214        Settings {
215            group_digits: false,
216            ..*self
217        }
218    }
219
220    /// A complex number `(re,im)`.
221    pub fn complex(&self, re: &Real, im: &Real) -> String {
222        let sep = if self.comma { ';' } else { ',' };
223        let s = self.inner();
224        format!("({}{sep}{})", s.real(re), s.real(im))
225    }
226}
227
228/// `text` (a FIX number, period as the mark) with its integer digits
229/// grouped by three; with `comma`, the groups are separated by periods
230/// (the caller swaps the mark afterwards).
231fn group(text: &str, comma: bool) -> String {
232    let sep = if comma { '\u{1}' } else { ',' };
233    let (sign, rest) = match text.strip_prefix('-') {
234        Some(r) => ("-", r),
235        None => ("", text),
236    };
237    if rest.contains('E') {
238        return text.to_string();
239    }
240    let (int, frac) = rest.split_once('.').unwrap_or((rest, ""));
241    let mut out = String::new();
242    for (i, c) in int.chars().enumerate() {
243        if i > 0 && (int.len() - i) % 3 == 0 {
244            out.push(sep);
245        }
246        out.push(c);
247    }
248    format!("{sign}{out}.{frac}")
249}
250
251/// Mantissa digits without trailing zeros (at least one).
252fn significant(r: &Real) -> &[u8] {
253    let last = r.digits.iter().rposition(|&d| d != 0).unwrap_or(0);
254    &r.digits[..=last]
255}
256
257fn digit(d: u8) -> char {
258    char::from(b'0' + d)
259}
260
261/// STD: fixed notation when the digits fit 12 places, else scientific.
262fn std_text(r: &Real, real_point: bool) -> String {
263    let sign = if r.negative { "-" } else { "" };
264    let sig = significant(r);
265    if r.digits.iter().all(|&d| d == 0) {
266        return if real_point { "0.".into() } else { "0".into() };
267    }
268    let e = r.exponent;
269    let k = sig.len() as i32;
270    if (0..12).contains(&e) {
271        // Integer part: the first e + 1 digits (zero-padded).
272        let int: String = (0..=e)
273            .map(|i| digit(sig.get(i as usize).copied().unwrap_or(0)))
274            .collect();
275        let frac: String = sig
276            .iter()
277            .skip((e + 1) as usize)
278            .map(|&d| digit(d))
279            .collect();
280        if frac.is_empty() {
281            let point = if real_point { "." } else { "" };
282            return format!("{sign}{int}{point}");
283        }
284        return format!("{sign}{int}.{frac}");
285    }
286    if e < 0 && -e - 1 + k <= 12 {
287        let zeros = "0".repeat((-e - 1) as usize);
288        let frac: String = sig.iter().map(|&d| digit(d)).collect();
289        return format!("{sign}.{zeros}{frac}");
290    }
291    let rest: String = sig[1..].iter().map(|&d| digit(d)).collect();
292    format!("{sign}{}.{rest}E{e}", digit(sig[0]))
293}
294
295/// Round the 12-digit mantissa of `r` to `keep` digits (half up); the
296/// digits and the exponent after a carry.
297fn rounded(r: &Real, keep: usize) -> (Vec<u8>, i32) {
298    let mut d: Vec<u8> = r.digits.to_vec();
299    let mut e = r.exponent;
300    if keep >= 12 {
301        return (d, e);
302    }
303    let up = d[keep] >= 5;
304    d.truncate(keep);
305    if up {
306        let mut i = keep;
307        loop {
308            if i == 0 {
309                d.insert(0, 1);
310                d.truncate(keep.max(1));
311                e += 1;
312                break;
313            }
314            i -= 1;
315            if d[i] == 9 {
316                d[i] = 0;
317            } else {
318                d[i] += 1;
319                break;
320            }
321        }
322    }
323    (d, e)
324}
325
326/// FIX: `decimals` places, at most 12 digits in all (fewer decimals for
327/// large numbers); scientific when the integer part needs more than 12
328/// digits or a nonzero number rounds to zero.
329fn fix_text(r: &Real, decimals: usize) -> String {
330    if r.digits.iter().all(|&d| d == 0) {
331        return format!("0.{}", "0".repeat(decimals));
332    }
333    let sign = if r.negative { "-" } else { "" };
334    let places = |e: i32| -> i32 {
335        if e >= 0 {
336            (decimals as i32).min(11 - e)
337        } else {
338            decimals as i32
339        }
340    };
341    // Digits kept from the first: the integer part and the decimals.
342    let keep = r.exponent + 1 + places(r.exponent);
343    let (d, e) = match keep {
344        k if k >= 1 => rounded(r, k as usize),
345        // Below the last place: rounds to one unit of it, or to zero.
346        0 if r.digits[0] >= 5 => (vec![1], r.exponent + 1),
347        _ => return sci_text(r, decimals.min(11), 1),
348    };
349    if e >= 12 {
350        return sci_text(r, decimals.min(11), 1);
351    }
352    let dec = places(e).max(0);
353    let at = |place: i32| -> u8 {
354        let i = e - place;
355        if i < 0 {
356            0
357        } else {
358            d.get(i as usize).copied().unwrap_or(0)
359        }
360    };
361    let int: String = if e >= 0 {
362        (0..=e).rev().map(|p| digit(at(p))).collect()
363    } else {
364        "0".into()
365    };
366    let frac: String = (1..=dec).map(|p| digit(at(-p))).collect();
367    format!("{sign}{int}.{frac}")
368}
369
370/// SCI (`group` 1) or ENG (`group` 3) with `decimals` + 1 significant
371/// digits; ENG pads the integer part to put the exponent on a multiple of
372/// three.
373fn sci_text(r: &Real, decimals: usize, group: i32) -> String {
374    let sign = if r.negative { "-" } else { "" };
375    if r.digits.iter().all(|&d| d == 0) {
376        let zeros = "0".repeat(decimals);
377        return format!("0.{zeros}E0");
378    }
379    let sig = (decimals + 1).min(12);
380    let (mut d, e) = rounded(r, sig);
381    let shift = e.rem_euclid(group);
382    let exp = e - shift;
383    let int_digits = (shift + 1) as usize;
384    d.resize(sig.max(int_digits), 0);
385    let int: String = d[..int_digits].iter().map(|&x| digit(x)).collect();
386    let frac: String = d[int_digits..sig.max(int_digits)]
387        .iter()
388        .map(|&x| digit(x))
389        .collect();
390    format!("{sign}{int}.{frac}E{exp}")
391}
392
393/// A string capped at [`MAX_TEXT`] characters.
394#[derive(Debug, Default)]
395pub(crate) struct Text {
396    s: String,
397    chars: usize,
398    full: bool,
399}
400
401impl Text {
402    pub(crate) fn push(&mut self, t: &str) {
403        if self.full {
404            return;
405        }
406        for c in t.chars() {
407            if self.chars == MAX_TEXT {
408                self.s.push_str(TRUNCATED);
409                self.full = true;
410                return;
411            }
412            self.s.push(c);
413            self.chars += 1;
414        }
415    }
416
417    pub(crate) fn full(&self) -> bool {
418        self.full
419    }
420
421    pub(crate) fn finish(self) -> String {
422        self.s
423    }
424}
425
426/// `obj` as the stack shows it as a whole level.
427pub fn display(obj: &Object, s: &Settings) -> String {
428    let mut t = Text::default();
429    match obj {
430        Object::Real { value } => t.push(
431            &Settings {
432                group_digits: true,
433                ..*s
434            }
435            .real(value),
436        ),
437        Object::Tagged { tag, object } => {
438            t.push(tag);
439            t.push(": ");
440            write_tagged_object(&mut t, tag, object, s);
441        }
442        _ => write_object(&mut t, obj, s),
443    }
444    t.finish()
445}
446
447/// The object of a tagged object. In the 49G's algebraic mode the stack
448/// shows the history echo (an empty tag on a program that is one
449/// backquoted expression, [`Syntax::Algebraic`]) as the expression typed,
450/// without the backquotes.
451fn write_tagged_object(t: &mut Text, tag: &str, object: &Object, s: &Settings) {
452    if s.algebraic
453        && tag.is_empty()
454        && let Some(inner) = backquoted(object)
455    {
456        t.push(inner);
457    } else {
458        write_object(t, object, s);
459    }
460}
461
462/// The expression of a program that is one backquoted expression and
463/// nothing else, without the backquotes.
464fn backquoted(obj: &Object) -> Option<&str> {
465    let Object::Program {
466        source: Some(source),
467    } = obj
468    else {
469        return None;
470    };
471    let inner = source.strip_prefix('`')?.strip_suffix('`')?;
472    (!inner.contains('`')).then_some(inner)
473}
474
475/// Whether the calculator's text of `obj` is known in full: every
476/// program, algebraic and unit inside it was decompiled and every command
477/// has a name (or is an XLIB name, shown by its numbers).
478pub fn has_text(obj: &Object) -> bool {
479    match obj {
480        Object::Program { source } | Object::Algebraic { source } => source.is_some(),
481        Object::Command {
482            name,
483            source,
484            address,
485            library,
486            command,
487        } => {
488            name.is_some()
489                || source.is_some()
490                || (address.is_none() && library.is_some() && command.is_some())
491        }
492        Object::Unit { unit, .. } => unit.is_some(),
493        Object::Unknown { source, .. } => source.is_some(),
494        Object::List { items } => items.iter().all(has_text),
495        Object::Tagged { object, .. } => has_text(object),
496        Object::Array { items, .. } => array_has_text(items),
497        _ => true,
498    }
499}
500
501fn array_has_text(items: &[ArrayItem]) -> bool {
502    items.iter().all(|i| match i {
503        ArrayItem::Item(o) => has_text(o),
504        ArrayItem::Row(row) => array_has_text(row),
505    })
506}
507
508/// The calculator's text of `obj` as a whole stack level or a variable's
509/// value ([`display`]; a name with its quotes, as the stack shows one), or
510/// `None` if a part of it has none ([`has_text`]).
511pub fn text(obj: &Object, s: &Settings) -> Option<String> {
512    if !has_text(obj) {
513        return None;
514    }
515    Some(match obj {
516        Object::Name { value } | Object::LocalName { value } => format!("'{value}'"),
517        _ => display(obj, s),
518    })
519}
520
521/// Text of `obj` to edit and compile again (the palette's editor), in
522/// settings made by [`Settings::for_editing`]: [`text`], but a tagged
523/// object as `:tag:object`, which compiles. An error for an object with
524/// no such text: a part without text ([`has_text`]), a string holding `"`
525/// (the calculator has no way to write one), or text cut at [`MAX_TEXT`].
526pub fn edit_text(obj: &Object, s: &Settings) -> Result<String> {
527    if !has_text(obj) {
528        return Err(Error::Invalid(
529            "it has no text form (a graphic, library, backup, directory or code object)".into(),
530        ));
531    }
532    if holds_quote(obj) {
533        return Err(Error::Invalid(
534            "it holds a string with a \" in it, which no text can write".into(),
535        ));
536    }
537    let text = match obj {
538        Object::Tagged { .. } => element_text(obj, s).unwrap_or_default(),
539        _ => text(obj, s).unwrap_or_default(),
540    };
541    if text.chars().count() > MAX_TEXT {
542        return Err(Error::Invalid(format!(
543            "its text is longer than {MAX_TEXT} characters"
544        )));
545    }
546    Ok(text)
547}
548
549/// Whether a string inside `obj` holds `"`.
550fn holds_quote(obj: &Object) -> bool {
551    match obj {
552        Object::String { value } => value.contains('"'),
553        Object::List { items } => items.iter().any(holds_quote),
554        Object::Tagged { object, .. } => holds_quote(object),
555        Object::Array { items, .. } => array_holds_quote(items),
556        _ => false,
557    }
558}
559
560fn array_holds_quote(items: &[ArrayItem]) -> bool {
561    items.iter().any(|i| match i {
562        ArrayItem::Item(o) => holds_quote(o),
563        ArrayItem::Row(row) => array_holds_quote(row),
564    })
565}
566
567/// The text of `obj` as an element of a list or a tagged object.
568fn element_text(obj: &Object, s: &Settings) -> Option<String> {
569    has_text(obj).then(|| {
570        let mut t = Text::default();
571        write_object(&mut t, obj, s);
572        t.finish()
573    })
574}
575
576/// The text of `obj` as an element of an array (a name quoted).
577fn cell_text(obj: &Object, s: &Settings) -> Option<String> {
578    match obj {
579        Object::Name { value } | Object::LocalName { value } => Some(format!("'{value}'")),
580        _ => element_text(obj, s),
581    }
582}
583
584/// `obj` as JSON (its serde shape) with the calculator's own text in a
585/// `text` field on the object and on every object inside it (a list's
586/// items, a tagged object's object, an array's elements), each as it is
587/// written in that place; no `text` where there is none ([`has_text`]).
588/// A front end shows and copies these texts and formats nothing itself.
589/// An error past [`MAX_DESCRIBED_TEXT`] characters of text.
590pub fn described(obj: &Object, s: &Settings) -> Result<serde_json::Value> {
591    let mut left = MAX_DESCRIBED_TEXT;
592    described_within(obj, s, &mut left)
593}
594
595/// [`described`] with `left` characters of text, shared by the caller's
596/// other calls (the levels of one stack).
597pub(crate) fn described_within(
598    obj: &Object,
599    s: &Settings,
600    left: &mut usize,
601) -> Result<serde_json::Value> {
602    let mut v = serde_json::to_value(obj).map_err(|e| Error::Invalid(e.to_string()))?;
603    describe(&mut v, obj, s, text(obj, s), left)?;
604    Ok(v)
605}
606
607fn describe(
608    v: &mut serde_json::Value,
609    obj: &Object,
610    s: &Settings,
611    text: Option<String>,
612    left: &mut usize,
613) -> Result<()> {
614    let Some(map) = v.as_object_mut() else {
615        return Ok(());
616    };
617    if let Some(text) = text {
618        let Some(rest) = left.checked_sub(text.chars().count()) else {
619            return Err(Error::Invalid(format!(
620                "more than {MAX_DESCRIBED_TEXT} characters of text (MAX_DESCRIBED_TEXT): \
621                 objects that repeat or nest deeply"
622            )));
623        };
624        *left = rest;
625        map.insert("text".to_string(), serde_json::Value::String(text));
626    }
627    match obj {
628        Object::List { items } => {
629            if let Some(vs) = map.get_mut("items").and_then(|i| i.as_array_mut()) {
630                for (cv, c) in vs.iter_mut().zip(items) {
631                    describe(cv, c, s, element_text(c, s), left)?;
632                }
633            }
634        }
635        Object::Tagged { object, .. } => {
636            if let Some(cv) = map.get_mut("object") {
637                describe(cv, object, s, element_text(object, s), left)?;
638            }
639        }
640        Object::Array { items, .. } => {
641            if let Some(vs) = map.get_mut("items").and_then(|i| i.as_array_mut()) {
642                describe_array(vs, items, s, left)?;
643            }
644        }
645        _ => {}
646    }
647    Ok(())
648}
649
650fn describe_array(
651    vs: &mut [serde_json::Value],
652    items: &[ArrayItem],
653    s: &Settings,
654    left: &mut usize,
655) -> Result<()> {
656    for (cv, item) in vs.iter_mut().zip(items) {
657        match item {
658            ArrayItem::Item(o) => describe(cv, o, s, cell_text(o, s), left)?,
659            ArrayItem::Row(row) => {
660                if let Some(rv) = cv.as_array_mut() {
661                    describe_array(rv, row, s, left)?;
662                }
663            }
664        }
665    }
666    Ok(())
667}
668
669pub(crate) fn write_object(t: &mut Text, obj: &Object, s: &Settings) {
670    if t.full() {
671        return;
672    }
673    match obj {
674        Object::Real { value } => t.push(&s.real(value)),
675        Object::Integer { value } => t.push(&value.to_source()),
676        Object::Complex { re, im } => t.push(&s.complex(re, im)),
677        Object::String { value } => {
678            t.push("\"");
679            t.push(value);
680            t.push("\"");
681        }
682        Object::Name { value } | Object::LocalName { value } => t.push(value),
683        Object::Character { value } => {
684            t.push("CHR ");
685            t.push(value);
686        }
687        Object::Binary { value, .. } => t.push(&s.binary(*value)),
688        Object::List { items } => {
689            t.push("{");
690            for o in items {
691                t.push(" ");
692                write_object(t, o, s);
693            }
694            t.push(" }");
695        }
696        Object::Tagged { tag, object } => {
697            if s.tag_colon {
698                t.push(":");
699            }
700            t.push(tag);
701            t.push(": ");
702            write_tagged_object(t, tag, object, s);
703        }
704        Object::Unit { value, unit } => {
705            t.push(&s.unit_number(value));
706            t.push("_");
707            t.push(unit.as_deref().unwrap_or("?"));
708        }
709        Object::Array { items, .. } => write_array(t, items, s),
710        Object::Program { source } | Object::Algebraic { source } => {
711            t.push(source.as_deref().unwrap_or("?"));
712        }
713        Object::Command {
714            name,
715            source,
716            address,
717            library,
718            command,
719        } => match name.as_deref().or(source.as_deref()) {
720            Some(text) => t.push(text),
721            None => t.push(&command_text(None, *address, library.zip(*command))),
722        },
723        Object::Unknown { kind, source, .. } => match source {
724            Some(src) => t.push(src),
725            None => t.push(kind.as_deref().unwrap_or("External")),
726        },
727    }
728}
729
730fn write_array(t: &mut Text, items: &[ArrayItem], s: &Settings) {
731    t.push("[");
732    for (i, item) in items.iter().enumerate() {
733        match item {
734            ArrayItem::Item(o) => {
735                t.push(" ");
736                // A name in a (49G symbolic) array is shown quoted.
737                if let Object::Name { value } | Object::LocalName { value } = o.as_ref() {
738                    t.push("'");
739                    t.push(value);
740                    t.push("'");
741                } else {
742                    write_object(t, o, s);
743                }
744            }
745            ArrayItem::Row(row) => {
746                t.push(if i == 0 { "" } else { " " });
747                write_array(t, row, s);
748            }
749        }
750    }
751    if matches!(items.first(), Some(ArrayItem::Row(_))) {
752        t.push("]");
753    } else {
754        t.push(" ]");
755    }
756}
757
758/// A command's text: its name; an XLIB name without one as the
759/// calculator shows it (`XLIB 1234 5`); an unnamed ROM object `External`.
760fn command_text(name: Option<&str>, address: Option<u32>, xlib: Option<(u16, u16)>) -> String {
761    match (name, address, xlib) {
762        (Some(n), _, _) => n.to_string(),
763        (None, None, Some((l, c))) => format!("XLIB {l} {c}"),
764        _ => "External".to_string(),
765    }
766}
767
768/// One element of a program, algebraic or unit body.
769#[derive(Clone, Debug)]
770pub(crate) enum Element {
771    /// A data object (embedded, or a ROM constant).
772    Object(Object),
773    /// A command: its name (`None`: no name in the ROM's tables), its ROM
774    /// address (not for XLIB names), its argument count when known.
775    Command {
776        name: Option<String>,
777        address: Option<u32>,
778        /// Library and command number, when known.
779        xlib: Option<(u16, u16)>,
780        arity: Option<u8>,
781        /// Shown as nothing (see `CommandInfo::silent`).
782        silent: bool,
783        /// What it marks in algebraic-mode syntax (see `CommandInfo::syntax`).
784        syntax: Option<Syntax>,
785    },
786    /// An algebraic inside a composite, with its own elements (an
787    /// operand of `∂`, `Σ`, `|`, a user function's name).
788    Symbolic {
789        object: Object,
790        elements: Vec<Element>,
791    },
792    /// A program inside a composite, with its own elements (the body of a
793    /// backquoted expression, see [`Syntax::Algebraic`]).
794    Secondary {
795        object: Object,
796        elements: Vec<Element>,
797    },
798    /// A system binary (in an algebraic, the operand count of a user
799    /// function or of `|`).
800    SystemBinary(u32),
801    /// A unit operator.
802    Unit(UnitOp),
803}
804
805/// Unit operators (wiki: protocols/rpl-libraries "Unit operators").
806#[derive(Clone, Copy, Debug, PartialEq, Eq)]
807pub(crate) enum UnitOp {
808    Times,
809    Divide,
810    Power,
811    Prefix,
812    End,
813}
814
815impl Element {
816    fn token(&self, t: &mut Text, s: &Settings) {
817        match self {
818            Element::Object(o)
819            | Element::Symbolic { object: o, .. }
820            | Element::Secondary { object: o, .. } => write_object(t, o, s),
821            Element::Command {
822                name,
823                address,
824                xlib,
825                ..
826            } => t.push(&command_text(name.as_deref(), *address, *xlib)),
827            Element::SystemBinary(v) => t.push(&format!("<{v:X}h>")),
828            Element::Unit(_) => t.push("External"),
829        }
830    }
831
832    fn is_quote(&self) -> bool {
833        matches!(self, Element::Command { name: Some(n), .. } if n == "'")
834    }
835}
836
837/// A program's text: its elements separated by spaces. A user program's
838/// body starts with the command `«` and ends with `»`; a program inside a
839/// structure (a CASE clause) has neither and shows its elements only. A
840/// quoted object `' X '` closes up to `'X'`.
841pub(crate) fn program(elements: &[Element], s: &Settings) -> String {
842    let mut t = Text::default();
843    let mut first = true;
844    let mut open = false;
845    let mut glue = false;
846    let mut iter = elements.iter().peekable();
847    while let Some(e) = iter.next() {
848        if t.full() {
849            break;
850        }
851        // An expression in algebraic-mode syntax: `CF(-95)`.
852        if let Element::Command {
853            syntax: Some(Syntax::Algebraic),
854            ..
855        } = e
856            && let Some(text) = iter.peek().and_then(|next| infix_text(next, s))
857        {
858            iter.next();
859            if !first && !glue {
860                t.push(" ");
861            }
862            first = false;
863            glue = false;
864            t.push("`");
865            t.push(&text);
866            t.push("`");
867            continue;
868        }
869        if matches!(e, Element::Command { silent: true, .. }) {
870            continue;
871        }
872        if e.is_quote() && open {
873            // Closing quote: no space before it.
874            t.push("'");
875            open = false;
876            continue;
877        }
878        if !first && !glue {
879            t.push(" ");
880        }
881        first = false;
882        glue = false;
883        if e.is_quote() {
884            // Opening quote: no space after it.
885            t.push("'");
886            open = true;
887            glue = true;
888        } else {
889            e.token(&mut t, s);
890        }
891    }
892    t.finish()
893}
894
895/// An expression node of an algebraic or unit.
896#[derive(Clone, Debug)]
897enum Node {
898    /// Text that binds tightest (a name, a number, a call).
899    Atom(String),
900    /// A built-in command's name as the function of a call in
901    /// algebraic-mode syntax (`CF` of `CF(-95)`; `DUP` alone without
902    /// arguments).
903    Function(String),
904    /// A number, which needs parentheses where its sign could bind
905    /// differently.
906    Negative(String),
907    /// A binary infix operator.
908    Infix {
909        op: String,
910        prec: u8,
911        left: usize,
912        right: usize,
913        spaced: bool,
914    },
915    /// A prefix operator (`-`, `NOT `, `√`).
916    Prefix { op: String, prec: u8, arg: usize },
917    /// A postfix operator (`!`).
918    Postfix { op: String, arg: usize },
919    /// A function call `F(a,b)`; with `equation`, the first two
920    /// arguments are written `a=b` (`Σ(K=0,M,K)`).
921    Call {
922        name: String,
923        args: Vec<usize>,
924        equation: bool,
925    },
926    /// `expr|(name=value,...)`.
927    Where {
928        expr: usize,
929        pairs: Vec<(usize, usize)>,
930    },
931    /// The derivative `∂X(f)`.
932    Derivative { var: usize, expr: usize },
933    /// Juxtaposition without operator (a unit prefix `k` + `m`).
934    Concat { left: usize, right: usize },
935}
936
937/// Binding strength of the operators (higher binds tighter).
938const ATOM: u8 = 20;
939const PREC_ROOT: u8 = 13;
940const PREC_POWER: u8 = 12;
941const PREC_NEG: u8 = 11;
942const PREC_PRODUCT: u8 = 10;
943const PREC_SUM: u8 = 9;
944const PREC_COMPARE: u8 = 7;
945const PREC_NOT: u8 = 6;
946const PREC_AND: u8 = 5;
947const PREC_OR: u8 = 4;
948const PREC_EQUATION: u8 = 3;
949const PREC_WHERE: u8 = 2;
950
951/// The infix operators by name: precedence, whether written with spaces.
952fn infix(name: &str) -> Option<(u8, bool)> {
953    Some(match name {
954        "^" => (PREC_POWER, false),
955        "*" | "/" => (PREC_PRODUCT, false),
956        "+" | "-" => (PREC_SUM, false),
957        "==" | "≠" | "<" | ">" | "≤" | "≥" => (PREC_COMPARE, false),
958        "AND" => (PREC_AND, true),
959        "OR" | "XOR" => (PREC_OR, true),
960        "=" => (PREC_EQUATION, false),
961        "|" => (PREC_WHERE, false),
962        _ => return None,
963    })
964}
965
966struct Tree {
967    nodes: Vec<Node>,
968}
969
970impl Tree {
971    fn add(&mut self, n: Node) -> usize {
972        self.nodes.push(n);
973        self.nodes.len() - 1
974    }
975
976    fn prec(&self, i: usize) -> u8 {
977        match &self.nodes[i] {
978            Node::Atom(_)
979            | Node::Function(_)
980            | Node::Call { .. }
981            | Node::Derivative { .. }
982            | Node::Concat { .. } => ATOM,
983            Node::Where { .. } => PREC_WHERE,
984            Node::Negative(_) => PREC_NEG,
985            Node::Infix { prec, .. } | Node::Prefix { prec, .. } => *prec,
986            Node::Postfix { .. } => ATOM - 1,
987        }
988    }
989
990    /// Whether node `i` starts with a sign or prefix operator.
991    fn signed(&self, i: usize) -> bool {
992        matches!(self.nodes[i], Node::Prefix { .. } | Node::Negative(_))
993    }
994
995    /// Render node `root` into `t` without recursion (garbage can nest
996    /// arbitrarily deep).
997    fn render(&self, root: usize, t: &mut Text, all_parens: bool, power_right: bool) {
998        enum Work<'a> {
999            Node(usize),
1000            Text(&'a str),
1001            Owned(String),
1002        }
1003        let mut stack = vec![Work::Node(root)];
1004        while let Some(w) = stack.pop() {
1005            if t.full() {
1006                return;
1007            }
1008            let i = match w {
1009                Work::Text(s) => {
1010                    t.push(s);
1011                    continue;
1012                }
1013                Work::Owned(s) => {
1014                    t.push(&s);
1015                    continue;
1016                }
1017                Work::Node(i) => i,
1018            };
1019            // Children are pushed in reverse order of output.
1020            let operand = |stack: &mut Vec<Work<'_>>, child: usize, parens: bool| {
1021                let compound = !matches!(
1022                    self.nodes[child],
1023                    Node::Atom(_) | Node::Function(_) | Node::Call { .. } | Node::Derivative { .. }
1024                );
1025                if parens || (all_parens && compound) {
1026                    stack.push(Work::Text(")"));
1027                    stack.push(Work::Node(child));
1028                    stack.push(Work::Text("("));
1029                } else {
1030                    stack.push(Work::Node(child));
1031                }
1032            };
1033            match &self.nodes[i] {
1034                Node::Atom(s) | Node::Negative(s) | Node::Function(s) => t.push(s),
1035                Node::Infix {
1036                    op,
1037                    prec,
1038                    left,
1039                    right,
1040                    spaced,
1041                } => {
1042                    // Operators group to the left, `^` to the right where
1043                    // `power_right`; a sign or prefix on the right reads
1044                    // unambiguously (`A^-B`).
1045                    let right_assoc = power_right && *prec == PREC_POWER;
1046                    // A where-expression on the left is written bare
1047                    // (`X|(X=2)+1`, `X|(X=2)^2`), on the right and under
1048                    // a prefix it is parenthesised (`1+(X|(X=2))`,
1049                    // `-(X|(X=2))`): oracle-checked on all three ROMs.
1050                    let where_left = matches!(self.nodes[*left], Node::Where { .. });
1051                    let lp = !where_left
1052                        && if right_assoc {
1053                            self.prec(*left) <= *prec
1054                        } else {
1055                            self.prec(*left) < *prec
1056                        };
1057                    let rp = if right_assoc {
1058                        self.prec(*right) < *prec
1059                    } else {
1060                        self.prec(*right) <= *prec
1061                    } && !self.signed(*right);
1062                    operand(&mut stack, *right, rp);
1063                    if *spaced {
1064                        stack.push(Work::Owned(format!(" {op} ")));
1065                    } else {
1066                        stack.push(Work::Owned(op.clone()));
1067                    }
1068                    operand(&mut stack, *left, lp);
1069                }
1070                Node::Prefix { op, prec, arg } => {
1071                    let p = self.prec(*arg) < *prec && !self.signed(*arg);
1072                    operand(&mut stack, *arg, p);
1073                    // `NOT A` but `NOT(A AND B)`.
1074                    t.push(if p { op.trim_end() } else { op });
1075                }
1076                Node::Postfix { op, arg } => {
1077                    stack.push(Work::Owned(op.clone()));
1078                    let p = self.prec(*arg) < ATOM;
1079                    operand(&mut stack, *arg, p);
1080                }
1081                Node::Call {
1082                    name,
1083                    args,
1084                    equation,
1085                } => {
1086                    stack.push(Work::Text(")"));
1087                    for (k, a) in args.iter().enumerate().rev() {
1088                        stack.push(Work::Node(*a));
1089                        if k == 1 && *equation {
1090                            stack.push(Work::Text("="));
1091                        } else if k > 0 {
1092                            stack.push(Work::Text(","));
1093                        }
1094                    }
1095                    t.push(name);
1096                    t.push("(");
1097                }
1098                Node::Where { expr, pairs } => {
1099                    stack.push(Work::Text(")"));
1100                    for (k, (n, v)) in pairs.iter().enumerate().rev() {
1101                        stack.push(Work::Node(*v));
1102                        stack.push(Work::Text("="));
1103                        stack.push(Work::Node(*n));
1104                        if k > 0 {
1105                            stack.push(Work::Text(","));
1106                        }
1107                    }
1108                    stack.push(Work::Text("|("));
1109                    let p = self.prec(*expr) <= PREC_WHERE;
1110                    operand(&mut stack, *expr, p);
1111                }
1112                Node::Derivative { var, expr } => {
1113                    stack.push(Work::Text(")"));
1114                    stack.push(Work::Node(*expr));
1115                    stack.push(Work::Text("("));
1116                    stack.push(Work::Node(*var));
1117                    t.push("∂");
1118                }
1119                Node::Concat { left, right } => {
1120                    stack.push(Work::Node(*right));
1121                    stack.push(Work::Node(*left));
1122                }
1123            }
1124        }
1125    }
1126}
1127
1128/// An operand on the evaluation stack: a node, or a user function's
1129/// argument count.
1130#[derive(Clone, Copy, Debug)]
1131enum Slot {
1132    Node(usize),
1133    Count(u32),
1134}
1135
1136/// An algebraic's text with its quotes, or `None` when its elements do
1137/// not form one expression (a command without a known argument count, a
1138/// malformed body).
1139pub(crate) fn algebraic(elements: &[Element], s: &Settings) -> Option<String> {
1140    let s = &s.inner();
1141    let mut tree = Tree { nodes: Vec::new() };
1142    let root = expression(elements, s, &mut tree, false)?;
1143    let mut t = Text::default();
1144    t.push("'");
1145    tree.render(root, &mut t, s.all_parens, s.power_right);
1146    t.push("'");
1147    Some(t.finish())
1148}
1149
1150/// The text of `e` in algebraic-mode syntax, without quotes or
1151/// backquotes (`CF(-95)`, `X+1`, `{1,2}`), or `None` when it does not form
1152/// one expression.
1153fn infix_text(e: &Element, s: &Settings) -> Option<String> {
1154    let s = &s.inner();
1155    let mut tree = Tree { nodes: Vec::new() };
1156    let root = expression(std::slice::from_ref(e), s, &mut tree, true)?;
1157    let mut t = Text::default();
1158    tree.render(root, &mut t, s.all_parens, s.power_right);
1159    Some(t.finish())
1160}
1161
1162/// An operand's node; with `marked` (inside a backquoted expression) a
1163/// list in algebraic syntax.
1164fn leaf(o: &Object, s: &Settings, marked: bool) -> Node {
1165    let mut t = Text::default();
1166    match o {
1167        Object::List { items } if marked => infix_list(&mut t, items, s),
1168        _ => write_object(&mut t, o, s),
1169    }
1170    let text = t.finish();
1171    let negative = match o {
1172        Object::Real { value } => value.negative,
1173        Object::Integer { value } => value.negative,
1174        Object::Unit { value, .. } => value.negative,
1175        _ => false,
1176    };
1177    if negative {
1178        Node::Negative(text)
1179    } else {
1180        Node::Atom(text)
1181    }
1182}
1183
1184/// The expression of `elements`, evaluated as RPN onto a stack of nodes:
1185/// an algebraic's body, or the body of a backquoted expression
1186/// ([`Syntax`]), where a command that is no function is called as a user
1187/// function is: its arguments, its name as a one-element algebraic, the
1188/// argument count and the (unnamed) command applying it. Only `marked`
1189/// (inside such an expression) reads the quote, embedded programs, a
1190/// one-command algebraic as a function name and lists in algebraic
1191/// syntax; an ordinary algebraic is read as before.
1192fn expression(elements: &[Element], s: &Settings, tree: &mut Tree, marked: bool) -> Option<usize> {
1193    let mut stack: Vec<Slot> = Vec::new();
1194    let mut quote = false;
1195    for e in elements {
1196        let node = match e {
1197            Element::Object(o) => tree.add(leaf(o, s, marked)),
1198            Element::Symbolic { elements, .. } => match elements.as_slice() {
1199                [
1200                    Element::Command {
1201                        name: Some(name), ..
1202                    },
1203                ] if marked => tree.add(Node::Function(name.clone())),
1204                _ => expression(elements, s, tree, marked)?,
1205            },
1206            Element::Secondary { object, elements } => {
1207                let user = matches!(
1208                    elements.first(),
1209                    Some(Element::Command { name: Some(n), .. }) if n == "«"
1210                );
1211                match (marked && !user)
1212                    .then(|| expression(elements, s, tree, true))
1213                    .flatten()
1214                {
1215                    Some(node) => node,
1216                    None => tree.add(leaf(object, s, false)),
1217                }
1218            }
1219            Element::SystemBinary(v) => {
1220                stack.push(Slot::Count(*v));
1221                continue;
1222            }
1223            Element::Unit(_) => return None,
1224            Element::Command {
1225                syntax: Some(syntax),
1226                ..
1227            } if marked => {
1228                quote = *syntax == Syntax::Quote;
1229                continue;
1230            }
1231            Element::Command { name, arity, .. } => {
1232                if let Some(Slot::Count(n)) = stack.last().copied() {
1233                    stack.pop();
1234                    counted(name.as_deref(), n, &mut stack, tree)?
1235                } else {
1236                    apply(name.as_deref()?, *arity, &mut stack, tree)?
1237                }
1238            }
1239        };
1240        let node = if std::mem::take(&mut quote) {
1241            let mut t = Text::default();
1242            tree.render(node, &mut t, s.all_parens, s.power_right);
1243            tree.add(Node::Atom(format!("'{}'", t.finish())))
1244        } else {
1245            node
1246        };
1247        stack.push(Slot::Node(node));
1248    }
1249    match stack.as_slice() {
1250        [Slot::Node(n)] => Some(*n),
1251        _ => None,
1252    }
1253}
1254
1255/// A command that takes its operand count from the stack: `|` (the
1256/// expression, then name and value pairs; the count covers all of them),
1257/// or a user function (the arguments and its name; the count covers the
1258/// arguments; the command applying it has no name).
1259fn counted(name: Option<&str>, n: u32, stack: &mut Vec<Slot>, tree: &mut Tree) -> Option<usize> {
1260    let mut pop = || match stack.pop()? {
1261        Slot::Node(n) => Some(n),
1262        Slot::Count(_) => None,
1263    };
1264    match name {
1265        Some("|") => {
1266            let mut items = Vec::new();
1267            for _ in 0..n {
1268                items.push(pop()?);
1269            }
1270            items.reverse();
1271            let (&expr, rest) = items.split_first()?;
1272            if rest.len() % 2 != 0 {
1273                return None;
1274            }
1275            let pairs = rest.chunks(2).map(|p| (p[0], p[1])).collect();
1276            Some(tree.add(Node::Where { expr, pairs }))
1277        }
1278        None => {
1279            let f = pop()?;
1280            let mut args = Vec::new();
1281            for _ in 0..n {
1282                args.push(pop()?);
1283            }
1284            args.reverse();
1285            let name = match &tree.nodes[f] {
1286                // A command without arguments is its name alone (`DUP`).
1287                Node::Function(name) if args.is_empty() => {
1288                    return Some(tree.add(Node::Atom(name.clone())));
1289                }
1290                Node::Atom(name) | Node::Function(name) => name.clone(),
1291                _ => return None,
1292            };
1293            Some(tree.add(Node::Call {
1294                name,
1295                args,
1296                equation: false,
1297            }))
1298        }
1299        Some(_) => None,
1300    }
1301}
1302
1303/// A list in algebraic syntax: `{1,X,{2}}`, names without quotes.
1304fn infix_list(t: &mut Text, items: &[Object], s: &Settings) {
1305    t.push("{");
1306    for (i, o) in items.iter().enumerate() {
1307        if i > 0 {
1308            t.push(",");
1309        }
1310        match o {
1311            Object::List { items } => infix_list(t, items, s),
1312            Object::Algebraic {
1313                source: Some(source),
1314            } => t.push(unquoted(source)),
1315            // A name in the list is kept from being evaluated as a
1316            // backquoted expression of its own: `{1,X}`.
1317            _ => match backquoted(o) {
1318                Some(inner) => t.push(inner),
1319                None => write_object(t, o, s),
1320            },
1321        }
1322    }
1323    t.push("}");
1324}
1325
1326/// `text` without the quotes around it, if it has them.
1327fn unquoted(text: &str) -> &str {
1328    text.strip_prefix('\'')
1329        .and_then(|x| x.strip_suffix('\''))
1330        .unwrap_or(text)
1331}
1332
1333/// Apply command `name` to operands on `stack`.
1334fn apply(name: &str, arity: Option<u8>, stack: &mut Vec<Slot>, tree: &mut Tree) -> Option<usize> {
1335    let mut pop = || match stack.pop()? {
1336        Slot::Node(n) => Some(n),
1337        Slot::Count(_) => None,
1338    };
1339    // Without a known argument count there is no telling how many
1340    // operands it takes: no text (the caller leaves `source` unset).
1341    let arity = arity?;
1342    if arity == 2
1343        && let Some((prec, spaced)) = infix(name)
1344    {
1345        let right = pop()?;
1346        let left = pop()?;
1347        return Some(tree.add(Node::Infix {
1348            op: name.to_string(),
1349            prec,
1350            left,
1351            right,
1352            spaced,
1353        }));
1354    }
1355    let unary = |op: &str, prec: u8, arg: usize, tree: &mut Tree| {
1356        tree.add(Node::Prefix {
1357            op: op.to_string(),
1358            prec,
1359            arg,
1360        })
1361    };
1362    if arity == 1 {
1363        match name {
1364            "NEG" => return Some(unary("-", PREC_NEG, pop()?, tree)),
1365            "NOT" => return Some(unary("NOT ", PREC_NOT, pop()?, tree)),
1366            "√" => return Some(unary("√", PREC_ROOT, pop()?, tree)),
1367            "!" => {
1368                let arg = pop()?;
1369                return Some(tree.add(Node::Postfix {
1370                    op: "!".into(),
1371                    arg,
1372                }));
1373            }
1374            _ => {}
1375        }
1376    }
1377    if name == "∂" && arity == 2 {
1378        let expr = pop()?;
1379        let var = pop()?;
1380        return Some(tree.add(Node::Derivative { var, expr }));
1381    }
1382    let mut args = Vec::new();
1383    for _ in 0..arity {
1384        args.push(pop()?);
1385    }
1386    args.reverse();
1387    if arity == 0 {
1388        return Some(tree.add(Node::Atom(name.to_string())));
1389    }
1390    Some(tree.add(Node::Call {
1391        name: name.to_string(),
1392        args,
1393        equation: name == "Σ" && arity == 4,
1394    }))
1395}
1396
1397/// A unit expression's text (`kg*m^2/s^2`) from the elements after the
1398/// number, or `None` if they do not form one.
1399pub(crate) fn unit(elements: &[Element], s: &Settings) -> Option<String> {
1400    let mut tree = Tree { nodes: Vec::new() };
1401    let mut stack: Vec<usize> = Vec::new();
1402    let mut ended = false;
1403    for e in elements {
1404        if ended {
1405            return None;
1406        }
1407        match e {
1408            Element::Object(Object::String { value })
1409            | Element::Object(Object::Character { value }) => {
1410                stack.push(tree.add(Node::Atom(value.clone())));
1411            }
1412            Element::Object(Object::Real { value }) => {
1413                stack.push(tree.add(Node::Atom(s.unit_number(value))));
1414            }
1415            Element::Object(o @ Object::Integer { .. }) => {
1416                stack.push(tree.add(leaf(o, s, false)));
1417            }
1418            Element::Unit(op) => {
1419                if *op == UnitOp::End {
1420                    ended = true;
1421                    continue;
1422                }
1423                let right = stack.pop()?;
1424                let left = stack.pop()?;
1425                let node = match op {
1426                    UnitOp::Prefix => Node::Concat { left, right },
1427                    UnitOp::Times | UnitOp::Divide | UnitOp::Power => {
1428                        let (op, prec) = match op {
1429                            UnitOp::Times => ("*", PREC_PRODUCT),
1430                            UnitOp::Divide => ("/", PREC_PRODUCT),
1431                            _ => ("^", PREC_POWER),
1432                        };
1433                        Node::Infix {
1434                            op: op.into(),
1435                            prec,
1436                            left,
1437                            right,
1438                            spaced: false,
1439                        }
1440                    }
1441                    UnitOp::End => return None,
1442                };
1443                stack.push(tree.add(node));
1444            }
1445            _ => return None,
1446        }
1447    }
1448    let [root] = stack.as_slice() else {
1449        return None;
1450    };
1451    let mut t = Text::default();
1452    tree.render(*root, &mut t, false, false);
1453    Some(t.finish())
1454}
1455
1456#[cfg(test)]
1457mod tests {
1458    use super::*;
1459    use crate::object::Integer;
1460
1461    fn real(s: &str) -> Real {
1462        Real::parse(s).unwrap()
1463    }
1464
1465    /// The text to edit keeps every digit and writes a tagged object so
1466    /// that it compiles; a string holding `"` has none.
1467    #[test]
1468    fn edit_text_compiles_back() {
1469        let fix = Settings {
1470            format: NumberFormat::Fix(2),
1471            base: Base::Hex,
1472            word_size: 16,
1473            tag_colon: false,
1474            group_digits: true,
1475            ..Settings::standard(Model::Hp49g)
1476        }
1477        .for_editing();
1478        let real = real_obj("1234.56789");
1479        assert_eq!(edit_text(&real, &fix).unwrap(), "1234.56789");
1480        let tagged = Object::Tagged {
1481            tag: "T".into(),
1482            object: Box::new(real.clone()),
1483        };
1484        assert_eq!(edit_text(&tagged, &fix).unwrap(), ":T: 1234.56789");
1485        let bin = Object::Binary {
1486            value: 0x12345,
1487            base: None,
1488            text: None,
1489        };
1490        assert_eq!(edit_text(&bin, &fix).unwrap(), "# 12345h");
1491        let quoted = Object::List {
1492            items: vec![Object::String {
1493                value: "say \"hi\"".into(),
1494            }],
1495        };
1496        assert!(
1497            edit_text(&quoted, &fix)
1498                .unwrap_err()
1499                .to_string()
1500                .contains('"')
1501        );
1502        let unknown = Object::Program { source: None };
1503        assert!(edit_text(&unknown, &fix).is_err());
1504    }
1505
1506    /// The texts a front end shows: the cases a formatter in the page got
1507    /// wrong (a name in a 49G array, a whole-number unit on the 49G, a
1508    /// small real), each on the object and on the objects inside.
1509    #[test]
1510    fn described_carries_the_text_of_every_object() {
1511        let name = |n: &str| Object::Name {
1512            value: n.to_string(),
1513        };
1514        let hp49 = Settings::standard(Model::Hp49g);
1515        let hp48 = Settings::standard(Model::Hp48gx);
1516        let array = Object::Array {
1517            dims: vec![1],
1518            items: vec![ArrayItem::Item(Box::new(name("A")))],
1519        };
1520        let v = described(&array, &hp49).unwrap();
1521        assert_eq!(v["text"], "[ 'A' ]");
1522        assert_eq!(v["items"][0]["text"], "'A'");
1523        let unit = Object::Unit {
1524            value: real("2"),
1525            unit: Some("m".to_string()),
1526        };
1527        assert_eq!(described(&unit, &hp49).unwrap()["text"], "2_m");
1528        assert_eq!(text(&real_obj("2"), &hp49), Some("2.".to_string()));
1529        assert_eq!(
1530            described(&real_obj("1.23456789012E-5"), &hp48).unwrap()["text"],
1531            "1.23456789012E-5"
1532        );
1533        // A list: its own text, and each element's as written inside it;
1534        // a name alone is quoted, inside a list it is not.
1535        let list = Object::List {
1536            items: vec![
1537                real_obj("1"),
1538                name("QQ"),
1539                Object::Tagged {
1540                    tag: "T".to_string(),
1541                    object: Box::new(real_obj("2")),
1542                },
1543                Object::Program { source: None },
1544            ],
1545        };
1546        let v = described(&list, &hp48).unwrap();
1547        assert!(v.get("text").is_none(), "{v}");
1548        assert_eq!(v["items"][1]["text"], "QQ");
1549        assert_eq!(v["items"][2]["text"], ":T: 2");
1550        assert_eq!(v["items"][2]["object"]["text"], "2");
1551        assert!(v["items"][3].get("text").is_none());
1552        assert_eq!(text(&name("QQ"), &hp48), Some("'QQ'".to_string()));
1553        assert_eq!(text(&Object::Program { source: None }, &hp48), None);
1554    }
1555
1556    fn real_obj(s: &str) -> Object {
1557        Object::Real { value: real(s) }
1558    }
1559
1560    #[test]
1561    fn std_numbers_as_the_48_shows_them() {
1562        let s = Settings::default();
1563        for (v, text) in [
1564            ("1", "1"),
1565            ("-2", "-2"),
1566            ("1.5", "1.5"),
1567            ("1E-5", ".00001"),
1568            (".001", ".001"),
1569            ("123456789012", "123456789012"),
1570            ("1E12", "1.E12"),
1571            ("1.5E300", "1.5E300"),
1572            ("-1.5E-300", "-1.5E-300"),
1573            ("1.23456", "1.23456"),
1574            ("0", "0"),
1575            ("100", "100"),
1576            (".5", ".5"),
1577        ] {
1578            assert_eq!(s.real(&real(v)), text, "{v}");
1579        }
1580        let p = Settings::standard(Model::Hp49g);
1581        assert_eq!(p.real(&real("5")), "5.");
1582        assert_eq!(p.real(&real("0")), "0.");
1583        assert_eq!(p.real(&real("2.5")), "2.5");
1584    }
1585
1586    #[test]
1587    fn text_is_capped() {
1588        let mut t = Text::default();
1589        for _ in 0..MAX_TEXT {
1590            t.push("ab");
1591        }
1592        let s = t.finish();
1593        assert!(s.ends_with(TRUNCATED));
1594        assert_eq!(s.chars().count(), MAX_TEXT + 1);
1595    }
1596
1597    fn cmd(name: &str, arity: u8) -> Element {
1598        Element::Command {
1599            name: Some(name.into()),
1600            address: None,
1601            xlib: None,
1602            arity: Some(arity),
1603            silent: false,
1604            syntax: None,
1605        }
1606    }
1607
1608    fn name(n: &str) -> Element {
1609        Element::Object(Object::Name { value: n.into() })
1610    }
1611
1612    fn num(v: &str) -> Element {
1613        Element::Object(Object::Real { value: real(v) })
1614    }
1615
1616    #[test]
1617    fn infix_with_precedence() {
1618        let s = Settings::default();
1619        let alg = |e: &[Element]| algebraic(e, &s).unwrap();
1620        assert_eq!(alg(&[name("A"), num("1"), cmd("+", 2)]), "'A+1'");
1621        // (A+B)*C and A+B*C.
1622        assert_eq!(
1623            alg(&[name("A"), name("B"), cmd("+", 2), name("C"), cmd("*", 2)]),
1624            "'(A+B)*C'"
1625        );
1626        assert_eq!(
1627            alg(&[name("A"), name("B"), name("C"), cmd("*", 2), cmd("+", 2)]),
1628            "'A+B*C'"
1629        );
1630        assert_eq!(
1631            alg(&[name("X"), cmd("SIN", 1), num("2"), cmd("^", 2)]),
1632            "'SIN(X)^2'"
1633        );
1634        assert_eq!(alg(&[name("A"), cmd("NEG", 1)]), "'-A'");
1635        assert_eq!(alg(&[name("A"), name("B"), cmd("MAX", 2)]), "'MAX(A,B)'");
1636        // Unknown argument count, or leftovers: no text.
1637        assert_eq!(algebraic(&[name("A"), name("B")], &s), None);
1638        assert_eq!(
1639            algebraic(
1640                &[
1641                    name("A"),
1642                    Element::Command {
1643                        name: None,
1644                        address: None,
1645                        xlib: None,
1646                        arity: None,
1647                        silent: false,
1648                        syntax: None,
1649                    }
1650                ],
1651                &s
1652            ),
1653            None
1654        );
1655    }
1656
1657    fn mark(syntax: Syntax) -> Element {
1658        Element::Command {
1659            name: None,
1660            address: None,
1661            xlib: None,
1662            arity: None,
1663            silent: true,
1664            syntax: Some(syntax),
1665        }
1666    }
1667
1668    /// A command that is no function, called in algebraic-mode syntax:
1669    /// its name as a one-element algebraic, the argument count and the
1670    /// unnamed command applying it.
1671    fn call(name: &str, arity: u8) -> [Element; 3] {
1672        [
1673            Element::Symbolic {
1674                object: Object::Algebraic { source: None },
1675                elements: vec![cmd(name, arity)],
1676            },
1677            Element::SystemBinary(u32::from(arity)),
1678            Element::Command {
1679                name: None,
1680                address: None,
1681                xlib: None,
1682                arity: None,
1683                silent: false,
1684                syntax: None,
1685            },
1686        ]
1687    }
1688
1689    fn secondary(elements: Vec<Element>, s: &Settings) -> Element {
1690        Element::Secondary {
1691            object: Object::Program {
1692                source: Some(program(&elements, s)),
1693            },
1694            elements,
1695        }
1696    }
1697
1698    fn int(v: &str) -> Element {
1699        Element::Object(Object::Integer {
1700            value: Integer::parse(v).unwrap(),
1701        })
1702    }
1703
1704    /// The 49G's algebraic-mode history echo: the program it holds is
1705    /// written as the ROM's `→STR` writes it, the echo on the stack as
1706    /// typed in algebraic mode and with its backquotes in RPN mode.
1707    #[test]
1708    fn backquoted_expressions() {
1709        let s = Settings::standard(Model::Hp49g);
1710        let alg = |e: Element| program(&[mark(Syntax::Algebraic), e], &s);
1711        // CF(-95), STO(5,'A'), DUP.
1712        let mut cf = vec![int("-95")];
1713        cf.extend(call("CF", 1));
1714        assert_eq!(alg(secondary(cf.clone(), &s)), "`CF(-95)`");
1715        let mut sto = vec![int("5"), mark(Syntax::Quote), name("A")];
1716        sto.extend(call("STO", 2));
1717        assert_eq!(alg(secondary(sto, &s)), "`STO(5,'A')`");
1718        assert_eq!(alg(secondary(call("DUP", 0).to_vec(), &s)), "`DUP`");
1719        // R→B(5)+1: a call inside an expression.
1720        let mut rb = vec![int("5")];
1721        rb.extend(call("R→B", 1));
1722        let sum = secondary(
1723            vec![
1724                mark(Syntax::Algebraic),
1725                secondary(rb, &s),
1726                int("1"),
1727                cmd("+", 2),
1728            ],
1729            &s,
1730        );
1731        assert_eq!(alg(sum), "`R→B(5)+1`");
1732        // An algebraic, a quoted one, a name, a list holding a name.
1733        let x1 = || Element::Symbolic {
1734            object: Object::Algebraic { source: None },
1735            elements: vec![name("X"), int("1"), cmd("+", 2)],
1736        };
1737        assert_eq!(alg(x1()), "`X+1`");
1738        assert_eq!(
1739            alg(secondary(vec![mark(Syntax::Quote), x1()], &s)),
1740            "`'X+1'`"
1741        );
1742        assert_eq!(alg(name("X")), "`X`");
1743        let list = Element::Object(Object::List {
1744            items: vec![
1745                Object::Integer {
1746                    value: Integer::parse("1").unwrap(),
1747                },
1748                Object::Program {
1749                    source: Some("`X`".into()),
1750                },
1751            ],
1752        });
1753        assert_eq!(alg(list), "`{1,X}`");
1754        // Followed by more: 1+2,3.
1755        assert_eq!(
1756            program(&[mark(Syntax::Algebraic), x1(), cmd(";", 0), int("3")], &s),
1757            "`X+1` ; 3"
1758        );
1759        // Nothing that forms an expression: silent, as before.
1760        let unknown = Element::Command {
1761            name: None,
1762            address: None,
1763            xlib: None,
1764            arity: None,
1765            silent: false,
1766            syntax: None,
1767        };
1768        assert_eq!(program(&[mark(Syntax::Algebraic), unknown], &s), "External");
1769        // The echo on the stack.
1770        let echo = Object::Tagged {
1771            tag: String::new(),
1772            object: Box::new(Object::Program {
1773                source: Some(alg(secondary(cf, &s))),
1774            }),
1775        };
1776        let algebraic = Settings {
1777            algebraic: true,
1778            ..s
1779        };
1780        assert_eq!(display(&echo, &algebraic), ": CF(-95)");
1781        assert_eq!(display(&echo, &s), ": `CF(-95)`");
1782        assert_eq!(
1783            edit_text(&echo, &algebraic.for_editing()).unwrap(),
1784            ":: `CF(-95)`"
1785        );
1786    }
1787
1788    /// Outside a backquoted expression an algebraic reads as before: a
1789    /// list as written on the stack, an embedded program by its text, a
1790    /// lone command no function name.
1791    #[test]
1792    fn ordinary_algebraics_keep_their_text() {
1793        let s = Settings::standard(Model::Hp49g);
1794        let list = Element::Object(Object::List {
1795            items: vec![
1796                Object::Integer {
1797                    value: Integer::parse("1").unwrap(),
1798                },
1799                Object::Algebraic {
1800                    source: Some("'X+1'".into()),
1801                },
1802            ],
1803        });
1804        assert_eq!(
1805            algebraic(&[list, int("1"), cmd("+", 2)], &s).as_deref(),
1806            Some("'{ 1 'X+1' }+1'")
1807        );
1808        // A program without « », even one that would read as infix.
1809        let body = secondary(vec![name("A"), int("1"), cmd("+", 2)], &s);
1810        assert_eq!(
1811            algebraic(&[body, int("1"), cmd("+", 2)], &s).as_deref(),
1812            Some("'A 1 ++1'")
1813        );
1814        let sin = Element::Symbolic {
1815            object: Object::Algebraic { source: None },
1816            elements: vec![cmd("SIN", 1)],
1817        };
1818        assert_eq!(algebraic(&[sin], &s), None);
1819        // Inside a marked expression, a program that forms none keeps its
1820        // text.
1821        let odd = secondary(vec![name("A"), name("B")], &s);
1822        assert_eq!(
1823            program(
1824                &[
1825                    mark(Syntax::Algebraic),
1826                    secondary(
1827                        vec![mark(Syntax::Algebraic), odd, int("1"), cmd("+", 2)],
1828                        &s
1829                    )
1830                ],
1831                &s
1832            ),
1833            "`A B+1`"
1834        );
1835    }
1836
1837    #[test]
1838    fn deep_expressions_render_without_recursion() {
1839        let s = Settings::default();
1840        let mut e = vec![name("A")];
1841        for _ in 0..200_000 {
1842            e.push(name("B"));
1843            e.push(cmd("-", 2));
1844        }
1845        let text = algebraic(&e, &s).unwrap();
1846        assert!(text.starts_with("'A-B-B"));
1847        assert!(text.ends_with(TRUNCATED));
1848    }
1849}