1const HIGH: [&str; 32] = [
10 "∡",
11 "x\u{0304}",
12 "∇",
13 "√",
14 "∫",
15 "Σ",
16 "▶",
17 "π",
18 "∂",
19 "≤",
20 "≥",
21 "≠",
22 "α",
23 "→",
24 "←",
25 "↓",
26 "↑",
27 "γ",
28 "δ",
29 "ε",
30 "η",
31 "θ",
32 "λ",
33 "ρ",
34 "σ",
35 "τ",
36 "ω",
37 "Δ",
38 "Π",
39 "Ω",
40 "■",
41 "∞",
42];
43
44pub fn decode(bytes: &[u8]) -> String {
46 let mut s = String::with_capacity(bytes.len());
47 for &b in bytes {
48 match b {
49 0x80..=0x9F => s.push_str(HIGH[usize::from(b - 0x80)]),
50 _ => s.push(char::from(b)),
51 }
52 }
53 s
54}
55
56pub fn char_of(b: u8) -> String {
58 decode(&[b])
59}
60
61pub fn encode(text: &str) -> Result<Vec<u8>, char> {
66 let mut out = Vec::with_capacity(text.len());
67 let mut chars = text.chars().peekable();
68 while let Some(c) = chars.next() {
69 if c == 'x' && chars.peek() == Some(&'\u{0304}') {
70 chars.next();
71 out.push(0x81);
72 continue;
73 }
74 let mut buf = [0u8; 4];
75 let s: &str = c.encode_utf8(&mut buf);
76 if let Some(i) = HIGH.iter().position(|&h| h == s) {
77 out.push(0x80 + i as u8);
78 continue;
79 }
80 match u32::from(c) {
81 n @ (0..=0x7F | 0xA0..=0xFF) => out.push(n as u8),
82 _ => return Err(c),
83 }
84 }
85 Ok(out)
86}
87
88const HIGH_TRIGRAPHS: [&str; 32] = [
92 "\\<)", "\\x-", "\\.V", "\\v/", "\\.S", "\\GS", "\\|>", "\\pi", "\\.d", "\\<=", "\\>=", "\\=/",
93 "\\Ga", "\\->", "\\<-", "\\|v", "\\|^", "\\Gg", "\\Gd", "\\Ge", "\\Gn", "\\Gh", "\\Gl", "\\Gr",
94 "\\Gs", "\\Gt", "\\Gw", "\\GD", "\\PI", "\\GW", "\\[]", "\\oo",
95];
96
97const LATIN_TRIGRAPHS: [(u8, &str); 8] = [
99 (171, "\\<<"),
100 (176, "\\^o"),
101 (181, "\\Gm"),
102 (187, "\\>>"),
103 (215, "\\.x"),
104 (216, "\\O/"),
105 (223, "\\Gb"),
106 (247, "\\:-"),
107];
108
109pub fn encode_command(text: &str) -> Result<Vec<u8>, char> {
114 let mut out = Vec::with_capacity(text.len());
115 let mut rest = text;
116 while let Some(pos) = rest.find('\\') {
117 out.extend(encode(&rest[..pos])?);
118 let tail = &rest[pos..];
119 match trigraph_at(tail) {
120 Some((byte, len)) => {
121 out.push(byte);
122 rest = &tail[len..];
123 }
124 None => {
125 out.push(b'\\');
126 rest = &tail[1..];
127 }
128 }
129 }
130 out.extend(encode(rest)?);
131 Ok(out)
132}
133
134fn trigraph_at(s: &str) -> Option<(u8, usize)> {
136 if let Some(head) = s.get(..3) {
137 if let Some(i) = HIGH_TRIGRAPHS.iter().position(|t| *t == head) {
138 return Some((0x80 + i as u8, 3));
139 }
140 if let Some((b, _)) = LATIN_TRIGRAPHS.iter().find(|(_, t)| *t == head) {
141 return Some((*b, 3));
142 }
143 }
144 let digits = s.get(1..4)?;
145 if !digits.bytes().all(|d| d.is_ascii_digit()) {
146 return None;
147 }
148 let n: u16 = digits.parse().ok()?;
149 u8::try_from(n).ok().filter(|b| *b >= 128).map(|b| (b, 4))
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn trigraphs_become_their_bytes() {
158 assert_eq!(
159 encode_command("\\<< 1 \\->LIST \\>> \\GS \\160 \\q").unwrap(),
160 [
161 0xAB, b' ', b'1', b' ', 0x8D, b'L', b'I', b'S', b'T', b' ', 0xBB, b' ', 0x85, b' ',
162 160, b' ', b'\\', b'q'
163 ]
164 );
165 assert_eq!(encode_command("\\127").unwrap(), b"\\127");
166 assert_eq!(encode_command("€"), Err('€'));
167 for (i, t) in HIGH_TRIGRAPHS.iter().enumerate() {
168 assert_eq!(encode_command(t).unwrap(), [0x80 + i as u8]);
169 }
170 }
171
172 #[test]
173 fn encode_is_decode_inverted() {
174 let all: Vec<u8> = (0..=255).collect();
175 assert_eq!(encode(&decode(&all)).unwrap(), all);
176 assert_eq!(
177 encode("« 1 2 + »").unwrap(),
178 [0xAB, 32, 49, 32, 50, 32, 43, 32, 0xBB]
179 );
180 assert_eq!(encode("\u{85}"), Err('\u{85}'));
181 assert_eq!(encode("€"), Err('€'));
182 }
183
184 #[test]
185 fn high_characters() {
186 assert_eq!(decode(b"AB"), "AB");
187 assert_eq!(decode(&[0x8D, 0x85, 0xAB, 0xBB]), "→Σ«»");
188 assert_eq!(decode(&[0x81]), "x\u{0304}");
189 }
190}