use dialectic::prelude::*;
use std::{
error::Error,
fmt::{self, Display},
str::FromStr,
};
use thiserror::Error;
use tokio::io::{AsyncWriteExt, BufReader, Stdin, Stdout};
mod common;
use common::{demo, prompt};
use serde::{Deserialize, Serialize};
#[tokio::main]
async fn main() {
demo::<Server, _, _, _, _, _, _>(&server, &client, usize::MAX).await;
}
pub type Client = Session! {
loop {
choose {
0 => {
send Operation;
call ClientTally;
},
1 => break,
}
}
};
#[Transmitter(Tx for Operation, i64)]
#[Receiver(Rx for i64)]
async fn client<Tx, Rx>(
mut input: BufReader<Stdin>,
mut output: Stdout,
mut chan: Chan<Client, Tx, Rx>,
) -> Result<(), Box<dyn Error>>
where
Tx::Error: Error + Send,
Rx::Error: Error + Send,
{
loop {
chan = if let Ok(operation) =
prompt("Operation (+ or *): ", &mut input, &mut output, str::parse).await
{
let chan = chan.choose::<0>().await?.send(operation).await?;
output
.write_all("Enter numbers (press ENTER to tally):\n".as_bytes())
.await?;
output.flush().await?;
let (done, chan) = chan
.call(|chan| client_tally(&operation, &mut input, &mut output, chan))
.await?;
let chan = chan.unwrap();
if done {
break chan.choose::<1>().await?;
} else {
chan
}
} else {
break chan.choose::<1>().await?;
}
}
.close();
Ok(())
}
pub type ClientTally = Session! {
loop {
choose {
0 => send i64,
1 => {
recv i64;
break;
}
}
}
};
#[Transmitter(Tx for Operation, i64)]
#[Receiver(Rx for i64)]
async fn client_tally<Tx, Rx>(
operation: &Operation,
input: &mut BufReader<Stdin>,
output: &mut Stdout,
mut chan: Chan<ClientTally, Tx, Rx>,
) -> Result<bool, Box<dyn Error>>
where
Tx::Error: Error + Send,
Rx::Error: Error + Send,
{
let (done, chan) = loop {
let user_input = prompt(&format!("{} ", operation), input, output, |s| {
let s = s.trim();
if s.is_empty() || s == "=" {
Ok(None)
} else if let Ok(n) = s.parse() {
Ok(Some(n))
} else {
Err(())
}
})
.await;
match user_input {
Ok(Some(n)) => chan = chan.choose::<0>().await?.send(n).await?,
Ok(None) | Err(_) => {
let (tally, chan) = chan.choose::<1>().await?.recv().await?;
output
.write_all(format!("= {}\n", tally).as_bytes())
.await?;
output.flush().await?;
break (user_input.is_err(), chan);
}
}
};
chan.close();
Ok(done)
}
type Server = <Client as Session>::Dual;
#[Transmitter(Tx for i64)]
#[Receiver(Rx for Operation, i64)]
async fn server<Tx, Rx>(mut chan: Chan<Server, Tx, Rx>) -> Result<(), Box<dyn Error>>
where
Tx::Error: Error + Send,
Rx::Error: Error + Send,
{
loop {
chan = offer!(in chan {
0 => {
let (op, chan) = chan.recv().await?;
chan.call(|chan| server_tally(op, chan)).await?.1.unwrap()
},
1 => break chan.close(),
})?;
}
Ok(())
}
type ServerTally = <ClientTally as Session>::Dual;
#[Transmitter(Tx for i64)]
#[Receiver(Rx for Operation, i64)]
async fn server_tally<Tx, Rx>(
op: Operation,
mut chan: Chan<ServerTally, Tx, Rx>,
) -> Result<(), Box<dyn Error>>
where
Tx::Error: Error + Send,
Rx::Error: Error + Send,
{
let mut tally = op.unit();
loop {
chan = offer!(in chan {
0 => {
let (i, chan) = chan.recv().await?;
tally = op.combine(tally, i);
chan
},
1 => {
chan.send(tally).await?.close();
break Ok(());
}
})?;
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum Operation {
Sum,
Product,
}
impl Operation {
pub fn unit(&self) -> i64 {
match self {
Operation::Sum => 0,
Operation::Product => 1,
}
}
pub fn combine(&self, x: i64, y: i64) -> i64 {
match self {
Operation::Sum => x + y,
Operation::Product => x * y,
}
}
}
#[derive(Debug, Copy, Clone, Error)]
#[error("couldn't parse operation")]
pub struct ParseOperationError;
impl FromStr for Operation {
type Err = ParseOperationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"+" => Ok(Operation::Sum),
"*" => Ok(Operation::Product),
_ => Err(ParseOperationError),
}
}
}
impl Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Operation::Sum => write!(f, "+"),
Operation::Product => write!(f, "*"),
}
}
}