1use crate::error::Error;
10use saturnus_core::{Machine, Model};
11use serde::Serialize;
12
13use crate::charset;
14use crate::object::Memory;
15
16type Result<T, E = Error> = std::result::Result<T, E>;
17
18pub const MAX_CHARS: usize = 65_536;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct EditorLayout {
27 pub text_end_ptr: u32,
30 pub stack_end_ptr: u32,
32 pub cursor: u32,
34 pub entry: u32,
36 pub line: u32,
38 pub keys: u32,
40 pub alpha_lock: u32,
42 pub program: u32,
44 pub message: u32,
48 pub message_box: bool,
50 pub chars_position: Option<u32>,
54}
55
56impl EditorLayout {
57 pub const HP48SX: EditorLayout = EditorLayout {
59 text_end_ptr: 0x70583,
60 stack_end_ptr: 0x7057E,
61 cursor: 0x70704,
62 entry: 0x70685,
63 line: 0x70687,
64 keys: 0x706C4,
65 alpha_lock: 0x70793,
66 program: 0x70794,
67 message: 0x7068B,
68 message_box: false,
69 chars_position: None,
70 };
71 pub const HP48GX: EditorLayout = EditorLayout {
73 text_end_ptr: 0x80702,
74 stack_end_ptr: 0x806FD,
75 cursor: 0x80882,
76 entry: 0x80803,
77 line: 0x80805,
78 keys: 0x80842,
79 alpha_lock: 0x80911,
80 program: 0x80912,
81 message: 0x80809,
82 message_box: false,
83 chars_position: None,
84 };
85 pub const HP49G: EditorLayout = EditorLayout {
87 text_end_ptr: 0x80702,
88 stack_end_ptr: 0x806FD,
89 cursor: 0x80F61,
90 entry: 0x80EC2,
91 line: 0x80EC4,
92 keys: 0x80F01,
93 alpha_lock: 0x80FF0,
94 program: 0x80FF1,
95 message: 0x80EC8,
96 message_box: true,
97 chars_position: Some(0x818CF),
98 };
99
100 pub fn of(model: Model) -> Option<EditorLayout> {
103 match model {
104 Model::Hp48sx => Some(Self::HP48SX),
105 Model::Hp48gx => Some(Self::HP48GX),
106 Model::Hp49g => Some(Self::HP49G),
107 Model::Hp38g
108 | Model::Hp39g
109 | Model::Hp40g
110 | Model::Hp42s
111 | Model::Hp27s
112 | Model::Hp17bii
113 | Model::Hp17b
114 | Model::Hp28s
115 | Model::Hp19bii => None,
116 }
117 }
118
119 pub fn require(model: Model) -> Result<EditorLayout> {
122 Self::of(model).ok_or(Error::NoCommandLine(model))
123 }
124}
125
126#[derive(Clone, Debug, Default, PartialEq, Eq)]
128pub struct Editor {
129 pub active: bool,
131 pub text: Vec<u8>,
133 pub cursor: usize,
135 pub insert: bool,
137 pub algebraic: bool,
139 pub program: bool,
141 pub lowercase: bool,
143 pub alpha: bool,
145 pub alpha_lock: bool,
147 pub left_shift: bool,
149 pub right_shift: bool,
151 pub message: bool,
153}
154
155#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
157pub struct CommandLine {
158 pub active: bool,
160 pub text: String,
162 pub cursor: usize,
164}
165
166fn nibble(mem: &dyn Memory, addr: u32) -> Result<u8> {
167 mem.nibble(addr).ok_or(Error::Unreadable(addr))
168}
169
170fn field(mem: &dyn Memory, addr: u32, len: u32) -> Result<u32> {
171 let mut v = 0;
172 for i in (0..len).rev() {
173 v = (v << 4) | u32::from(nibble(mem, addr + i)?);
174 }
175 Ok(v)
176}
177
178fn bit(mem: &dyn Memory, addr: u32, b: u8) -> Result<bool> {
179 Ok(nibble(mem, addr)? & (1 << b) != 0)
180}
181
182pub fn read(mem: &dyn Memory, layout: &EditorLayout) -> Result<Editor> {
184 let active = bit(mem, layout.line, 1)?;
185 let mut text = Vec::new();
186 if active {
187 let end = field(mem, layout.text_end_ptr, 5)?;
188 let floor = field(mem, layout.stack_end_ptr, 5)?;
189 if floor >= end {
190 return Err(Error::Memory(format!(
191 "the command line pointers (#{floor:05X}, #{end:05X}) are not the ROM's"
192 )));
193 }
194 let mut a = end;
195 while a >= floor + 2 {
196 a -= 2;
197 let b = field(mem, a, 2)? as u8;
198 if b == 0 {
199 break;
200 }
201 if text.len() >= MAX_CHARS {
202 return Err(Error::Memory(format!(
203 "the command line is longer than {MAX_CHARS} characters"
204 )));
205 }
206 text.push(b);
207 }
208 }
209 let cursor = field(mem, layout.cursor, 5)? as usize;
210 let keys = nibble(mem, layout.keys)?;
211 let message = if layout.message_box {
212 !active && (nibble(mem, layout.message)? == 0xF || nibble(mem, layout.message + 1)? == 0xF)
213 } else {
214 bit(mem, layout.message, 0)?
215 };
216 Ok(Editor {
217 message,
218 active,
219 cursor: if active { cursor.min(text.len()) } else { 0 },
220 text,
221 insert: bit(mem, layout.entry, 1)?,
222 algebraic: bit(mem, layout.entry, 2)?,
223 program: bit(mem, layout.program, 0)?,
224 lowercase: bit(mem, layout.line, 2)?,
225 alpha: keys & 4 != 0,
226 alpha_lock: bit(mem, layout.alpha_lock, 0)?,
227 left_shift: keys & 1 != 0,
228 right_shift: keys & 2 != 0,
229 })
230}
231
232pub fn editor(machine: &Machine) -> Result<Editor> {
234 read(machine, &EditorLayout::require(machine.model())?)
235}
236
237pub fn command_line(machine: &Machine) -> Result<CommandLine> {
239 let e = editor(machine)?;
240 Ok(CommandLine {
241 active: e.active,
242 text: charset::decode(&e.text),
243 cursor: e.cursor,
244 })
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use std::collections::HashMap;
251
252 #[derive(Default)]
253 struct Ram(HashMap<u32, u8>);
254
255 impl Memory for Ram {
256 fn nibble(&self, addr: u32) -> Option<u8> {
257 Some(self.0.get(&addr).copied().unwrap_or(0))
258 }
259 }
260
261 impl Ram {
262 fn put(&mut self, addr: u32, value: u32, len: u32) {
263 for i in 0..len {
264 self.0.insert(addr + i, ((value >> (4 * i)) & 15) as u8);
265 }
266 }
267 }
268
269 #[test]
270 fn reads_the_reversed_text_and_the_flags() {
271 let l = EditorLayout::HP48SX;
272 let mut ram = Ram::default();
273 ram.put(l.text_end_ptr, 0x7FEE3, 5);
275 ram.put(l.stack_end_ptr, 0x7FEB3, 5);
276 for (i, b) in b"FEDCBA".iter().enumerate() {
277 ram.put(0x7FED7 + 2 * i as u32, u32::from(*b), 2);
278 }
279 ram.put(l.cursor, 4, 5);
280 ram.put(l.line, 3, 1);
281 ram.put(l.entry, 3, 1);
282 ram.put(l.keys, 4, 1);
283 let e = read(&ram, &l).unwrap();
284 assert!(e.active && e.insert && !e.algebraic && e.alpha && !e.alpha_lock);
285 assert_eq!(e.text, b"ABCDEF");
286 assert_eq!(e.cursor, 4);
287 ram.put(l.line, 1, 1);
289 let e = read(&ram, &l).unwrap();
290 assert!(!e.active && e.text.is_empty() && e.cursor == 0);
291 }
292
293 #[test]
294 fn the_text_stops_at_the_stack() {
295 let l = EditorLayout::HP48GX;
296 let mut ram = Ram::default();
297 ram.put(l.text_end_ptr, 0x90010, 5);
298 ram.put(l.stack_end_ptr, 0x9000C, 5);
299 ram.put(0x9000C, 0x4242, 4);
300 ram.put(0x9000E, 0x41, 2);
301 ram.put(l.line, 2, 1);
302 assert_eq!(read(&ram, &l).unwrap().text, b"AB");
303 ram.put(l.stack_end_ptr, 0x90020, 5);
304 assert!(read(&ram, &l).is_err());
305 }
306
307 #[test]
308 fn aplet_models_have_none() {
309 assert!(EditorLayout::of(Model::Hp38g).is_none());
310 let err = EditorLayout::require(Model::Hp42s).unwrap_err();
311 assert!(err.to_string().contains("42S has no RPL command line"));
312 }
313}