1const HIGH: [&str; 32] = [
18 "∡",
19 "x\u{0304}",
20 "∇",
21 "√",
22 "∫",
23 "Σ",
24 "▶",
25 "π",
26 "∂",
27 "≤",
28 "≥",
29 "≠",
30 "α",
31 "→",
32 "←",
33 "↓",
34 "↑",
35 "γ",
36 "δ",
37 "ε",
38 "η",
39 "θ",
40 "λ",
41 "ρ",
42 "σ",
43 "τ",
44 "ω",
45 "Δ",
46 "Π",
47 "Ω",
48 "■",
49 "∞",
50];
51
52pub fn decode(bytes: &[u8]) -> String {
54 let mut s = String::with_capacity(bytes.len());
55 for &b in bytes {
56 match b {
57 0x80..=0x9F => s.push_str(HIGH[usize::from(b - 0x80)]),
58 _ => s.push(char::from(b)),
59 }
60 }
61 s
62}
63
64pub const HP28_HIGH: [char; 20] = [
68 ' ', '÷', '×', '√', '∫', 'Σ', '▶', 'π', '∂', '≤', '≥', '≠', 'α', '→', '←', 'µ', '¶', '°', '«',
69 '»',
70];
71
72pub fn decode_hp28(bytes: &[u8]) -> String {
75 bytes
76 .iter()
77 .map(|&b| match b {
78 0..=127 => char::from(b),
79 128..=147 => HP28_HIGH[usize::from(b - 128)],
80 _ => char::REPLACEMENT_CHARACTER,
81 })
82 .collect()
83}
84
85pub fn char_of(b: u8) -> String {
87 decode(&[b])
88}
89
90pub fn encode(text: &str) -> Result<Vec<u8>, char> {
95 let mut out = Vec::with_capacity(text.len());
96 let mut chars = text.chars().peekable();
97 while let Some(c) = chars.next() {
98 if c == 'x' && chars.peek() == Some(&'\u{0304}') {
99 chars.next();
100 out.push(0x81);
101 continue;
102 }
103 let mut buf = [0u8; 4];
104 let s: &str = c.encode_utf8(&mut buf);
105 if let Some(i) = HIGH.iter().position(|&h| h == s) {
106 out.push(0x80 + i as u8);
107 continue;
108 }
109 match u32::from(c) {
110 n @ (0..=0x7F | 0xA0..=0xFF) => out.push(n as u8),
111 _ => return Err(c),
112 }
113 }
114 Ok(out)
115}
116
117const HIGH_TRIGRAPHS: [&str; 32] = [
121 "\\<)", "\\x-", "\\.V", "\\v/", "\\.S", "\\GS", "\\|>", "\\pi", "\\.d", "\\<=", "\\>=", "\\=/",
122 "\\Ga", "\\->", "\\<-", "\\|v", "\\|^", "\\Gg", "\\Gd", "\\Ge", "\\Gn", "\\Gh", "\\Gl", "\\Gr",
123 "\\Gs", "\\Gt", "\\Gw", "\\GD", "\\PI", "\\GW", "\\[]", "\\oo",
124];
125
126const LATIN_TRIGRAPHS: [(u8, &str); 8] = [
128 (171, "\\<<"),
129 (176, "\\^o"),
130 (181, "\\Gm"),
131 (187, "\\>>"),
132 (215, "\\.x"),
133 (216, "\\O/"),
134 (223, "\\Gb"),
135 (247, "\\:-"),
136];
137
138pub fn encode_command(text: &str) -> Result<Vec<u8>, char> {
143 let mut out = Vec::with_capacity(text.len());
144 let mut rest = text;
145 while let Some(pos) = rest.find('\\') {
146 out.extend(encode(&rest[..pos])?);
147 let tail = &rest[pos..];
148 match trigraph_at(tail) {
149 Some((byte, len)) => {
150 out.push(byte);
151 rest = &tail[len..];
152 }
153 None => {
154 out.push(b'\\');
155 rest = &tail[1..];
156 }
157 }
158 }
159 out.extend(encode(rest)?);
160 Ok(out)
161}
162
163pub fn from_trigraphs(text: &str) -> String {
169 let mut out = String::with_capacity(text.len());
170 let mut rest = text;
171 while let Some(pos) = rest.find('\\') {
172 out.push_str(&rest[..pos]);
173 let tail = &rest[pos..];
174 if let Some(after) = tail.strip_prefix("\\\\") {
175 out.push('\\');
176 rest = after;
177 continue;
178 }
179 match trigraph_at(tail) {
180 Some((byte, len)) => {
181 out.push_str(&char_of(byte));
182 rest = &tail[len..];
183 }
184 None => {
185 out.push('\\');
186 rest = &tail[1..];
187 }
188 }
189 }
190 out.push_str(rest);
191 out
192}
193
194pub fn trigraph_at(s: &str) -> Option<(u8, usize)> {
197 if let Some(head) = s.get(..3) {
198 if let Some(i) = HIGH_TRIGRAPHS.iter().position(|t| *t == head) {
199 return Some((0x80 + i as u8, 3));
200 }
201 if let Some((b, _)) = LATIN_TRIGRAPHS.iter().find(|(_, t)| *t == head) {
202 return Some((*b, 3));
203 }
204 }
205 let digits = s.get(1..4)?;
206 if !digits.bytes().all(|d| d.is_ascii_digit()) {
207 return None;
208 }
209 let n: u16 = digits.parse().ok()?;
210 u8::try_from(n).ok().filter(|b| *b >= 128).map(|b| (b, 4))
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct NamedChar {
217 pub code: u8,
219 pub text: String,
221 pub trigraph: &'static str,
223}
224
225pub fn named_chars() -> Vec<NamedChar> {
229 let high = (0x80u8..)
230 .zip(HIGH.iter().zip(HIGH_TRIGRAPHS))
231 .map(|(code, (text, trigraph))| NamedChar {
232 code,
233 text: (*text).to_string(),
234 trigraph,
235 });
236 let latin = LATIN_TRIGRAPHS.iter().map(|&(code, trigraph)| NamedChar {
237 code,
238 text: char::from(code).to_string(),
239 trigraph,
240 });
241 high.chain(latin).collect()
242}
243
244pub fn to_trigraphs(bytes: &[u8]) -> String {
248 let mut s = String::with_capacity(bytes.len());
249 for &b in bytes {
250 match b {
251 0x80..=0x9F => s.push_str(HIGH_TRIGRAPHS[usize::from(b - 0x80)]),
252 0xA0..=0xFF => match LATIN_TRIGRAPHS.iter().find(|(l, _)| *l == b) {
253 Some((_, t)) => s.push_str(t),
254 None => s.push_str(&format!("\\{b}")),
255 },
256 _ => s.push(char::from(b)),
257 }
258 }
259 s
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 const SX_T3: &str = concat!(
269 "\\<)\\x-\\.V\\v/\\.S\\GS\\|>\\pi\\.d\\<=\\>=\\=/\\Ga\\->\\<-\\|v\\|^\\Gg\\Gd",
270 "\\Ge\\Gn\\Gh\\Gl\\Gr\\Gs\\Gt\\Gw\\GD\\PI\\GW\\[]\\oo\\160\\161\\162\\163",
271 "\\164\\165\\166\\167\\168\\169\\170\\<<\\172\\173\\174\\175\\^o\\177\\178",
272 "\\179\\180\\Gm\\182\\183\\184\\185\\186\\>>\\188\\189\\190\\191\\192\\193",
273 "\\194\\195\\196\\197\\198\\199\\200\\201\\202\\203\\204\\205\\206\\207",
274 "\\208\\209\\210\\211\\212\\213\\214\\.x\\O/\\217\\218\\219\\220\\221\\222",
275 "\\Gb\\224\\225\\226\\227\\228\\229\\230\\231\\232\\233\\234\\235\\236",
276 "\\237\\238\\239\\240\\241\\242\\243\\244\\245\\246\\:-\\248\\249\\250",
277 "\\251\\252\\253\\254\\255",
278 );
279
280 #[test]
281 fn trigraphs_match_the_calculator() {
282 let high: Vec<u8> = (128..=255).collect();
283 assert_eq!(to_trigraphs(&high), SX_T3);
284 assert_eq!(encode_command(SX_T3).unwrap(), high);
285 }
286
287 #[test]
288 fn named_chars_cover_both_tables_and_round_trip() {
289 let named = named_chars();
290 assert_eq!(named.len(), HIGH.len() + LATIN_TRIGRAPHS.len());
291 assert!(named.windows(2).all(|w| w[0].code < w[1].code));
292 for c in &named {
293 assert_eq!(
294 encode_command(c.trigraph).unwrap(),
295 [c.code],
296 "{}",
297 c.trigraph
298 );
299 assert_eq!(encode(&c.text).unwrap(), [c.code], "{}", c.text);
300 assert_eq!(decode(&[c.code]), c.text);
301 assert_eq!(to_trigraphs(&[c.code]), c.trigraph);
302 }
303 assert_eq!(named[3].trigraph, "\\v/");
304 assert_eq!(named[3].text, "√");
305 }
306
307 #[test]
308 fn command_trigraphs() {
309 assert_eq!(
310 encode_command("{ 1 2 } \\->STR").unwrap(),
311 b"{ 1 2 } \x8dSTR"
312 );
313 assert_eq!(encode_command("→STR").unwrap(), b"\x8dSTR");
314 assert_eq!(encode_command("\"a\\b\" \\1").unwrap(), b"\"a\\b\" \\1");
316 assert_eq!(encode_command("\\127 \\256").unwrap(), b"\\127 \\256");
317 assert_eq!(encode_command("\\").unwrap(), b"\\");
318 }
319
320 #[test]
321 fn trigraphs_become_their_bytes() {
322 assert_eq!(
323 encode_command("\\<< 1 \\->LIST \\>> \\GS \\160 \\q").unwrap(),
324 [
325 0xAB, b' ', b'1', b' ', 0x8D, b'L', b'I', b'S', b'T', b' ', 0xBB, b' ', 0x85, b' ',
326 160, b' ', b'\\', b'q'
327 ]
328 );
329 assert_eq!(encode_command("\\127").unwrap(), b"\\127");
330 assert_eq!(encode_command("€"), Err('€'));
331 for (i, t) in HIGH_TRIGRAPHS.iter().enumerate() {
332 assert_eq!(encode_command(t).unwrap(), [0x80 + i as u8]);
333 }
334 }
335
336 #[test]
337 fn from_trigraphs_reads_every_code_as_the_calculator_does() {
338 let high: Vec<u8> = (128..=255).collect();
340 assert_eq!(from_trigraphs(SX_T3), decode(&high));
341 assert_eq!(
342 from_trigraphs("'\\GS(I=0,1000,1/I!)' \\->NUM"),
343 "'Σ(I=0,1000,1/I!)' →NUM"
344 );
345 assert_eq!(
346 from_trigraphs("\\v/ \\pi \\<= \\>= \\=/ \\Ga \\<) \\.d \\|> \\oo \\<- \\|v \\|^"),
347 "√ π ≤ ≥ ≠ α ∡ ∂ ▶ ∞ ← ↓ ↑"
348 );
349 assert_eq!(from_trigraphs("\\<< 1 + \\>>"), "« 1 + »");
350 assert_eq!(from_trigraphs("\\x-"), "x\u{0304}");
351 assert_eq!(from_trigraphs("\"a\\\\b\""), "\"a\\b\"");
354 assert_eq!(from_trigraphs("\\\\GS"), "\\GS");
355 assert_eq!(from_trigraphs("\\q \\127 \\"), "\\q \\127 \\");
356 assert_eq!(
358 from_trigraphs("« 'Σ(I=1,10,I^2)' →NUM »"),
359 "« 'Σ(I=1,10,I^2)' →NUM »"
360 );
361 assert_eq!(from_trigraphs("\\é\\GS"), "\\éΣ");
362 assert_eq!(from_trigraphs(""), "");
363 }
364
365 #[test]
366 fn encode_is_decode_inverted() {
367 let all: Vec<u8> = (0..=255).collect();
368 assert_eq!(encode(&decode(&all)).unwrap(), all);
369 assert_eq!(
370 encode("« 1 2 + »").unwrap(),
371 [0xAB, 32, 49, 32, 50, 32, 43, 32, 0xBB]
372 );
373 assert_eq!(encode("\u{85}"), Err('\u{85}'));
374 assert_eq!(encode("€"), Err('€'));
375 }
376
377 #[test]
378 fn high_characters() {
379 assert_eq!(decode(b"AB"), "AB");
380 assert_eq!(decode(&[0x8D, 0x85, 0xAB, 0xBB]), "→Σ«»");
381 assert_eq!(decode(&[0x81]), "x\u{0304}");
382 }
383}