Last active
December 9, 2025 05:31
-
-
Save randrews/bef62954159b07341912e1b610ee3943 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::collections::VecDeque; | |
| #[derive(Copy, Clone, Debug, PartialEq)] | |
| #[rustfmt::skip] | |
| enum Alphabet { | |
| L, U, P, | |
| } | |
| enum ZChar { | |
| Literal(char), | |
| Abbreviation(u8, u8), | |
| } | |
| #[rustfmt::skip] | |
| const EXTENDED_CHARS: [char; 69] = [ | |
| '\u{00e4}', '\u{00f6}', '\u{00fc}', '\u{00c4}', '\u{00d6}', '\u{00dc}', '\u{00df}', '\u{00bb}', | |
| '\u{00ab}', '\u{00eb}', '\u{00ef}', '\u{00ff}', '\u{00cb}', '\u{00cf}', '\u{00e1}', '\u{00e9}', | |
| '\u{00ed}', '\u{00f3}', '\u{00fa}', '\u{00fd}', '\u{00c1}', '\u{00c9}', '\u{00cd}', '\u{00d3}', | |
| '\u{00da}', '\u{00dd}', '\u{00e0}', '\u{00e8}', '\u{00ec}', '\u{00f2}', '\u{00f9}', '\u{00c0}', | |
| '\u{00c8}', '\u{00cc}', '\u{00d2}', '\u{00d9}', '\u{00e2}', '\u{00ea}', '\u{00ee}', '\u{00f4}', | |
| '\u{00fb}', '\u{00c2}', '\u{00ca}', '\u{00ce}', '\u{00d4}', '\u{00db}', '\u{00e5}', '\u{00c5}', | |
| '\u{00f8}', '\u{00d8}', '\u{00e3}', '\u{00f1}', '\u{00f5}', '\u{00c3}', '\u{00d1}', '\u{00d5}', | |
| '\u{00e6}', '\u{00c6}', '\u{00e7}', '\u{00c7}', '\u{00fe}', '\u{00f0}', '\u{00de}', '\u{00d0}', | |
| '\u{00a3}', '\u{0153}', '\u{0152}', '\u{00a1}', '\u{00bf}', | |
| ]; | |
| impl ZChar { | |
| fn parse<I: Iterator<Item = u8>>(iter: &mut I) -> Option<Self> { | |
| let mut zch = iter.next()?; | |
| let mut alphabet = Alphabet::L; | |
| loop { | |
| match zch & 0x1f { | |
| // 0 is always a space | |
| 0 => return Some(ZChar::Literal(' ')), | |
| // 1-3 is an abbreviation, which depends on the current alphabet | |
| 1..4 => return Some(ZChar::Abbreviation(zch, iter.next()? & 0x1f)), | |
| // 4 and 5 change alphabets for the next char: | |
| 4 => { | |
| alphabet = Alphabet::U; | |
| zch = iter.next()? | |
| } | |
| 5 => { | |
| alphabet = Alphabet::P; | |
| zch = iter.next()? | |
| } | |
| // 6 in alphabet P is a literal: | |
| 6 if alphabet == Alphabet::P => { | |
| let high = iter.next()?; | |
| let low = iter.next()?; | |
| return Self::literal(high, low); | |
| } | |
| 6..32 => { | |
| let chars = match alphabet { | |
| Alphabet::L => " ^^^^^abcdefghijklmnopqrstuvwxyz", | |
| Alphabet::U => " ^^^^^ABCDEFGHIJKLMNOPQRSTUVWXYZ", | |
| Alphabet::P => " ^^^^^ \n0123456789.,!?_#'\"/\\-:()", | |
| }; | |
| let ch = chars | |
| .chars() | |
| .nth(zch as usize) | |
| .expect("We covered everything else above..."); | |
| return Some(ZChar::Literal(ch)); | |
| } | |
| _ => unreachable!(), | |
| } | |
| } | |
| } | |
| fn literal(high: u8, low: u8) -> Option<Self> { | |
| let value = ((high as u16) & 0x1f) << 5 | ((low as u16) & 0x1f); | |
| match value { | |
| 0 => Some(Self::Literal('\0')), | |
| 13 => Some(Self::Literal('\n')), | |
| 32..127 => Some(Self::Literal(char::from_u32(value as u32).unwrap())), | |
| 155..252 => Some(Self::Literal(EXTENDED_CHARS[(value - 155) as usize])), | |
| _ => None, | |
| } | |
| } | |
| } | |
| pub fn convert(zscii: &[u8]) -> String { | |
| let mut zchars = VecDeque::new(); | |
| let mut i = 0usize; | |
| loop { | |
| // This can't happen because we'll search for the terminating | |
| // word before sending a slice here | |
| assert!(i < zscii.len()); | |
| let word = (zscii[i] as u16) << 8 | zscii[i + 1] as u16; | |
| zchars.push_back(((word >> 10) & 0x1f) as u8); | |
| zchars.push_back(((word >> 5) & 0x1f) as u8); | |
| zchars.push_back((word & 0x1f) as u8); | |
| if word & 0x8000 != 0 { | |
| break; | |
| } | |
| i += 2; | |
| } | |
| let mut output = String::new(); | |
| let mut iter = zchars.into_iter(); | |
| while let Some(zch) = ZChar::parse(&mut iter) { | |
| match zch { | |
| ZChar::Literal(ch) => output.push(ch), | |
| ZChar::Abbreviation(_, _) => {} // TODO abbreviations | |
| } | |
| } | |
| output | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment