1use saturnus_core::{Machine, Model};
15use serde::Serialize;
16
17use crate::error::{Error, ResultExt};
18
19type Result<T, E = Error> = std::result::Result<T, E>;
20
21use crate::decompile::Settings;
22use crate::names::NameTable;
23use crate::object::{Base, Memory, Object, Reader};
24use crate::prolog::{ObjectType, Record, records};
25
26const MAX_LEVELS: usize = 100_000;
29const MAX_REGION: u32 = 1 << 20;
31
32impl Memory for Machine {
33 fn nibble(&self, addr: u32) -> Option<u8> {
34 (addr < MAX_REGION).then(|| self.peek(addr))
35 }
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct Layout {
43 pub home_ptr: u32,
45 pub home_end_ptr: u32,
47 pub context_ptr: u32,
49 pub stack_ptr: u32,
51 pub stack_end_ptr: u32,
53 pub system_flags: u32,
55 pub user_flags: u32,
57 pub flag_words: usize,
59 pub menu_ptr: u32,
63 pub menu_page: u32,
67}
68
69impl Layout {
70 pub const HP48SX: Layout = Layout {
72 home_ptr: 0x70592,
73 home_end_ptr: 0x70597,
74 context_ptr: 0x7059C,
75 stack_ptr: 0x70579,
76 stack_end_ptr: 0x7057E,
77 system_flags: 0x706C5,
78 user_flags: 0x706D5,
79 flag_words: 1,
80 menu_ptr: 0x7061E,
81 menu_page: 0x707C9,
82 };
83 pub const HP48GX: Layout = Layout {
85 home_ptr: 0x80711,
86 home_end_ptr: 0x80716,
87 context_ptr: 0x8071B,
88 stack_ptr: 0x806F8,
89 stack_end_ptr: 0x806FD,
90 system_flags: 0x80843,
91 user_flags: 0x80853,
92 flag_words: 1,
93 menu_ptr: 0x8079D,
94 menu_page: 0x80947,
95 };
96 pub const HP49G: Layout = Layout {
98 home_ptr: 0x80711,
99 home_end_ptr: 0x80716,
100 context_ptr: 0x8071B,
101 stack_ptr: 0x806F8,
102 stack_end_ptr: 0x806FD,
103 system_flags: 0x80F02,
104 user_flags: 0x80F22,
105 flag_words: 2,
106 menu_ptr: 0x807ED,
107 menu_page: 0x81026,
108 };
109
110 pub fn of(model: Model) -> Option<Layout> {
113 match model {
114 Model::Hp48sx => Some(Self::HP48SX),
115 Model::Hp48gx => Some(Self::HP48GX),
116 Model::Hp49g => Some(Self::HP49G),
117 Model::Hp38g
118 | Model::Hp39g
119 | Model::Hp40g
120 | Model::Hp42s
121 | Model::Hp27s
122 | Model::Hp17bii
123 | Model::Hp17b
124 | Model::Hp28s
125 | Model::Hp19bii => None,
126 }
127 }
128}
129
130#[derive(Clone, Debug, PartialEq, Serialize)]
133pub struct Variable {
134 pub name: String,
136 #[serde(rename = "type")]
138 pub kind: String,
139 pub size: f64,
142 pub checksum: u16,
144 pub address: u32,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub variables: Option<Vec<Variable>>,
149}
150
151#[derive(Clone, Debug, PartialEq, Eq)]
153pub struct Flags {
154 pub system: Vec<u64>,
156 pub user: Vec<u64>,
158}
159
160impl Flags {
161 pub fn get(&self, n: i32) -> Option<bool> {
164 let (words, i) = match n {
165 0 => return None,
166 n if n < 0 => (&self.system, n.unsigned_abs() - 1),
167 n => (&self.user, n.unsigned_abs() - 1),
168 };
169 let word = words.get(usize::try_from(i / 64).ok()?)?;
170 Some(word >> (i % 64) & 1 == 1)
171 }
172
173 pub fn base(&self) -> Base {
175 match (self.get(-11), self.get(-12)) {
176 (Some(true), Some(true)) => Base::Hex,
177 (Some(true), _) => Base::Oct,
178 (_, Some(true)) => Base::Bin,
179 _ => Base::Dec,
180 }
181 }
182
183 pub fn set(&self) -> Vec<i32> {
186 let mut out = Vec::new();
187 for (words, sign) in [(&self.system, -1i32), (&self.user, 1)] {
188 for (w, word) in words.iter().enumerate() {
189 for bit in 0..64 {
190 if word >> bit & 1 == 1 {
191 let n = i32::try_from(w * 64 + bit + 1).unwrap_or(i32::MAX);
192 out.push(sign * n);
193 }
194 }
195 }
196 }
197 out
198 }
199}
200
201impl Serialize for Flags {
202 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
206 use serde::ser::SerializeStruct;
207 let hex = |w: &Vec<u64>| w.iter().map(|v| format!("{v:016X}")).collect::<Vec<_>>();
208 let mut st = s.serialize_struct("Flags", 3)?;
209 st.serialize_field("system", &hex(&self.system))?;
210 st.serialize_field("user", &hex(&self.user))?;
211 st.serialize_field("set", &self.set())?;
212 st.end()
213 }
214}
215
216pub fn crc(nibbles: &[u8]) -> u16 {
219 let mut c: u32 = 0;
220 for &n in nibbles {
221 c = (c >> 4) ^ (((c ^ u32::from(n)) & 0xF) * 0x1081);
222 }
223 (c & 0xFFFF) as u16
224}
225
226#[derive(Clone, Copy)]
228pub struct UserMemory<'a> {
229 mem: &'a dyn Memory,
230 layout: Layout,
231 names: Option<&'a NameTable>,
234}
235
236impl std::fmt::Debug for UserMemory<'_> {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 f.debug_struct("UserMemory")
239 .field("layout", &self.layout)
240 .field("names", &self.names.is_some())
241 .finish_non_exhaustive()
242 }
243}
244
245struct Home {
247 addr: u32,
249 nibbles: Vec<u8>,
251 offset_at: usize,
253}
254
255impl<'a> UserMemory<'a> {
256 pub fn new(mem: &'a dyn Memory, layout: Layout) -> Self {
258 Self {
259 mem,
260 layout,
261 names: None,
262 }
263 }
264
265 pub fn with_names(self, names: &'a NameTable) -> Self {
269 Self {
270 names: Some(names),
271 ..self
272 }
273 }
274
275 pub fn of(machine: &'a Machine) -> Result<Self> {
278 let model = machine.model();
279 let layout = Layout::of(model).ok_or(Error::NoUserMemory(model))?;
280 Ok(Self::new(machine, layout))
281 }
282
283 pub fn layout(&self) -> Layout {
285 self.layout
286 }
287
288 fn nibble(&self, addr: u32) -> Result<u8> {
289 self.mem.nibble(addr).ok_or(Error::Unreadable(addr))
290 }
291
292 fn pointer(&self, addr: u32) -> Result<u32> {
294 let mut v = 0;
295 for i in (0..5).rev() {
296 v = (v << 4) | u32::from(self.nibble(addr + i)?);
297 }
298 Ok(v)
299 }
300
301 fn region(&self, from: u32, to: u32) -> Result<Vec<u8>> {
303 if from > to || to - from > MAX_REGION {
304 return Err(Error::Memory(format!(
305 "region #{from:05X}-#{to:05X} is not plausible"
306 )));
307 }
308 (from..to).map(|a| self.nibble(a)).collect()
309 }
310
311 fn home(&self) -> Result<Home> {
312 let addr = self.pointer(self.layout.home_ptr)?;
313 let end = self.pointer(self.layout.home_end_ptr)?;
314 let nibbles = self.region(addr, end).context("reading HOME")?;
315 let prolog = crate::prolog::read_field(&nibbles, 0, 5);
316 if prolog != Some(ObjectType::Directory.prolog()) {
317 return Err(Error::NotSetUp { home: addr });
318 }
319 let count = crate::prolog::read_field(&nibbles, 5, 3)
322 .ok_or_else(|| Error::Memory("HOME truncated".into()))?;
323 let offset_at = 8 + 13 * to_usize(count);
324 Ok(Home {
325 addr,
326 nibbles,
327 offset_at,
328 })
329 }
330
331 fn variables(
335 &self,
336 n: &[u8],
337 base: u32,
338 offset_at: usize,
339 depth: usize,
340 ) -> Result<Vec<Variable>> {
341 let rs = records(n, offset_at, depth)?;
342 let mut out = Vec::with_capacity(rs.len());
343 for r in rs.iter().rev() {
344 if r.name_len == 0 {
346 continue;
347 }
348 out.push(self.variable(n, base, r, depth)?);
349 }
350 Ok(out)
351 }
352
353 fn variable(&self, n: &[u8], base: u32, r: &Record, depth: usize) -> Result<Variable> {
354 let name_bytes: Vec<u8> = (0..r.name_len)
355 .map(|i| {
356 crate::prolog::read_field(n, r.name_at + 2 + 2 * i, 2)
357 .map(|b| (b & 0xFF) as u8)
358 .ok_or_else(|| Error::Memory("name truncated".into()))
359 })
360 .collect::<Result<_>>()?;
361 let object = &n[r.object..r.end()];
362 let address = base + to_u32(r.object)?;
363 let prolog = crate::prolog::read_field(object, 0, 5)
364 .ok_or_else(|| Error::Memory("object truncated".into()))?;
365 let ty = ObjectType::from_prolog(prolog);
366 let (kind, variables) = match ty {
367 Some(ObjectType::Directory) => (
368 ty.map(ObjectType::name),
369 Some(self.variables(n, base, r.object + 8, depth + 1)?),
370 ),
371 Some(t) => (Some(t.name()), None),
372 None => (
375 self.pointer(prolog)
376 .ok()
377 .and_then(ObjectType::from_prolog)
378 .map(ObjectType::name),
379 None,
380 ),
381 };
382 Ok(Variable {
383 name: crate::charset::decode(&name_bytes),
384 kind: kind.unwrap_or("ROM Pointer").to_string(),
385 size: (r.end() - r.start) as f64 / 2.0,
386 checksum: crc(object),
387 address,
388 variables,
389 })
390 }
391
392 pub fn tree(&self) -> Result<Vec<Variable>> {
397 let home = self.home()?;
398 let vars = self.variables(&home.nibbles, home.addr, home.offset_at, 0)?;
399 let end = records(&home.nibbles, home.offset_at, 0)?
401 .last()
402 .map_or(home.offset_at + 5, Record::end);
403 if end != home.nibbles.len() {
404 return Err(Error::Memory(format!(
405 "HOME's variables end at #{:05X}, HOME at #{:05X}",
406 home.addr + to_u32(end)?,
407 home.addr + to_u32(home.nibbles.len())?
408 )));
409 }
410 Ok(vars)
411 }
412
413 pub fn current_path(&self) -> Result<Vec<String>> {
415 let context = self.pointer(self.layout.context_ptr)?;
416 let home = self.pointer(self.layout.home_ptr)?;
417 let mut path = vec!["HOME".to_string()];
418 if context == home {
419 return Ok(path);
420 }
421 if find(&self.tree()?, context, &mut path) {
422 Ok(path)
423 } else {
424 Err(Error::Memory(format!(
425 "the current directory (#{context:05X}) is not under HOME"
426 )))
427 }
428 }
429
430 pub fn stack_addresses(&self) -> Result<Vec<u32>> {
432 let top = self.pointer(self.layout.stack_ptr)?;
433 let end = self.pointer(self.layout.stack_end_ptr)?;
434 let span = end
435 .checked_sub(top)
436 .filter(|s| s % 5 == 0 && *s >= 5)
437 .ok_or_else(|| {
438 Error::Memory(format!(
439 "stack pointers #{top:05X}-#{end:05X} are not plausible"
440 ))
441 })?;
442 let depth = to_usize(span / 5 - 1);
443 if depth > MAX_LEVELS {
444 return Err(Error::Memory(format!(
445 "stack deeper than {MAX_LEVELS} levels"
446 )));
447 }
448 if self.pointer(end - 5)? != 0 {
449 return Err(Error::Memory(format!(
450 "no end marker at #{:05X}: the ROM is not idle",
451 end - 5
452 )));
453 }
454 (0..depth)
455 .map(|i| {
456 let at = top + 5 * to_u32(i)?;
457 let p = self.pointer(at)?;
458 if p == 0 {
459 return Err(Error::Memory(format!(
460 "stack level {} is 0 at #{at:05X}",
461 i + 1
462 )));
463 }
464 Ok(p)
465 })
466 .collect()
467 }
468
469 pub fn stack(&self) -> Result<Vec<Object>> {
472 let flags = self.flags()?;
473 let base = flags.base();
474 let settings = self.settings(&flags);
475 let reader = self.reader(&settings);
478 self.stack_addresses()?
479 .into_iter()
480 .enumerate()
481 .map(|(i, addr)| {
482 let mut obj = reader
483 .decode_at(addr)
484 .with_context(|| format!("stack level {}", i + 1))?;
485 obj.set_base(base);
486 Ok(obj)
487 })
488 .collect()
489 }
490
491 fn settings(&self, flags: &Flags) -> Settings {
494 let model = if self.layout == Layout::HP49G {
495 Model::Hp49g
496 } else {
497 Model::Hp48gx
498 };
499 Settings::from_flags(flags, model)
500 }
501
502 fn reader(&self, settings: &Settings) -> Reader<'a> {
503 match self.names {
504 Some(names) => Reader::with_names(self.mem, names, *settings),
505 None => Reader::new(self.mem),
506 }
507 }
508
509 pub fn object_at(&self, addr: u32) -> Result<Object> {
512 let flags = self.flags()?;
513 let mut obj = self.reader(&self.settings(&flags)).decode_at(addr)?;
514 obj.set_base(flags.base());
515 Ok(obj)
516 }
517
518 pub fn stack_described(&self) -> Result<Vec<serde_json::Value>> {
523 let settings = self.settings(&self.flags()?);
524 let mut left = crate::decompile::MAX_DESCRIBED_TEXT;
525 self.stack()?
526 .iter()
527 .map(|o| crate::decompile::described_within(o, &settings, &mut left))
528 .collect()
529 }
530
531 pub fn stack_top(&self) -> Result<(usize, Option<serde_json::Value>)> {
535 let addresses = self.stack_addresses()?;
536 let top = match addresses.first() {
537 Some(&addr) => Some(self.object_described(addr).context("stack level 1")?),
538 None => None,
539 };
540 Ok((addresses.len(), top))
541 }
542
543 pub fn object_described(&self, addr: u32) -> Result<serde_json::Value> {
546 let settings = self.settings(&self.flags()?);
547 crate::decompile::described(&self.object_at(addr)?, &settings)
548 }
549
550 pub fn edit_text_at(&self, addr: u32) -> Result<String> {
555 let flags = self.flags()?;
556 let settings = self.settings(&flags).for_editing();
557 let mut obj = self.reader(&settings).decode_at(addr)?;
558 obj.set_base(flags.base());
559 crate::decompile::edit_text(&obj, &settings)
560 }
561
562 pub fn identity_at(&self, addr: u32) -> Result<(usize, u16)> {
566 let n: Vec<u8> = (addr..MAX_REGION)
567 .map(|a| self.nibble(a))
568 .collect::<Result<_>>()?;
569 let size = crate::prolog::object_size(&n, 0)?;
570 Ok((size, crc(&n[..size])))
571 }
572
573 fn words(&self, at: u32) -> Result<Vec<u64>> {
574 (0..self.layout.flag_words)
575 .map(|w| {
576 let start = at + 16 * to_u32(w)?;
577 let mut v = 0u64;
578 for i in (0..16).rev() {
579 v = (v << 4) | u64::from(self.nibble(start + i)?);
580 }
581 Ok(v)
582 })
583 .collect()
584 }
585
586 pub fn menu_pointer(&self) -> Result<u32> {
589 self.pointer(self.layout.menu_ptr)
590 }
591
592 pub fn menu_first_key(&self) -> Result<usize> {
595 let first = self.pointer(self.layout.menu_page)?;
596 first
597 .checked_sub(1)
598 .map(to_usize)
599 .ok_or_else(|| Error::Memory(format!("menu page field reads {first}")))
600 }
601
602 pub fn current_variables(&self) -> Result<Vec<Variable>> {
607 let context = self.pointer(self.layout.context_ptr)?;
608 let home = self.home()?;
609 let n = &home.nibbles;
610 let mut offset_at = home.offset_at;
611 let mut depth = 0;
612 if context != home.addr {
613 'down: loop {
614 if depth > crate::prolog::MAX_DEPTH {
615 return Err(Error::Memory("directories nested too deep".into()));
616 }
617 for r in records(n, offset_at, depth)? {
618 let (from, to) = (home.addr + to_u32(r.object)?, home.addr + to_u32(r.end())?);
619 let dir = crate::prolog::read_field(n, r.object, 5)
620 == Some(ObjectType::Directory.prolog());
621 if dir && (from..to).contains(&context) {
622 offset_at = r.object + 8;
623 depth += 1;
624 if from == context {
625 break 'down;
626 }
627 continue 'down;
628 }
629 }
630 return Err(Error::Memory(format!(
631 "the current directory (#{context:05X}) is not under HOME"
632 )));
633 }
634 }
635 let rs = records(n, offset_at, depth)?;
636 rs.iter()
637 .rev()
638 .filter(|r| r.name_len != 0)
639 .map(|r| self.shallow_variable(n, home.addr, r))
640 .collect()
641 }
642
643 fn shallow_variable(&self, n: &[u8], base: u32, r: &Record) -> Result<Variable> {
645 let name_bytes: Vec<u8> = (0..r.name_len)
646 .map(|i| {
647 crate::prolog::read_field(n, r.name_at + 2 + 2 * i, 2)
648 .map(|b| (b & 0xFF) as u8)
649 .ok_or_else(|| Error::Memory("name truncated".into()))
650 })
651 .collect::<Result<_>>()?;
652 let prolog = crate::prolog::read_field(n, r.object, 5)
653 .ok_or_else(|| Error::Memory("object truncated".into()))?;
654 let kind = ObjectType::from_prolog(prolog)
655 .or_else(|| self.pointer(prolog).ok().and_then(ObjectType::from_prolog))
656 .map_or("ROM Pointer", ObjectType::name);
657 Ok(Variable {
658 name: crate::charset::decode(&name_bytes),
659 kind: kind.to_string(),
660 size: (r.end() - r.start) as f64 / 2.0,
661 checksum: 0,
662 address: base + to_u32(r.object)?,
663 variables: None,
664 })
665 }
666
667 pub fn flags(&self) -> Result<Flags> {
669 Ok(Flags {
670 system: self.words(self.layout.system_flags)?,
671 user: self.words(self.layout.user_flags)?,
672 })
673 }
674
675 pub fn change_counter(&self) -> Result<u64> {
683 let mut h = Fnv::default();
684 let l = self.layout;
685 for p in [
686 l.home_ptr,
687 l.home_end_ptr,
688 l.context_ptr,
689 l.stack_ptr,
690 l.stack_end_ptr,
691 ] {
692 h.word(u64::from(self.pointer(p)?));
693 }
694 let home = self.pointer(l.home_ptr)?;
695 let end = self.pointer(l.home_end_ptr)?;
696 for n in self.region(home, end)? {
697 h.byte(n);
698 }
699 let top = self.pointer(l.stack_ptr)?;
701 let stack_end = self
702 .pointer(l.stack_end_ptr)?
703 .min(top + 5 * MAX_LEVELS as u32);
704 for a in (top..stack_end).step_by(5) {
705 h.word(u64::from(self.pointer(a)?));
706 }
707 let flags = self.flags()?;
708 for w in flags.system.iter().chain(&flags.user) {
709 h.word(*w);
710 }
711 Ok(h.0)
712 }
713}
714
715fn to_usize(v: u32) -> usize {
717 usize::try_from(v).unwrap_or(usize::MAX)
718}
719
720fn to_u32(v: usize) -> Result<u32> {
722 u32::try_from(v).map_err(|_| Error::Memory(format!("{v} is past the address space")))
723}
724
725fn find(vars: &[Variable], addr: u32, path: &mut Vec<String>) -> bool {
727 for v in vars {
728 let Some(children) = &v.variables else {
729 continue;
730 };
731 path.push(v.name.clone());
732 if v.address == addr || find(children, addr, path) {
733 return true;
734 }
735 path.pop();
736 }
737 false
738}
739
740struct Fnv(u64);
742
743impl Default for Fnv {
744 fn default() -> Self {
745 Fnv(0xCBF2_9CE4_8422_2325)
746 }
747}
748
749impl Fnv {
750 fn byte(&mut self, b: u8) {
751 self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01B3);
752 }
753
754 fn word(&mut self, w: u64) {
755 for b in w.to_le_bytes() {
756 self.byte(b);
757 }
758 }
759}
760
761pub fn memory_tree(machine: &Machine) -> Result<Vec<Variable>> {
763 UserMemory::of(machine)?.tree()
764}
765
766pub fn current_path(machine: &Machine) -> Result<Vec<String>> {
768 UserMemory::of(machine)?.current_path()
769}
770
771pub fn stack_objects(machine: &Machine) -> Result<Vec<Object>> {
773 UserMemory::of(machine)?.stack()
774}
775
776pub fn flags(machine: &Machine) -> Result<Flags> {
778 UserMemory::of(machine)?.flags()
779}
780
781pub fn change_counter(machine: &Machine) -> Result<u64> {
783 UserMemory::of(machine)?.change_counter()
784}
785
786#[cfg(test)]
787mod tests {
788 use super::*;
789
790 #[test]
793 fn models_without_a_memory_view() {
794 for model in [Model::Hp38g, Model::Hp39g, Model::Hp40g, Model::Hp42s] {
795 assert!(Layout::of(model).is_none(), "{}", model.name());
796 }
797 let m = Machine::new(Model::Hp42s, &vec![0u8; Model::Hp42s.rom_bytes()]).unwrap();
798 let e = UserMemory::of(&m).err().unwrap().to_string();
799 assert!(e.contains("the 42S has no RPL user memory"), "{e}");
800 let m = Machine::new(Model::Hp38g, &vec![0u8; Model::Hp38g.rom_bytes()]).unwrap();
801 let e = UserMemory::of(&m).err().unwrap().to_string();
802 assert!(e.contains("keeps aplets"), "{e}");
803 }
804 use crate::object::{MAX_DECODED_OBJECTS, Real, decode_at};
805
806 struct Ram(Vec<u8>);
808
809 impl Memory for Ram {
810 fn nibble(&self, addr: u32) -> Option<u8> {
811 let i = usize::try_from(addr.checked_sub(0x70000)?).ok()?;
812 self.0.get(i).copied()
813 }
814 }
815
816 impl Ram {
817 fn put(&mut self, addr: u32, hex: &str) {
818 let at = (addr - 0x70000) as usize;
819 for (i, c) in hex.chars().enumerate() {
820 self.0[at + i] = c.to_digit(16).unwrap() as u8;
821 }
822 }
823
824 fn put_ptr(&mut self, addr: u32, value: u32) {
825 let hex: String = (0..5)
826 .map(|i| format!("{:X}", value >> (4 * i) & 0xF))
827 .collect();
828 self.put(addr, &hex);
829 }
830 }
831
832 fn sx() -> Ram {
836 let mut r = Ram(vec![0; 0x10000]);
837 r.put(
838 0x7FE9B,
839 concat!(
840 "69A20200200746228D356007EFD2200000501000",
841 "00000069A20FF7C500000000C055375627B45697",
842 "37E2342534C0E4A20900006E57A2000805537562",
843 "7B45697378047A20B2130E10006014C61627D637",
844 "6047A20B2130080005094F40514255047A201932",
845 "24B2A24B2A24B2A23F2A29C2A2B2130630001085",
846 "10339201000000000000240B100020959520C2A2",
847 "09000084946100040449425144069A20FF7A0000",
848 "0000010A51033920000000000000005",
849 ),
850 );
851 let l = Layout::HP48SX;
852 r.put_ptr(l.home_ptr, 0x7FE9B);
853 r.put_ptr(l.home_end_ptr, 0x7FFFB);
854 r.put_ptr(l.context_ptr, 0x7FFCE);
855 r.put(0x71000, "C2A2090000142433920000000000000007");
857 r.put(0x71030, "E4A2051000A200000000000000");
858 r.put_ptr(0x7F000, 0x71000);
859 r.put_ptr(0x7F005, 0x7100E);
860 r.put_ptr(0x7F00A, 0x71030);
861 r.put_ptr(l.stack_ptr, 0x7F000);
862 r.put_ptr(l.stack_end_ptr, 0x7F014);
863 r.put(l.system_flags, "0FF0000008000000");
865 r.put(l.user_flags, "0400000000000000");
866 r
867 }
868
869 #[test]
870 fn tree_sizes_and_checksums_match_the_calculator() {
871 let ram = sx();
872 let m = UserMemory::new(&ram, Layout::HP48SX);
873 let tree = m.tree().unwrap();
874 let flat: Vec<(&str, &str, f64, u16)> = tree
876 .iter()
877 .map(|v| (v.name.as_str(), v.kind.as_str(), v.size, v.checksum))
878 .collect();
879 assert_eq!(
880 flat,
881 [
882 ("DIRA", "Directory", 31.0, 44093),
883 ("YY", "String", 13.5, 5889),
884 ("X", "Real Number", 16.0, 59472),
885 ("IOPAR", "List", 29.5, 8861),
886 ]
887 );
888 let dira = tree[0].variables.as_ref().unwrap();
889 assert_eq!(dira.len(), 1);
890 assert_eq!(
891 (dira[0].name.as_str(), dira[0].size, dira[0].checksum),
892 ("Z", 16.0, 23381)
893 );
894 assert_eq!(dira[0].address, 0x7FFE6);
895 assert_eq!(m.current_path().unwrap(), ["HOME", "DIRA"]);
896 assert_eq!(
897 decode_at(tree[2].address, &ram).unwrap(),
898 Object::Real {
899 value: Real::parse("42").unwrap()
900 }
901 );
902 }
903
904 #[test]
905 fn stack_and_flags() {
906 let ram = sx();
907 let m = UserMemory::new(&ram, Layout::HP48SX);
908 let flags = m.flags().unwrap();
909 assert_eq!(flags.get(-40), Some(true));
910 assert_eq!(flags.get(7), Some(true));
911 assert_eq!(flags.get(8), Some(false));
912 assert_eq!(flags.get(65), None);
913 assert_eq!(flags.base(), Base::Hex);
914 assert_eq!(flags.set(), [-5, -6, -7, -8, -9, -10, -11, -12, -40, 7]);
915 let json = serde_json::to_value(&flags).unwrap();
916 assert_eq!(json["system"][0], "0000008000000FF0");
917 assert_eq!(json["user"][0], "0000000000000040");
918 let stack = m.stack().unwrap();
919 assert_eq!(stack.len(), 3);
920 assert_eq!(stack[0], Object::String { value: "AB".into() });
921 assert_eq!(
922 stack[1],
923 Object::Real {
924 value: Real::parse("7").unwrap()
925 }
926 );
927 assert_eq!(
928 serde_json::to_value(&stack[2]).unwrap(),
929 serde_json::json!({"type": "binary", "value": 42, "base": "hex", "text": "# 2Ah"})
930 );
931 }
932
933 #[test]
934 fn change_counter_moves_with_the_tree_and_the_stack() {
935 let mut ram = sx();
936 let c0 = UserMemory::new(&ram, Layout::HP48SX)
937 .change_counter()
938 .unwrap();
939 assert_eq!(
940 UserMemory::new(&ram, Layout::HP48SX)
941 .change_counter()
942 .unwrap(),
943 c0
944 );
945 ram.put(0x7FF39 + 16, "3");
947 let c1 = UserMemory::new(&ram, Layout::HP48SX)
948 .change_counter()
949 .unwrap();
950 assert_ne!(c1, c0);
951 ram.put_ptr(Layout::HP48SX.stack_ptr, 0x7F005);
952 assert_ne!(
953 UserMemory::new(&ram, Layout::HP48SX)
954 .change_counter()
955 .unwrap(),
956 c1
957 );
958 }
959
960 fn shared_pointers(levels: u32) -> Ram {
963 let mut r = sx();
964 let base = 0x71000;
965 for i in 0..levels {
966 let a = base + 20 * i;
967 r.put(a, "47A20");
968 if i + 1 < levels {
969 r.put_ptr(a + 5, a + 20);
970 r.put_ptr(a + 10, a + 20);
971 r.put(a + 15, "B2130");
972 } else {
973 r.put(a + 5, "B2130");
974 }
975 }
976 r.put_ptr(0x7F000, base);
977 r.put_ptr(0x7F005, 0);
978 r.put_ptr(Layout::HP48SX.stack_ptr, 0x7F000);
979 r.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F00A);
980 r
981 }
982
983 #[test]
984 fn shared_pointers_hit_the_decode_budget() {
985 let ram = shared_pointers(40);
986 let m = UserMemory::new(&ram, Layout::HP48SX);
987 let calls: [&dyn Fn() -> Result<()>; 2] = [
988 &|| decode_at(0x71000, &ram).map(drop).map_err(Error::from),
989 &|| m.stack().map(drop),
990 ];
991 for call in calls {
992 let e = call().unwrap_err();
995 assert!(format!("{e:#}").contains("(MAX_DECODED_"), "{e:#}");
996 }
997 let ram = shared_pointers(12);
999 let obj = decode_at(0x71000, &ram).unwrap();
1000 fn count(o: &Object) -> usize {
1001 match o {
1002 Object::List { items } => 1 + items.iter().map(count).sum::<usize>(),
1003 _ => 1,
1004 }
1005 }
1006 assert_eq!(count(&obj), (1 << 12) - 1);
1007 assert!(count(&obj) < MAX_DECODED_OBJECTS);
1008 }
1009
1010 fn repeated(levels: u32, hex: &str) -> Ram {
1012 let mut r = sx();
1013 r.put(0x71000, hex);
1014 for i in 0..levels {
1015 r.put_ptr(0x7F000 + 5 * i, 0x71000);
1016 }
1017 r.put_ptr(0x7F000 + 5 * levels, 0);
1018 r.put_ptr(Layout::HP48SX.stack_ptr, 0x7F000);
1019 r.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F005 + 5 * levels);
1020 r
1021 }
1022
1023 fn string(chars: usize) -> String {
1025 let mut len = String::new();
1026 for i in 0..5 {
1027 len.push_str(&format!("{:X}", (5 + 2 * chars) >> (4 * i) & 0xF));
1028 }
1029 format!("C2A20{len}{}", "14".repeat(chars))
1030 }
1031
1032 #[test]
1033 fn repeated_large_objects_hit_the_nibble_budget() {
1034 let ram = repeated(500, &string(20_000));
1037 let m = UserMemory::new(&ram, Layout::HP48SX);
1038 for e in [m.stack().unwrap_err(), m.stack_described().unwrap_err()] {
1039 assert!(format!("{e:#}").contains("MAX_CLONED_NIBBLES"), "{e:#}");
1040 }
1041 let ram = repeated(100, &string(20_000));
1044 let stack = UserMemory::new(&ram, Layout::HP48SX).stack().unwrap();
1045 assert_eq!(stack.len(), 100);
1046 assert_eq!(
1047 stack[99],
1048 Object::String {
1049 value: "A".repeat(20_000)
1050 }
1051 );
1052 }
1053
1054 #[test]
1055 fn nested_texts_hit_the_described_budget() {
1056 let hex = format!(
1059 "{}{}{}",
1060 "47A20".repeat(60),
1061 string(2000),
1062 "B2130".repeat(60)
1063 );
1064 let ram = repeated(100, &hex);
1065 let m = UserMemory::new(&ram, Layout::HP48SX);
1066 assert_eq!(m.stack().unwrap().len(), 100);
1067 let e = m.stack_described().unwrap_err();
1068 assert!(format!("{e:#}").contains("MAX_DESCRIBED_TEXT"), "{e:#}");
1069 let ram = repeated(1, &hex);
1071 let level = &UserMemory::new(&ram, Layout::HP48SX)
1072 .stack_described()
1073 .unwrap()[0];
1074 assert_eq!(
1075 level["items"][0]["text"].as_str().map(str::len),
1076 Some(2000 + 59 * 4 + 2)
1077 );
1078 }
1079
1080 #[test]
1081 fn broken_pointers_are_errors() {
1082 let mut ram = sx();
1083 ram.put_ptr(Layout::HP48SX.home_end_ptr, 0x7FFF0);
1084 assert!(UserMemory::new(&ram, Layout::HP48SX).tree().is_err());
1085 let mut ram = sx();
1086 ram.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F013);
1087 assert!(UserMemory::new(&ram, Layout::HP48SX).stack().is_err());
1088 ram.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F00F);
1089 assert!(UserMemory::new(&ram, Layout::HP48SX).stack().is_err());
1090 }
1091}