use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
use std::vec;
use serde::{Deserialize, Serialize};
use crate::failure::{Doing, Failure};
use bash_strings::{emit_array, parse_array};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub struct Micros(pub u64);
impl Micros {
pub(crate) fn now() -> Result<Self, Failure> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|since| Self(since.as_micros() as u64))
.doing(|| "reading the run's clock".into())
}
fn parse_epoch(text: &str) -> Option<Self> {
let (seconds, micros) = text.split_once(['.', ','])?;
if micros.len() != 6 {
return None;
}
Some(Self(
seconds.parse::<u64>().ok()? * 1_000_000 + micros.parse::<u64>().ok()?,
))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct Pid(pub u32);
impl fmt::Display for Pid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Verb {
Say,
Ask,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Stamp {
pub sent_at: Micros,
pub heard_at: Micros,
}
#[derive(Debug, Clone, Serialize)]
pub struct Message {
pub verb: Verb,
pub stamp: Stamp,
pub words: Vec<String>,
}
impl Message {
pub fn behind(&self, lead: &str) -> Option<&[String]> {
match self.words.split_first() {
Some((first, rest)) if first == lead => Some(rest),
_ => None,
}
}
pub(crate) fn read(line: Line) -> Result<Self, Failure> {
let refused = |why: &str| {
Failure::new(
format!("reading the line {:?}", line.text),
why,
)
};
let mut ahead = Ahead::over(&line.text).map_err(|why| refused(&why))?;
let verb = match ahead.word().map_err(refused)?.as_str() {
"SAY" => Verb::Say,
"ASK" => Verb::Ask,
other => {
return Err(refused(&format!(
"{other} is not a verb"
)));
}
};
let sent_at = ahead.clock().map_err(refused)?;
Ok(Self {
verb,
stamp: Stamp {
sent_at,
heard_at: line.heard_at,
},
words: ahead.rest(),
})
}
}
#[derive(Debug)]
pub(crate) struct Account {
pub stamp: Stamp,
pub words: Vec<String>,
}
impl Account {
pub(crate) fn read(text: &str, heard_at: Micros) -> Result<Self, Failure> {
let refused = |why: &str| {
Failure::new(
format!("reading the account {text:?}"),
why,
)
};
let mut ahead = Ahead::over(text).map_err(|why| refused(&why))?;
let sent_at = ahead.clock().map_err(refused)?;
Ok(Self {
stamp: Stamp { sent_at, heard_at },
words: ahead.rest(),
})
}
}
#[derive(Debug)]
pub(crate) struct Line {
pub text: String,
pub heard_at: Micros,
}
struct Ahead(vec::IntoIter<String>);
impl Ahead {
fn over(text: &str) -> Result<Self, String> {
parse_array(text)
.map(|words| Self(words.into_iter()))
.map_err(|why| why.to_string())
}
fn word(&mut self) -> Result<String, &'static str> {
self.0.next().ok_or("the line ended early")
}
fn clock(&mut self) -> Result<Micros, &'static str> {
match self.word()?.split_once('=') {
Some(("at", value)) => Micros::parse_epoch(value).ok_or("bad at"),
_ => Err("no at= header"),
}
}
fn rest(self) -> Vec<String> {
self.0.collect()
}
}
pub fn field<'a>(words: &'a [String], key: &str) -> Option<&'a str> {
words
.chunks_exact(2)
.find(|pair| pair[0] == key)
.map(|pair| pair[1].as_str())
}
#[derive(Debug)]
pub struct Answer(Vec<String>);
impl Answer {
pub fn of(command: impl Into<String>, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
let mut words = vec![command.into()];
words.extend(args.into_iter().map(Into::into));
Self(words)
}
pub fn status(code: u8) -> Self {
Self::of("__bc_status", [code.to_string()])
}
pub fn unknown() -> Self {
Self::status(127)
}
pub fn returning(code: u8) -> Self {
Self::of("return", [code.to_string()])
}
}
impl fmt::Display for Answer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", emit_array(&self.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn words(items: &[&str]) -> Vec<String> {
items.iter().map(|item| item.to_string()).collect()
}
fn line(kind: &str, payload: &[&str]) -> Line {
let mut all = words(&[kind, "at=1.000002"]);
all.extend(words(payload));
Line {
text: emit_array(&all),
heard_at: Micros(50),
}
}
fn spoke(payload: &[&str]) -> Message {
Message::read(line("SAY", payload)).expect("a message")
}
#[test]
fn the_protocols_words_come_off_and_the_clients_remain() {
let message = spoke(&["REC", "a space", ""]);
assert_eq!(message.verb, Verb::Say);
assert_eq!(message.stamp.sent_at, Micros(1_000_002));
assert_eq!(
message.stamp.heard_at,
Micros(50),
"the run's clock, from the read"
);
assert_eq!(
message.words,
words(&["REC", "a space", ""]),
"the payload alone"
);
assert_eq!(
message.behind("REC"),
Some(words(&["a space", ""]).as_slice())
);
}
#[test]
fn a_message_may_carry_nothing_of_its_own() {
let message = spoke(&[]);
assert!(message.words.is_empty());
assert_eq!(message.behind("REC"), None);
}
#[test]
fn an_account_is_the_clock_and_the_pairs() {
let text = emit_array(&words(&[
"at=1.000002",
"zero",
"x.bash",
"flags",
"hB",
]));
let account = Account::read(&text, Micros(50)).unwrap();
assert_eq!(
account.stamp,
Stamp {
sent_at: Micros(1_000_002),
heard_at: Micros(50)
}
);
assert_eq!(
field(&account.words, "zero"),
Some("x.bash")
);
let verbed = emit_array(&words(&["JOIN", "at=1.000002"]));
assert!(
Account::read(&verbed, Micros(0)).is_err(),
"a verb where the clock goes"
);
}
#[test]
fn a_header_the_protocol_did_not_write_is_an_error() {
let bad = [
emit_array(&words(&["MUMBLE", "at=1.000002"])),
emit_array(&words(&["JOIN", "at=1.000002"])),
emit_array(&words(&["SAY", "when=1.000002"])),
emit_array(&words(&["SAY", "at=1.0"])),
emit_array(&words(&["SAY"])),
"(unquoted".to_string(),
];
for text in bad {
let refused = Message::read(Line {
text: text.clone(),
heard_at: Micros(0),
});
assert!(
refused.is_err(),
"{text} should not read"
);
}
}
#[test]
fn an_answer_is_one_line_whatever_it_carries() {
let carried = ["%s", "two\nlines", "a\ttab", "\u{ff}", "it's", ""];
let message = Answer::of("printf", carried).to_string();
assert!(
!message.contains('\n'),
"a raw newline would truncate the read: {message}"
);
let mut expected = vec!["printf".to_string()];
expected.extend(carried.iter().map(|word| word.to_string()));
assert_eq!(
parse_array(&message).unwrap(),
expected,
"and it still reads back"
);
}
#[test]
fn messages_round_trip() {
let nested = emit_array(&words(&["INNER", "x y"]));
let message = spoke(&["TAG", "quote'inside", "two\nlines", &nested]);
assert_eq!(
message.words,
words(&["TAG", "quote'inside", "two\nlines", &nested])
);
assert_eq!(
parse_array(&nested).unwrap(),
words(&["INNER", "x y"])
);
}
}