const HIGH: [&str; 32] = [
"∡",
"x\u{0304}",
"∇",
"√",
"∫",
"Σ",
"▶",
"π",
"∂",
"≤",
"≥",
"≠",
"α",
"→",
"←",
"↓",
"↑",
"γ",
"δ",
"ε",
"η",
"θ",
"λ",
"ρ",
"σ",
"τ",
"ω",
"Δ",
"Π",
"Ω",
"■",
"∞",
];
pub fn decode(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len());
for &b in bytes {
match b {
0x80..=0x9F => s.push_str(HIGH[usize::from(b - 0x80)]),
_ => s.push(char::from(b)),
}
}
s
}
pub const HP28_HIGH: [char; 20] = [
' ', '÷', '×', '√', '∫', 'Σ', '▶', 'π', '∂', '≤', '≥', '≠', 'α', '→', '←', 'µ', '¶', '°', '«',
'»',
];
pub fn decode_hp28(bytes: &[u8]) -> String {
bytes
.iter()
.map(|&b| match b {
0..=127 => char::from(b),
128..=147 => HP28_HIGH[usize::from(b - 128)],
_ => char::REPLACEMENT_CHARACTER,
})
.collect()
}
pub fn char_of(b: u8) -> String {
decode(&[b])
}
pub fn encode(text: &str) -> Result<Vec<u8>, char> {
let mut out = Vec::with_capacity(text.len());
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
if c == 'x' && chars.peek() == Some(&'\u{0304}') {
chars.next();
out.push(0x81);
continue;
}
let mut buf = [0u8; 4];
let s: &str = c.encode_utf8(&mut buf);
if let Some(i) = HIGH.iter().position(|&h| h == s) {
out.push(0x80 + i as u8);
continue;
}
match u32::from(c) {
n @ (0..=0x7F | 0xA0..=0xFF) => out.push(n as u8),
_ => return Err(c),
}
}
Ok(out)
}
const HIGH_TRIGRAPHS: [&str; 32] = [
"\\<)", "\\x-", "\\.V", "\\v/", "\\.S", "\\GS", "\\|>", "\\pi", "\\.d", "\\<=", "\\>=", "\\=/",
"\\Ga", "\\->", "\\<-", "\\|v", "\\|^", "\\Gg", "\\Gd", "\\Ge", "\\Gn", "\\Gh", "\\Gl", "\\Gr",
"\\Gs", "\\Gt", "\\Gw", "\\GD", "\\PI", "\\GW", "\\[]", "\\oo",
];
const LATIN_TRIGRAPHS: [(u8, &str); 8] = [
(171, "\\<<"),
(176, "\\^o"),
(181, "\\Gm"),
(187, "\\>>"),
(215, "\\.x"),
(216, "\\O/"),
(223, "\\Gb"),
(247, "\\:-"),
];
pub fn encode_command(text: &str) -> Result<Vec<u8>, char> {
let mut out = Vec::with_capacity(text.len());
let mut rest = text;
while let Some(pos) = rest.find('\\') {
out.extend(encode(&rest[..pos])?);
let tail = &rest[pos..];
match trigraph_at(tail) {
Some((byte, len)) => {
out.push(byte);
rest = &tail[len..];
}
None => {
out.push(b'\\');
rest = &tail[1..];
}
}
}
out.extend(encode(rest)?);
Ok(out)
}
pub fn from_trigraphs(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(pos) = rest.find('\\') {
out.push_str(&rest[..pos]);
let tail = &rest[pos..];
if let Some(after) = tail.strip_prefix("\\\\") {
out.push('\\');
rest = after;
continue;
}
match trigraph_at(tail) {
Some((byte, len)) => {
out.push_str(&char_of(byte));
rest = &tail[len..];
}
None => {
out.push('\\');
rest = &tail[1..];
}
}
}
out.push_str(rest);
out
}
pub fn trigraph_at(s: &str) -> Option<(u8, usize)> {
if let Some(head) = s.get(..3) {
if let Some(i) = HIGH_TRIGRAPHS.iter().position(|t| *t == head) {
return Some((0x80 + i as u8, 3));
}
if let Some((b, _)) = LATIN_TRIGRAPHS.iter().find(|(_, t)| *t == head) {
return Some((*b, 3));
}
}
let digits = s.get(1..4)?;
if !digits.bytes().all(|d| d.is_ascii_digit()) {
return None;
}
let n: u16 = digits.parse().ok()?;
u8::try_from(n).ok().filter(|b| *b >= 128).map(|b| (b, 4))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamedChar {
pub code: u8,
pub text: String,
pub trigraph: &'static str,
}
pub fn named_chars() -> Vec<NamedChar> {
let high = (0x80u8..)
.zip(HIGH.iter().zip(HIGH_TRIGRAPHS))
.map(|(code, (text, trigraph))| NamedChar {
code,
text: (*text).to_string(),
trigraph,
});
let latin = LATIN_TRIGRAPHS.iter().map(|&(code, trigraph)| NamedChar {
code,
text: char::from(code).to_string(),
trigraph,
});
high.chain(latin).collect()
}
pub fn to_trigraphs(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len());
for &b in bytes {
match b {
0x80..=0x9F => s.push_str(HIGH_TRIGRAPHS[usize::from(b - 0x80)]),
0xA0..=0xFF => match LATIN_TRIGRAPHS.iter().find(|(l, _)| *l == b) {
Some((_, t)) => s.push_str(t),
None => s.push_str(&format!("\\{b}")),
},
_ => s.push(char::from(b)),
}
}
s
}
#[cfg(test)]
mod tests {
use super::*;
const SX_T3: &str = concat!(
"\\<)\\x-\\.V\\v/\\.S\\GS\\|>\\pi\\.d\\<=\\>=\\=/\\Ga\\->\\<-\\|v\\|^\\Gg\\Gd",
"\\Ge\\Gn\\Gh\\Gl\\Gr\\Gs\\Gt\\Gw\\GD\\PI\\GW\\[]\\oo\\160\\161\\162\\163",
"\\164\\165\\166\\167\\168\\169\\170\\<<\\172\\173\\174\\175\\^o\\177\\178",
"\\179\\180\\Gm\\182\\183\\184\\185\\186\\>>\\188\\189\\190\\191\\192\\193",
"\\194\\195\\196\\197\\198\\199\\200\\201\\202\\203\\204\\205\\206\\207",
"\\208\\209\\210\\211\\212\\213\\214\\.x\\O/\\217\\218\\219\\220\\221\\222",
"\\Gb\\224\\225\\226\\227\\228\\229\\230\\231\\232\\233\\234\\235\\236",
"\\237\\238\\239\\240\\241\\242\\243\\244\\245\\246\\:-\\248\\249\\250",
"\\251\\252\\253\\254\\255",
);
#[test]
fn trigraphs_match_the_calculator() {
let high: Vec<u8> = (128..=255).collect();
assert_eq!(to_trigraphs(&high), SX_T3);
assert_eq!(encode_command(SX_T3).unwrap(), high);
}
#[test]
fn named_chars_cover_both_tables_and_round_trip() {
let named = named_chars();
assert_eq!(named.len(), HIGH.len() + LATIN_TRIGRAPHS.len());
assert!(named.windows(2).all(|w| w[0].code < w[1].code));
for c in &named {
assert_eq!(
encode_command(c.trigraph).unwrap(),
[c.code],
"{}",
c.trigraph
);
assert_eq!(encode(&c.text).unwrap(), [c.code], "{}", c.text);
assert_eq!(decode(&[c.code]), c.text);
assert_eq!(to_trigraphs(&[c.code]), c.trigraph);
}
assert_eq!(named[3].trigraph, "\\v/");
assert_eq!(named[3].text, "√");
}
#[test]
fn command_trigraphs() {
assert_eq!(
encode_command("{ 1 2 } \\->STR").unwrap(),
b"{ 1 2 } \x8dSTR"
);
assert_eq!(encode_command("→STR").unwrap(), b"\x8dSTR");
assert_eq!(encode_command("\"a\\b\" \\1").unwrap(), b"\"a\\b\" \\1");
assert_eq!(encode_command("\\127 \\256").unwrap(), b"\\127 \\256");
assert_eq!(encode_command("\\").unwrap(), b"\\");
}
#[test]
fn trigraphs_become_their_bytes() {
assert_eq!(
encode_command("\\<< 1 \\->LIST \\>> \\GS \\160 \\q").unwrap(),
[
0xAB, b' ', b'1', b' ', 0x8D, b'L', b'I', b'S', b'T', b' ', 0xBB, b' ', 0x85, b' ',
160, b' ', b'\\', b'q'
]
);
assert_eq!(encode_command("\\127").unwrap(), b"\\127");
assert_eq!(encode_command("€"), Err('€'));
for (i, t) in HIGH_TRIGRAPHS.iter().enumerate() {
assert_eq!(encode_command(t).unwrap(), [0x80 + i as u8]);
}
}
#[test]
fn from_trigraphs_reads_every_code_as_the_calculator_does() {
let high: Vec<u8> = (128..=255).collect();
assert_eq!(from_trigraphs(SX_T3), decode(&high));
assert_eq!(
from_trigraphs("'\\GS(I=0,1000,1/I!)' \\->NUM"),
"'Σ(I=0,1000,1/I!)' →NUM"
);
assert_eq!(
from_trigraphs("\\v/ \\pi \\<= \\>= \\=/ \\Ga \\<) \\.d \\|> \\oo \\<- \\|v \\|^"),
"√ π ≤ ≥ ≠ α ∡ ∂ ▶ ∞ ← ↓ ↑"
);
assert_eq!(from_trigraphs("\\<< 1 + \\>>"), "« 1 + »");
assert_eq!(from_trigraphs("\\x-"), "x\u{0304}");
assert_eq!(from_trigraphs("\"a\\\\b\""), "\"a\\b\"");
assert_eq!(from_trigraphs("\\\\GS"), "\\GS");
assert_eq!(from_trigraphs("\\q \\127 \\"), "\\q \\127 \\");
assert_eq!(
from_trigraphs("« 'Σ(I=1,10,I^2)' →NUM »"),
"« 'Σ(I=1,10,I^2)' →NUM »"
);
assert_eq!(from_trigraphs("\\é\\GS"), "\\éΣ");
assert_eq!(from_trigraphs(""), "");
}
#[test]
fn encode_is_decode_inverted() {
let all: Vec<u8> = (0..=255).collect();
assert_eq!(encode(&decode(&all)).unwrap(), all);
assert_eq!(
encode("« 1 2 + »").unwrap(),
[0xAB, 32, 49, 32, 50, 32, 43, 32, 0xBB]
);
assert_eq!(encode("\u{85}"), Err('\u{85}'));
assert_eq!(encode("€"), Err('€'));
}
#[test]
fn high_characters() {
assert_eq!(decode(b"AB"), "AB");
assert_eq!(decode(&[0x8D, 0x85, 0xAB, 0xBB]), "→Σ«»");
assert_eq!(decode(&[0x81]), "x\u{0304}");
}
}