1use 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
24pub const MAX_TEXT: usize = 1 << 16;
26pub const TRUNCATED: &str = "…";
28pub const MAX_DESCRIBED_TEXT: usize = 1 << 22;
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum NumberFormat {
39 Std,
41 Fix(u8),
43 Sci(u8),
45 Eng(u8),
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct Settings {
52 pub format: NumberFormat,
54 pub comma: bool,
57 pub base: Base,
59 pub word_size: u32,
61 pub all_parens: bool,
63 pub real_point: bool,
66 pub power_right: bool,
69 pub group_digits: bool,
75 pub tag_colon: bool,
79 pub algebraic: bool,
83}
84
85impl Default for Settings {
86 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 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 pub fn from_flags(flags: &Flags, model: Model) -> Settings {
117 let set = |n: i32| flags.get(n).unwrap_or(false);
118 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 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 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 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 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 fn inner(&self) -> Settings {
214 Settings {
215 group_digits: false,
216 ..*self
217 }
218 }
219
220 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
228fn 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
251fn 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
261fn 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 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
295fn 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
326fn 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 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 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
370fn 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#[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
426pub 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
447fn 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
462fn 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
475pub 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
508pub 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
521pub 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
549fn 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
567fn 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
576fn 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
584pub 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
595pub(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 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
758fn 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#[derive(Clone, Debug)]
770pub(crate) enum Element {
771 Object(Object),
773 Command {
776 name: Option<String>,
777 address: Option<u32>,
778 xlib: Option<(u16, u16)>,
780 arity: Option<u8>,
781 silent: bool,
783 syntax: Option<Syntax>,
785 },
786 Symbolic {
789 object: Object,
790 elements: Vec<Element>,
791 },
792 Secondary {
795 object: Object,
796 elements: Vec<Element>,
797 },
798 SystemBinary(u32),
801 Unit(UnitOp),
803}
804
805#[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
837pub(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 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 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 t.push("'");
886 open = true;
887 glue = true;
888 } else {
889 e.token(&mut t, s);
890 }
891 }
892 t.finish()
893}
894
895#[derive(Clone, Debug)]
897enum Node {
898 Atom(String),
900 Function(String),
904 Negative(String),
907 Infix {
909 op: String,
910 prec: u8,
911 left: usize,
912 right: usize,
913 spaced: bool,
914 },
915 Prefix { op: String, prec: u8, arg: usize },
917 Postfix { op: String, arg: usize },
919 Call {
922 name: String,
923 args: Vec<usize>,
924 equation: bool,
925 },
926 Where {
928 expr: usize,
929 pairs: Vec<(usize, usize)>,
930 },
931 Derivative { var: usize, expr: usize },
933 Concat { left: usize, right: usize },
935}
936
937const 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
951fn 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 fn signed(&self, i: usize) -> bool {
992 matches!(self.nodes[i], Node::Prefix { .. } | Node::Negative(_))
993 }
994
995 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 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 let right_assoc = power_right && *prec == PREC_POWER;
1046 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 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#[derive(Clone, Copy, Debug)]
1131enum Slot {
1132 Node(usize),
1133 Count(u32),
1134}
1135
1136pub(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
1150fn 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
1162fn 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
1184fn 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
1255fn 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 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
1303fn 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 _ => match backquoted(o) {
1318 Some(inner) => t.push(inner),
1319 None => write_object(t, o, s),
1320 },
1321 }
1322 }
1323 t.push("}");
1324}
1325
1326fn unquoted(text: &str) -> &str {
1328 text.strip_prefix('\'')
1329 .and_then(|x| x.strip_suffix('\''))
1330 .unwrap_or(text)
1331}
1332
1333fn 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 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
1397pub(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 #[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("ed, &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 #[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 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 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 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 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 #[test]
1708 fn backquoted_expressions() {
1709 let s = Settings::standard(Model::Hp49g);
1710 let alg = |e: Element| program(&[mark(Syntax::Algebraic), e], &s);
1711 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 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 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 assert_eq!(
1756 program(&[mark(Syntax::Algebraic), x1(), cmd(";", 0), int("3")], &s),
1757 "`X+1` ; 3"
1758 );
1759 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 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 #[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 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 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}