use std::borrow::Cow;
use crate::stream::{NCReadStream, NCWriteStream, Tag, TagValue};
#[derive(rustradio_macros::Block)]
#[rustradio(crate, new, sync_nocopy_tag)]
pub struct MorseEncode {
#[rustradio(in)]
src: NCReadStream<String>,
#[rustradio(out)]
dst: NCWriteStream<Vec<u8>>,
}
const MORSE_AZ_TABLE: [&str; 26] = [
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
];
const MORSE_DIGIT_TABLE: [&str; 10] = [
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
];
const DIT: &[u8] = &[1, 0];
const DAH: &[u8] = &[1, 1, 1, 0];
const CHAR_GAP: &[u8] = &[0, 0];
const WORD_GAP: &[u8] = &[0, 0, 0, 0, 0, 0];
fn encode(msg: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(msg.len() * 32);
let mut chars = msg.chars().peekable();
while let Some(c) = chars.next() {
match c.to_ascii_lowercase() {
'0'..='9' => {
let morse = MORSE_DIGIT_TABLE[(c as u8 - b'0') as usize];
for sym in morse.chars() {
out.extend(match sym {
'.' => DIT,
'-' => DAH,
other => panic!("can't happen, got {other}"),
});
}
if let Some(next) = chars.peek()
&& *next != ' '
{
out.extend(CHAR_GAP);
}
}
'a'..='z' => {
let c = c.to_ascii_lowercase();
let morse = MORSE_AZ_TABLE[(c as u8 - b'a') as usize];
for sym in morse.chars() {
out.extend(match sym {
'.' => DIT,
'-' => DAH,
other => panic!("can't happen, got {other}"),
});
}
if let Some(next) = chars.peek()
&& *next != ' '
{
out.extend(CHAR_GAP);
}
}
' ' => out.extend(WORD_GAP),
other => log::warn!("morse code got invalid character '{other}'. Ignoring"),
}
}
out.extend(WORD_GAP);
out
}
impl MorseEncode {
fn process_sync_tags<'a>(&mut self, msg: String, tags: &'a [Tag]) -> (Vec<u8>, Cow<'a, [Tag]>) {
let mut tags = tags.to_vec();
let enc = encode(&msg);
tags.push(Tag::new(0, "MorseEncode::message", TagValue::String(msg)));
(enc, Cow::Owned(tags))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::{Block, BlockRet};
use crate::stream::new_nocopy_stream;
#[test]
fn simple() {
let (tx, rx) = new_nocopy_stream();
let (mut b, out) = MorseEncode::new(rx);
assert!(matches![b.work(), Ok(BlockRet::WaitForStream(_, _))]);
assert!(out.is_empty());
for (i, want) in &[
("", vec![0, 0, 0, 0, 0, 0]),
("A", vec![1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]),
(
"7",
vec![1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
),
(
"M0THC 73",
vec![
1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, ],
),
(
"hello",
vec![
1u8, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, ],
),
] {
tx.push(i.to_string(), &[]);
assert!(matches![b.work(), Ok(BlockRet::WaitForStream(_, _))]);
let (o, _) = out.pop().unwrap();
assert_eq!(&o, want, "For input {i}");
}
}
}