1use anyhow::{Result, bail};
16use saturnus_core::Model;
17
18use crate::object::{ArrayItem, Base, Object, Real};
19use crate::ram::Flags;
20
21pub const MAX_TEXT: usize = 1 << 16;
23pub const TRUNCATED: &str = "…";
25pub const MAX_DESCRIBED_TEXT: usize = 1 << 22;
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum NumberFormat {
36 Std,
38 Fix(u8),
40 Sci(u8),
42 Eng(u8),
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct Settings {
49 pub format: NumberFormat,
51 pub comma: bool,
54 pub base: Base,
56 pub word_size: u32,
58 pub all_parens: bool,
60 pub real_point: bool,
63 pub power_right: bool,
66 pub group_digits: bool,
72 pub tag_colon: bool,
76}
77
78impl Default for Settings {
79 fn default() -> Self {
81 Settings {
82 format: NumberFormat::Std,
83 comma: false,
84 base: Base::Dec,
85 word_size: 64,
86 all_parens: false,
87 real_point: false,
88 power_right: false,
89 group_digits: false,
90 tag_colon: true,
91 }
92 }
93}
94
95impl Settings {
96 pub fn standard(model: Model) -> Settings {
98 let hp49 = model == Model::Hp49g;
99 Settings {
100 real_point: hp49,
101 power_right: hp49,
102 tag_colon: !hp49,
103 ..Settings::default()
104 }
105 }
106
107 pub fn from_flags(flags: &Flags, model: Model) -> Settings {
109 let set = |n: i32| flags.get(n).unwrap_or(false);
110 let value = |first: i32, bits: i32| {
112 (0..bits).fold(0u32, |v, k| v | (u32::from(set(-(first + k))) << k))
113 };
114 let digits = u8::try_from(value(45, 4)).unwrap_or(0);
115 let format = match (set(-49), set(-50)) {
116 (false, false) => NumberFormat::Std,
117 (true, false) => NumberFormat::Fix(digits),
118 (false, true) => NumberFormat::Sci(digits),
119 (true, true) => NumberFormat::Eng(digits),
120 };
121 let hp49 = model == Model::Hp49g;
122 Settings {
123 format,
124 comma: set(-51),
125 base: flags.base(),
126 word_size: value(5, 6) + 1,
127 all_parens: set(-53),
128 real_point: hp49,
129 power_right: hp49,
130 group_digits: hp49,
131 tag_colon: !hp49,
132 }
133 }
134
135 pub fn for_editing(self) -> Settings {
140 Settings {
141 format: NumberFormat::Std,
142 word_size: 64,
143 group_digits: false,
144 tag_colon: true,
145 ..self
146 }
147 }
148
149 pub fn binary(&self, value: u64) -> String {
151 let mask = if self.word_size >= 64 {
152 u64::MAX
153 } else {
154 (1u64 << self.word_size) - 1
155 };
156 self.base.format(value & mask)
157 }
158
159 pub fn real(&self, r: &Real) -> String {
161 let s = match self.format {
162 NumberFormat::Std => std_text(r, self.real_point),
163 NumberFormat::Fix(d) if self.group_digits => {
164 group(&fix_text(r, usize::from(d)), self.comma)
165 }
166 NumberFormat::Fix(d) => fix_text(r, usize::from(d)),
167 NumberFormat::Sci(d) => sci_text(r, usize::from(d), 1),
168 NumberFormat::Eng(d) => sci_text(r, usize::from(d), 3),
169 };
170 if self.comma {
171 s.replace('.', ",").replace('\u{1}', ".")
172 } else {
173 s
174 }
175 }
176
177 pub fn unit_number(&self, r: &Real) -> String {
182 let integer = r.exponent >= 0
183 && r.exponent < 12
184 && r.digits[(r.exponent as usize + 1).min(12)..]
185 .iter()
186 .all(|&d| d == 0);
187 let format = if integer || r.digits.iter().all(|&d| d == 0) {
188 NumberFormat::Std
189 } else {
190 self.format
191 };
192 Settings {
193 format,
194 real_point: false,
195 group_digits: false,
196 ..*self
197 }
198 .real(r)
199 }
200
201 fn inner(&self) -> Settings {
204 Settings {
205 group_digits: false,
206 ..*self
207 }
208 }
209
210 pub fn complex(&self, re: &Real, im: &Real) -> String {
212 let sep = if self.comma { ';' } else { ',' };
213 let s = self.inner();
214 format!("({}{sep}{})", s.real(re), s.real(im))
215 }
216}
217
218fn group(text: &str, comma: bool) -> String {
222 let sep = if comma { '\u{1}' } else { ',' };
223 let (sign, rest) = match text.strip_prefix('-') {
224 Some(r) => ("-", r),
225 None => ("", text),
226 };
227 if rest.contains('E') {
228 return text.to_string();
229 }
230 let (int, frac) = rest.split_once('.').unwrap_or((rest, ""));
231 let mut out = String::new();
232 for (i, c) in int.chars().enumerate() {
233 if i > 0 && (int.len() - i) % 3 == 0 {
234 out.push(sep);
235 }
236 out.push(c);
237 }
238 format!("{sign}{out}.{frac}")
239}
240
241fn significant(r: &Real) -> &[u8] {
243 let last = r.digits.iter().rposition(|&d| d != 0).unwrap_or(0);
244 &r.digits[..=last]
245}
246
247fn digit(d: u8) -> char {
248 char::from(b'0' + d)
249}
250
251fn std_text(r: &Real, real_point: bool) -> String {
253 let sign = if r.negative { "-" } else { "" };
254 let sig = significant(r);
255 if r.digits.iter().all(|&d| d == 0) {
256 return if real_point { "0.".into() } else { "0".into() };
257 }
258 let e = r.exponent;
259 let k = sig.len() as i32;
260 if (0..12).contains(&e) {
261 let int: String = (0..=e)
263 .map(|i| digit(sig.get(i as usize).copied().unwrap_or(0)))
264 .collect();
265 let frac: String = sig
266 .iter()
267 .skip((e + 1) as usize)
268 .map(|&d| digit(d))
269 .collect();
270 if frac.is_empty() {
271 let point = if real_point { "." } else { "" };
272 return format!("{sign}{int}{point}");
273 }
274 return format!("{sign}{int}.{frac}");
275 }
276 if e < 0 && -e - 1 + k <= 12 {
277 let zeros = "0".repeat((-e - 1) as usize);
278 let frac: String = sig.iter().map(|&d| digit(d)).collect();
279 return format!("{sign}.{zeros}{frac}");
280 }
281 let rest: String = sig[1..].iter().map(|&d| digit(d)).collect();
282 format!("{sign}{}.{rest}E{e}", digit(sig[0]))
283}
284
285fn rounded(r: &Real, keep: usize) -> (Vec<u8>, i32) {
288 let mut d: Vec<u8> = r.digits.to_vec();
289 let mut e = r.exponent;
290 if keep >= 12 {
291 return (d, e);
292 }
293 let up = d[keep] >= 5;
294 d.truncate(keep);
295 if up {
296 let mut i = keep;
297 loop {
298 if i == 0 {
299 d.insert(0, 1);
300 d.truncate(keep.max(1));
301 e += 1;
302 break;
303 }
304 i -= 1;
305 if d[i] == 9 {
306 d[i] = 0;
307 } else {
308 d[i] += 1;
309 break;
310 }
311 }
312 }
313 (d, e)
314}
315
316fn fix_text(r: &Real, decimals: usize) -> String {
320 if r.digits.iter().all(|&d| d == 0) {
321 return format!("0.{}", "0".repeat(decimals));
322 }
323 let sign = if r.negative { "-" } else { "" };
324 let places = |e: i32| -> i32 {
325 if e >= 0 {
326 (decimals as i32).min(11 - e)
327 } else {
328 decimals as i32
329 }
330 };
331 let keep = r.exponent + 1 + places(r.exponent);
333 let (d, e) = match keep {
334 k if k >= 1 => rounded(r, k as usize),
335 0 if r.digits[0] >= 5 => (vec![1], r.exponent + 1),
337 _ => return sci_text(r, decimals.min(11), 1),
338 };
339 if e >= 12 {
340 return sci_text(r, decimals.min(11), 1);
341 }
342 let dec = places(e).max(0);
343 let at = |place: i32| -> u8 {
344 let i = e - place;
345 if i < 0 {
346 0
347 } else {
348 d.get(i as usize).copied().unwrap_or(0)
349 }
350 };
351 let int: String = if e >= 0 {
352 (0..=e).rev().map(|p| digit(at(p))).collect()
353 } else {
354 "0".into()
355 };
356 let frac: String = (1..=dec).map(|p| digit(at(-p))).collect();
357 format!("{sign}{int}.{frac}")
358}
359
360fn sci_text(r: &Real, decimals: usize, group: i32) -> String {
364 let sign = if r.negative { "-" } else { "" };
365 if r.digits.iter().all(|&d| d == 0) {
366 let zeros = "0".repeat(decimals);
367 return format!("0.{zeros}E0");
368 }
369 let sig = (decimals + 1).min(12);
370 let (mut d, e) = rounded(r, sig);
371 let shift = e.rem_euclid(group);
372 let exp = e - shift;
373 let int_digits = (shift + 1) as usize;
374 d.resize(sig.max(int_digits), 0);
375 let int: String = d[..int_digits].iter().map(|&x| digit(x)).collect();
376 let frac: String = d[int_digits..sig.max(int_digits)]
377 .iter()
378 .map(|&x| digit(x))
379 .collect();
380 format!("{sign}{int}.{frac}E{exp}")
381}
382
383#[derive(Debug, Default)]
385pub(crate) struct Text {
386 s: String,
387 chars: usize,
388 full: bool,
389}
390
391impl Text {
392 pub(crate) fn push(&mut self, t: &str) {
393 if self.full {
394 return;
395 }
396 for c in t.chars() {
397 if self.chars == MAX_TEXT {
398 self.s.push_str(TRUNCATED);
399 self.full = true;
400 return;
401 }
402 self.s.push(c);
403 self.chars += 1;
404 }
405 }
406
407 pub(crate) fn full(&self) -> bool {
408 self.full
409 }
410
411 pub(crate) fn finish(self) -> String {
412 self.s
413 }
414}
415
416pub fn display(obj: &Object, s: &Settings) -> String {
418 let mut t = Text::default();
419 match obj {
420 Object::Real { value } => t.push(
421 &Settings {
422 group_digits: true,
423 ..*s
424 }
425 .real(value),
426 ),
427 Object::Tagged { tag, object } => {
428 t.push(tag);
429 t.push(": ");
430 write_object(&mut t, object, s);
431 }
432 _ => write_object(&mut t, obj, s),
433 }
434 t.finish()
435}
436
437pub fn has_text(obj: &Object) -> bool {
441 match obj {
442 Object::Program { source } | Object::Algebraic { source } => source.is_some(),
443 Object::Command {
444 name,
445 source,
446 address,
447 library,
448 command,
449 } => {
450 name.is_some()
451 || source.is_some()
452 || (address.is_none() && library.is_some() && command.is_some())
453 }
454 Object::Unit { unit, .. } => unit.is_some(),
455 Object::Unknown { source, .. } => source.is_some(),
456 Object::List { items } => items.iter().all(has_text),
457 Object::Tagged { object, .. } => has_text(object),
458 Object::Array { items, .. } => array_has_text(items),
459 _ => true,
460 }
461}
462
463fn array_has_text(items: &[ArrayItem]) -> bool {
464 items.iter().all(|i| match i {
465 ArrayItem::Item(o) => has_text(o),
466 ArrayItem::Row(row) => array_has_text(row),
467 })
468}
469
470pub fn text(obj: &Object, s: &Settings) -> Option<String> {
474 if !has_text(obj) {
475 return None;
476 }
477 Some(match obj {
478 Object::Name { value } | Object::LocalName { value } => format!("'{value}'"),
479 _ => display(obj, s),
480 })
481}
482
483pub fn edit_text(obj: &Object, s: &Settings) -> Result<String> {
489 if !has_text(obj) {
490 bail!("it has no text form (a graphic, library, backup, directory or code object)");
491 }
492 if holds_quote(obj) {
493 bail!("it holds a string with a \" in it, which no text can write");
494 }
495 let text = match obj {
496 Object::Tagged { .. } => element_text(obj, s).unwrap_or_default(),
497 _ => text(obj, s).unwrap_or_default(),
498 };
499 if text.chars().count() > MAX_TEXT {
500 bail!("its text is longer than {MAX_TEXT} characters");
501 }
502 Ok(text)
503}
504
505fn holds_quote(obj: &Object) -> bool {
507 match obj {
508 Object::String { value } => value.contains('"'),
509 Object::List { items } => items.iter().any(holds_quote),
510 Object::Tagged { object, .. } => holds_quote(object),
511 Object::Array { items, .. } => array_holds_quote(items),
512 _ => false,
513 }
514}
515
516fn array_holds_quote(items: &[ArrayItem]) -> bool {
517 items.iter().any(|i| match i {
518 ArrayItem::Item(o) => holds_quote(o),
519 ArrayItem::Row(row) => array_holds_quote(row),
520 })
521}
522
523fn element_text(obj: &Object, s: &Settings) -> Option<String> {
525 has_text(obj).then(|| {
526 let mut t = Text::default();
527 write_object(&mut t, obj, s);
528 t.finish()
529 })
530}
531
532fn cell_text(obj: &Object, s: &Settings) -> Option<String> {
534 match obj {
535 Object::Name { value } | Object::LocalName { value } => Some(format!("'{value}'")),
536 _ => element_text(obj, s),
537 }
538}
539
540pub fn described(obj: &Object, s: &Settings) -> Result<serde_json::Value> {
547 let mut left = MAX_DESCRIBED_TEXT;
548 described_within(obj, s, &mut left)
549}
550
551pub(crate) fn described_within(
554 obj: &Object,
555 s: &Settings,
556 left: &mut usize,
557) -> Result<serde_json::Value> {
558 let mut v = serde_json::to_value(obj)?;
559 describe(&mut v, obj, s, text(obj, s), left)?;
560 Ok(v)
561}
562
563fn describe(
564 v: &mut serde_json::Value,
565 obj: &Object,
566 s: &Settings,
567 text: Option<String>,
568 left: &mut usize,
569) -> Result<()> {
570 let Some(map) = v.as_object_mut() else {
571 return Ok(());
572 };
573 if let Some(text) = text {
574 let Some(rest) = left.checked_sub(text.chars().count()) else {
575 bail!(
576 "more than {MAX_DESCRIBED_TEXT} characters of text (MAX_DESCRIBED_TEXT): \
577 objects that repeat or nest deeply"
578 );
579 };
580 *left = rest;
581 map.insert("text".to_string(), serde_json::Value::String(text));
582 }
583 match obj {
584 Object::List { items } => {
585 if let Some(vs) = map.get_mut("items").and_then(|i| i.as_array_mut()) {
586 for (cv, c) in vs.iter_mut().zip(items) {
587 describe(cv, c, s, element_text(c, s), left)?;
588 }
589 }
590 }
591 Object::Tagged { object, .. } => {
592 if let Some(cv) = map.get_mut("object") {
593 describe(cv, object, s, element_text(object, s), left)?;
594 }
595 }
596 Object::Array { items, .. } => {
597 if let Some(vs) = map.get_mut("items").and_then(|i| i.as_array_mut()) {
598 describe_array(vs, items, s, left)?;
599 }
600 }
601 _ => {}
602 }
603 Ok(())
604}
605
606fn describe_array(
607 vs: &mut [serde_json::Value],
608 items: &[ArrayItem],
609 s: &Settings,
610 left: &mut usize,
611) -> Result<()> {
612 for (cv, item) in vs.iter_mut().zip(items) {
613 match item {
614 ArrayItem::Item(o) => describe(cv, o, s, cell_text(o, s), left)?,
615 ArrayItem::Row(row) => {
616 if let Some(rv) = cv.as_array_mut() {
617 describe_array(rv, row, s, left)?;
618 }
619 }
620 }
621 }
622 Ok(())
623}
624
625pub(crate) fn write_object(t: &mut Text, obj: &Object, s: &Settings) {
626 if t.full() {
627 return;
628 }
629 match obj {
630 Object::Real { value } => t.push(&s.real(value)),
631 Object::Integer { value } => t.push(&value.to_source()),
632 Object::Complex { re, im } => t.push(&s.complex(re, im)),
633 Object::String { value } => {
634 t.push("\"");
635 t.push(value);
636 t.push("\"");
637 }
638 Object::Name { value } | Object::LocalName { value } => t.push(value),
639 Object::Character { value } => {
640 t.push("CHR ");
641 t.push(value);
642 }
643 Object::Binary { value, .. } => t.push(&s.binary(*value)),
644 Object::List { items } => {
645 t.push("{");
646 for o in items {
647 t.push(" ");
648 write_object(t, o, s);
649 }
650 t.push(" }");
651 }
652 Object::Tagged { tag, object } => {
653 if s.tag_colon {
654 t.push(":");
655 }
656 t.push(tag);
657 t.push(": ");
658 write_object(t, object, s);
659 }
660 Object::Unit { value, unit } => {
661 t.push(&s.unit_number(value));
662 t.push("_");
663 t.push(unit.as_deref().unwrap_or("?"));
664 }
665 Object::Array { items, .. } => write_array(t, items, s),
666 Object::Program { source } | Object::Algebraic { source } => {
667 t.push(source.as_deref().unwrap_or("?"));
668 }
669 Object::Command {
670 name,
671 source,
672 address,
673 library,
674 command,
675 } => match name.as_deref().or(source.as_deref()) {
676 Some(text) => t.push(text),
677 None => t.push(&command_text(None, *address, library.zip(*command))),
678 },
679 Object::Unknown { kind, source, .. } => match source {
680 Some(src) => t.push(src),
681 None => t.push(kind.as_deref().unwrap_or("External")),
682 },
683 }
684}
685
686fn write_array(t: &mut Text, items: &[ArrayItem], s: &Settings) {
687 t.push("[");
688 for (i, item) in items.iter().enumerate() {
689 match item {
690 ArrayItem::Item(o) => {
691 t.push(" ");
692 if let Object::Name { value } | Object::LocalName { value } = o.as_ref() {
694 t.push("'");
695 t.push(value);
696 t.push("'");
697 } else {
698 write_object(t, o, s);
699 }
700 }
701 ArrayItem::Row(row) => {
702 t.push(if i == 0 { "" } else { " " });
703 write_array(t, row, s);
704 }
705 }
706 }
707 if matches!(items.first(), Some(ArrayItem::Row(_))) {
708 t.push("]");
709 } else {
710 t.push(" ]");
711 }
712}
713
714fn command_text(name: Option<&str>, address: Option<u32>, xlib: Option<(u16, u16)>) -> String {
717 match (name, address, xlib) {
718 (Some(n), _, _) => n.to_string(),
719 (None, None, Some((l, c))) => format!("XLIB {l} {c}"),
720 _ => "External".to_string(),
721 }
722}
723
724#[derive(Clone, Debug)]
726pub(crate) enum Element {
727 Object(Object),
729 Command {
732 name: Option<String>,
733 address: Option<u32>,
734 xlib: Option<(u16, u16)>,
736 arity: Option<u8>,
737 silent: bool,
739 },
740 Symbolic {
743 object: Object,
744 elements: Vec<Element>,
745 },
746 SystemBinary(u32),
749 Unit(UnitOp),
751}
752
753#[derive(Clone, Copy, Debug, PartialEq, Eq)]
755pub(crate) enum UnitOp {
756 Times,
757 Divide,
758 Power,
759 Prefix,
760 End,
761}
762
763impl Element {
764 fn token(&self, t: &mut Text, s: &Settings) {
765 match self {
766 Element::Object(o) | Element::Symbolic { object: o, .. } => write_object(t, o, s),
767 Element::Command {
768 name,
769 address,
770 xlib,
771 ..
772 } => t.push(&command_text(name.as_deref(), *address, *xlib)),
773 Element::SystemBinary(v) => t.push(&format!("<{v:X}h>")),
774 Element::Unit(_) => t.push("External"),
775 }
776 }
777
778 fn is_quote(&self) -> bool {
779 matches!(self, Element::Command { name: Some(n), .. } if n == "'")
780 }
781}
782
783pub(crate) fn program(elements: &[Element], s: &Settings) -> String {
788 let mut t = Text::default();
789 let mut first = true;
790 let mut open = false;
791 let mut glue = false;
792 for e in elements {
793 if t.full() {
794 break;
795 }
796 if matches!(e, Element::Command { silent: true, .. }) {
797 continue;
798 }
799 if e.is_quote() && open {
800 t.push("'");
802 open = false;
803 continue;
804 }
805 if !first && !glue {
806 t.push(" ");
807 }
808 first = false;
809 glue = false;
810 if e.is_quote() {
811 t.push("'");
813 open = true;
814 glue = true;
815 } else {
816 e.token(&mut t, s);
817 }
818 }
819 t.finish()
820}
821
822#[derive(Clone, Debug)]
824enum Node {
825 Atom(String),
827 Negative(String),
830 Infix {
832 op: String,
833 prec: u8,
834 left: usize,
835 right: usize,
836 spaced: bool,
837 },
838 Prefix { op: String, prec: u8, arg: usize },
840 Postfix { op: String, arg: usize },
842 Call {
845 name: String,
846 args: Vec<usize>,
847 equation: bool,
848 },
849 Where {
851 expr: usize,
852 pairs: Vec<(usize, usize)>,
853 },
854 Derivative { var: usize, expr: usize },
856 Concat { left: usize, right: usize },
858}
859
860const ATOM: u8 = 20;
862const PREC_ROOT: u8 = 13;
863const PREC_POWER: u8 = 12;
864const PREC_NEG: u8 = 11;
865const PREC_PRODUCT: u8 = 10;
866const PREC_SUM: u8 = 9;
867const PREC_COMPARE: u8 = 7;
868const PREC_NOT: u8 = 6;
869const PREC_AND: u8 = 5;
870const PREC_OR: u8 = 4;
871const PREC_EQUATION: u8 = 3;
872const PREC_WHERE: u8 = 2;
873
874fn infix(name: &str) -> Option<(u8, bool)> {
876 Some(match name {
877 "^" => (PREC_POWER, false),
878 "*" | "/" => (PREC_PRODUCT, false),
879 "+" | "-" => (PREC_SUM, false),
880 "==" | "≠" | "<" | ">" | "≤" | "≥" => (PREC_COMPARE, false),
881 "AND" => (PREC_AND, true),
882 "OR" | "XOR" => (PREC_OR, true),
883 "=" => (PREC_EQUATION, false),
884 "|" => (PREC_WHERE, false),
885 _ => return None,
886 })
887}
888
889struct Tree {
890 nodes: Vec<Node>,
891}
892
893impl Tree {
894 fn add(&mut self, n: Node) -> usize {
895 self.nodes.push(n);
896 self.nodes.len() - 1
897 }
898
899 fn prec(&self, i: usize) -> u8 {
900 match &self.nodes[i] {
901 Node::Atom(_) | Node::Call { .. } | Node::Derivative { .. } | Node::Concat { .. } => {
902 ATOM
903 }
904 Node::Where { .. } => PREC_WHERE,
905 Node::Negative(_) => PREC_NEG,
906 Node::Infix { prec, .. } | Node::Prefix { prec, .. } => *prec,
907 Node::Postfix { .. } => ATOM - 1,
908 }
909 }
910
911 fn signed(&self, i: usize) -> bool {
915 matches!(self.nodes[i], Node::Prefix { .. } | Node::Negative(_))
916 }
917
918 fn render(&self, root: usize, t: &mut Text, all_parens: bool, power_right: bool) {
919 enum Work<'a> {
920 Node(usize),
921 Text(&'a str),
922 Owned(String),
923 }
924 let mut stack = vec![Work::Node(root)];
925 while let Some(w) = stack.pop() {
926 if t.full() {
927 return;
928 }
929 let i = match w {
930 Work::Text(s) => {
931 t.push(s);
932 continue;
933 }
934 Work::Owned(s) => {
935 t.push(&s);
936 continue;
937 }
938 Work::Node(i) => i,
939 };
940 let operand = |stack: &mut Vec<Work<'_>>, child: usize, parens: bool| {
942 let compound = !matches!(
943 self.nodes[child],
944 Node::Atom(_) | Node::Call { .. } | Node::Derivative { .. }
945 );
946 if parens || (all_parens && compound) {
947 stack.push(Work::Text(")"));
948 stack.push(Work::Node(child));
949 stack.push(Work::Text("("));
950 } else {
951 stack.push(Work::Node(child));
952 }
953 };
954 match &self.nodes[i] {
955 Node::Atom(s) | Node::Negative(s) => t.push(s),
956 Node::Infix {
957 op,
958 prec,
959 left,
960 right,
961 spaced,
962 } => {
963 let right_assoc = power_right && *prec == PREC_POWER;
967 let where_left = matches!(self.nodes[*left], Node::Where { .. });
972 let lp = !where_left
973 && if right_assoc {
974 self.prec(*left) <= *prec
975 } else {
976 self.prec(*left) < *prec
977 };
978 let rp = if right_assoc {
979 self.prec(*right) < *prec
980 } else {
981 self.prec(*right) <= *prec
982 } && !self.signed(*right);
983 operand(&mut stack, *right, rp);
984 if *spaced {
985 stack.push(Work::Owned(format!(" {op} ")));
986 } else {
987 stack.push(Work::Owned(op.clone()));
988 }
989 operand(&mut stack, *left, lp);
990 }
991 Node::Prefix { op, prec, arg } => {
992 let p = self.prec(*arg) < *prec && !self.signed(*arg);
993 operand(&mut stack, *arg, p);
994 t.push(if p { op.trim_end() } else { op });
996 }
997 Node::Postfix { op, arg } => {
998 stack.push(Work::Owned(op.clone()));
999 let p = self.prec(*arg) < ATOM;
1000 operand(&mut stack, *arg, p);
1001 }
1002 Node::Call {
1003 name,
1004 args,
1005 equation,
1006 } => {
1007 stack.push(Work::Text(")"));
1008 for (k, a) in args.iter().enumerate().rev() {
1009 stack.push(Work::Node(*a));
1010 if k == 1 && *equation {
1011 stack.push(Work::Text("="));
1012 } else if k > 0 {
1013 stack.push(Work::Text(","));
1014 }
1015 }
1016 t.push(name);
1017 t.push("(");
1018 }
1019 Node::Where { expr, pairs } => {
1020 stack.push(Work::Text(")"));
1021 for (k, (n, v)) in pairs.iter().enumerate().rev() {
1022 stack.push(Work::Node(*v));
1023 stack.push(Work::Text("="));
1024 stack.push(Work::Node(*n));
1025 if k > 0 {
1026 stack.push(Work::Text(","));
1027 }
1028 }
1029 stack.push(Work::Text("|("));
1030 let p = self.prec(*expr) <= PREC_WHERE;
1031 operand(&mut stack, *expr, p);
1032 }
1033 Node::Derivative { var, expr } => {
1034 stack.push(Work::Text(")"));
1035 stack.push(Work::Node(*expr));
1036 stack.push(Work::Text("("));
1037 stack.push(Work::Node(*var));
1038 t.push("∂");
1039 }
1040 Node::Concat { left, right } => {
1041 stack.push(Work::Node(*right));
1042 stack.push(Work::Node(*left));
1043 }
1044 }
1045 }
1046 }
1047}
1048
1049#[derive(Clone, Copy, Debug)]
1052enum Slot {
1053 Node(usize),
1054 Count(u32),
1055}
1056
1057pub(crate) fn algebraic(elements: &[Element], s: &Settings) -> Option<String> {
1061 let s = &s.inner();
1062 let mut tree = Tree { nodes: Vec::new() };
1063 let root = expression(elements, s, &mut tree)?;
1064 let mut t = Text::default();
1065 t.push("'");
1066 tree.render(root, &mut t, s.all_parens, s.power_right);
1067 t.push("'");
1068 Some(t.finish())
1069}
1070
1071fn leaf(o: &Object, s: &Settings) -> Node {
1072 let mut t = Text::default();
1073 write_object(&mut t, o, s);
1074 let text = t.finish();
1075 let negative = match o {
1076 Object::Real { value } => value.negative,
1077 Object::Integer { value } => value.negative,
1078 Object::Unit { value, .. } => value.negative,
1079 _ => false,
1080 };
1081 if negative {
1082 Node::Negative(text)
1083 } else {
1084 Node::Atom(text)
1085 }
1086}
1087
1088fn expression(elements: &[Element], s: &Settings, tree: &mut Tree) -> Option<usize> {
1089 let mut stack: Vec<Slot> = Vec::new();
1090 for e in elements {
1091 let node = match e {
1092 Element::Object(o) => tree.add(leaf(o, s)),
1093 Element::Symbolic { elements, .. } => expression(elements, s, tree)?,
1094 Element::SystemBinary(v) => {
1095 stack.push(Slot::Count(*v));
1096 continue;
1097 }
1098 Element::Unit(_) => return None,
1099 Element::Command { name, arity, .. } => {
1100 if let Some(Slot::Count(n)) = stack.last().copied() {
1101 stack.pop();
1102 counted(name.as_deref(), n, &mut stack, tree)?
1103 } else {
1104 apply(name.as_deref()?, *arity, &mut stack, tree)?
1105 }
1106 }
1107 };
1108 stack.push(Slot::Node(node));
1109 }
1110 match stack.as_slice() {
1111 [Slot::Node(n)] => Some(*n),
1112 _ => None,
1113 }
1114}
1115
1116fn counted(name: Option<&str>, n: u32, stack: &mut Vec<Slot>, tree: &mut Tree) -> Option<usize> {
1121 let mut pop = || match stack.pop()? {
1122 Slot::Node(n) => Some(n),
1123 Slot::Count(_) => None,
1124 };
1125 match name {
1126 Some("|") => {
1127 let mut items = Vec::new();
1128 for _ in 0..n {
1129 items.push(pop()?);
1130 }
1131 items.reverse();
1132 let (&expr, rest) = items.split_first()?;
1133 if rest.len() % 2 != 0 {
1134 return None;
1135 }
1136 let pairs = rest.chunks(2).map(|p| (p[0], p[1])).collect();
1137 Some(tree.add(Node::Where { expr, pairs }))
1138 }
1139 None => {
1140 let f = pop()?;
1141 let mut args = Vec::new();
1142 for _ in 0..n {
1143 args.push(pop()?);
1144 }
1145 args.reverse();
1146 let Node::Atom(name) = &tree.nodes[f] else {
1147 return None;
1148 };
1149 let name = name.clone();
1150 Some(tree.add(Node::Call {
1151 name,
1152 args,
1153 equation: false,
1154 }))
1155 }
1156 Some(_) => None,
1157 }
1158}
1159
1160fn apply(name: &str, arity: Option<u8>, stack: &mut Vec<Slot>, tree: &mut Tree) -> Option<usize> {
1162 let mut pop = || match stack.pop()? {
1163 Slot::Node(n) => Some(n),
1164 Slot::Count(_) => None,
1165 };
1166 let arity = arity?;
1169 if arity == 2
1170 && let Some((prec, spaced)) = infix(name)
1171 {
1172 let right = pop()?;
1173 let left = pop()?;
1174 return Some(tree.add(Node::Infix {
1175 op: name.to_string(),
1176 prec,
1177 left,
1178 right,
1179 spaced,
1180 }));
1181 }
1182 let unary = |op: &str, prec: u8, arg: usize, tree: &mut Tree| {
1183 tree.add(Node::Prefix {
1184 op: op.to_string(),
1185 prec,
1186 arg,
1187 })
1188 };
1189 if arity == 1 {
1190 match name {
1191 "NEG" => return Some(unary("-", PREC_NEG, pop()?, tree)),
1192 "NOT" => return Some(unary("NOT ", PREC_NOT, pop()?, tree)),
1193 "√" => return Some(unary("√", PREC_ROOT, pop()?, tree)),
1194 "!" => {
1195 let arg = pop()?;
1196 return Some(tree.add(Node::Postfix {
1197 op: "!".into(),
1198 arg,
1199 }));
1200 }
1201 _ => {}
1202 }
1203 }
1204 if name == "∂" && arity == 2 {
1205 let expr = pop()?;
1206 let var = pop()?;
1207 return Some(tree.add(Node::Derivative { var, expr }));
1208 }
1209 let mut args = Vec::new();
1210 for _ in 0..arity {
1211 args.push(pop()?);
1212 }
1213 args.reverse();
1214 if arity == 0 {
1215 return Some(tree.add(Node::Atom(name.to_string())));
1216 }
1217 Some(tree.add(Node::Call {
1218 name: name.to_string(),
1219 args,
1220 equation: name == "Σ" && arity == 4,
1221 }))
1222}
1223
1224pub(crate) fn unit(elements: &[Element], s: &Settings) -> Option<String> {
1227 let mut tree = Tree { nodes: Vec::new() };
1228 let mut stack: Vec<usize> = Vec::new();
1229 let mut ended = false;
1230 for e in elements {
1231 if ended {
1232 return None;
1233 }
1234 match e {
1235 Element::Object(Object::String { value })
1236 | Element::Object(Object::Character { value }) => {
1237 stack.push(tree.add(Node::Atom(value.clone())));
1238 }
1239 Element::Object(Object::Real { value }) => {
1240 stack.push(tree.add(Node::Atom(s.unit_number(value))));
1241 }
1242 Element::Object(o @ Object::Integer { .. }) => {
1243 stack.push(tree.add(leaf(o, s)));
1244 }
1245 Element::Unit(op) => {
1246 if *op == UnitOp::End {
1247 ended = true;
1248 continue;
1249 }
1250 let right = stack.pop()?;
1251 let left = stack.pop()?;
1252 let node = match op {
1253 UnitOp::Prefix => Node::Concat { left, right },
1254 UnitOp::Times | UnitOp::Divide | UnitOp::Power => {
1255 let (op, prec) = match op {
1256 UnitOp::Times => ("*", PREC_PRODUCT),
1257 UnitOp::Divide => ("/", PREC_PRODUCT),
1258 _ => ("^", PREC_POWER),
1259 };
1260 Node::Infix {
1261 op: op.into(),
1262 prec,
1263 left,
1264 right,
1265 spaced: false,
1266 }
1267 }
1268 UnitOp::End => return None,
1269 };
1270 stack.push(tree.add(node));
1271 }
1272 _ => return None,
1273 }
1274 }
1275 let [root] = stack.as_slice() else {
1276 return None;
1277 };
1278 let mut t = Text::default();
1279 tree.render(*root, &mut t, false, false);
1280 Some(t.finish())
1281}
1282
1283#[cfg(test)]
1284mod tests {
1285 use super::*;
1286
1287 fn real(s: &str) -> Real {
1288 Real::parse(s).unwrap()
1289 }
1290
1291 #[test]
1294 fn edit_text_compiles_back() {
1295 let fix = Settings {
1296 format: NumberFormat::Fix(2),
1297 base: Base::Hex,
1298 word_size: 16,
1299 tag_colon: false,
1300 group_digits: true,
1301 ..Settings::standard(Model::Hp49g)
1302 }
1303 .for_editing();
1304 let real = real_obj("1234.56789");
1305 assert_eq!(edit_text(&real, &fix).unwrap(), "1234.56789");
1306 let tagged = Object::Tagged {
1307 tag: "T".into(),
1308 object: Box::new(real.clone()),
1309 };
1310 assert_eq!(edit_text(&tagged, &fix).unwrap(), ":T: 1234.56789");
1311 let bin = Object::Binary {
1312 value: 0x12345,
1313 base: None,
1314 text: None,
1315 };
1316 assert_eq!(edit_text(&bin, &fix).unwrap(), "# 12345h");
1317 let quoted = Object::List {
1318 items: vec![Object::String {
1319 value: "say \"hi\"".into(),
1320 }],
1321 };
1322 assert!(
1323 edit_text("ed, &fix)
1324 .unwrap_err()
1325 .to_string()
1326 .contains('"')
1327 );
1328 let unknown = Object::Program { source: None };
1329 assert!(edit_text(&unknown, &fix).is_err());
1330 }
1331
1332 #[test]
1336 fn described_carries_the_text_of_every_object() {
1337 let name = |n: &str| Object::Name {
1338 value: n.to_string(),
1339 };
1340 let hp49 = Settings::standard(Model::Hp49g);
1341 let hp48 = Settings::standard(Model::Hp48gx);
1342 let array = Object::Array {
1343 dims: vec![1],
1344 items: vec![ArrayItem::Item(Box::new(name("A")))],
1345 };
1346 let v = described(&array, &hp49).unwrap();
1347 assert_eq!(v["text"], "[ 'A' ]");
1348 assert_eq!(v["items"][0]["text"], "'A'");
1349 let unit = Object::Unit {
1350 value: real("2"),
1351 unit: Some("m".to_string()),
1352 };
1353 assert_eq!(described(&unit, &hp49).unwrap()["text"], "2_m");
1354 assert_eq!(text(&real_obj("2"), &hp49), Some("2.".to_string()));
1355 assert_eq!(
1356 described(&real_obj("1.23456789012E-5"), &hp48).unwrap()["text"],
1357 "1.23456789012E-5"
1358 );
1359 let list = Object::List {
1362 items: vec![
1363 real_obj("1"),
1364 name("QQ"),
1365 Object::Tagged {
1366 tag: "T".to_string(),
1367 object: Box::new(real_obj("2")),
1368 },
1369 Object::Program { source: None },
1370 ],
1371 };
1372 let v = described(&list, &hp48).unwrap();
1373 assert!(v.get("text").is_none(), "{v}");
1374 assert_eq!(v["items"][1]["text"], "QQ");
1375 assert_eq!(v["items"][2]["text"], ":T: 2");
1376 assert_eq!(v["items"][2]["object"]["text"], "2");
1377 assert!(v["items"][3].get("text").is_none());
1378 assert_eq!(text(&name("QQ"), &hp48), Some("'QQ'".to_string()));
1379 assert_eq!(text(&Object::Program { source: None }, &hp48), None);
1380 }
1381
1382 fn real_obj(s: &str) -> Object {
1383 Object::Real { value: real(s) }
1384 }
1385
1386 #[test]
1387 fn std_numbers_as_the_48_shows_them() {
1388 let s = Settings::default();
1389 for (v, text) in [
1390 ("1", "1"),
1391 ("-2", "-2"),
1392 ("1.5", "1.5"),
1393 ("1E-5", ".00001"),
1394 (".001", ".001"),
1395 ("123456789012", "123456789012"),
1396 ("1E12", "1.E12"),
1397 ("1.5E300", "1.5E300"),
1398 ("-1.5E-300", "-1.5E-300"),
1399 ("1.23456", "1.23456"),
1400 ("0", "0"),
1401 ("100", "100"),
1402 (".5", ".5"),
1403 ] {
1404 assert_eq!(s.real(&real(v)), text, "{v}");
1405 }
1406 let p = Settings::standard(Model::Hp49g);
1407 assert_eq!(p.real(&real("5")), "5.");
1408 assert_eq!(p.real(&real("0")), "0.");
1409 assert_eq!(p.real(&real("2.5")), "2.5");
1410 }
1411
1412 #[test]
1413 fn text_is_capped() {
1414 let mut t = Text::default();
1415 for _ in 0..MAX_TEXT {
1416 t.push("ab");
1417 }
1418 let s = t.finish();
1419 assert!(s.ends_with(TRUNCATED));
1420 assert_eq!(s.chars().count(), MAX_TEXT + 1);
1421 }
1422
1423 fn cmd(name: &str, arity: u8) -> Element {
1424 Element::Command {
1425 name: Some(name.into()),
1426 address: None,
1427 xlib: None,
1428 arity: Some(arity),
1429 silent: false,
1430 }
1431 }
1432
1433 fn name(n: &str) -> Element {
1434 Element::Object(Object::Name { value: n.into() })
1435 }
1436
1437 fn num(v: &str) -> Element {
1438 Element::Object(Object::Real { value: real(v) })
1439 }
1440
1441 #[test]
1442 fn infix_with_precedence() {
1443 let s = Settings::default();
1444 let alg = |e: &[Element]| algebraic(e, &s).unwrap();
1445 assert_eq!(alg(&[name("A"), num("1"), cmd("+", 2)]), "'A+1'");
1446 assert_eq!(
1448 alg(&[name("A"), name("B"), cmd("+", 2), name("C"), cmd("*", 2)]),
1449 "'(A+B)*C'"
1450 );
1451 assert_eq!(
1452 alg(&[name("A"), name("B"), name("C"), cmd("*", 2), cmd("+", 2)]),
1453 "'A+B*C'"
1454 );
1455 assert_eq!(
1456 alg(&[name("X"), cmd("SIN", 1), num("2"), cmd("^", 2)]),
1457 "'SIN(X)^2'"
1458 );
1459 assert_eq!(alg(&[name("A"), cmd("NEG", 1)]), "'-A'");
1460 assert_eq!(alg(&[name("A"), name("B"), cmd("MAX", 2)]), "'MAX(A,B)'");
1461 assert_eq!(algebraic(&[name("A"), name("B")], &s), None);
1463 assert_eq!(
1464 algebraic(
1465 &[
1466 name("A"),
1467 Element::Command {
1468 name: None,
1469 address: None,
1470 xlib: None,
1471 arity: None,
1472 silent: false,
1473 }
1474 ],
1475 &s
1476 ),
1477 None
1478 );
1479 }
1480
1481 #[test]
1482 fn deep_expressions_render_without_recursion() {
1483 let s = Settings::default();
1484 let mut e = vec![name("A")];
1485 for _ in 0..200_000 {
1486 e.push(name("B"));
1487 e.push(cmd("-", 2));
1488 }
1489 let text = algebraic(&e, &s).unwrap();
1490 assert!(text.starts_with("'A-B-B"));
1491 assert!(text.ends_with(TRUNCATED));
1492 }
1493}