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