use anyhow::anyhow;
use thiserror::Error;
use crate::net::protocol::{HelpTopic, Request, Response};
use crate::pixmap::Color;
#[derive(Debug, Error, Copy, Clone, Eq, PartialEq)]
pub enum ParseErr {
#[error("Unknown Command")]
UnknownCommand,
#[error("Invalid Command Invocation")]
InvalidCommand,
}
#[inline(always)]
fn parse_px_set_args(x: &str, y: &str, px: &str) -> Result<Request, ParseErr> {
let xres = x.parse();
let yres = y.parse();
let cres = u32::from_str_radix(px, 16);
match (xres, yres, cres) {
(Ok(x), Ok(y), Ok(color)) => Ok(Request::SetPixel {
x,
y,
color: Color::from(color),
}),
(_, _, _) => Err(ParseErr::UnknownCommand),
}
}
#[inline(always)]
fn parse_px_get_args(x: &str, y: &str) -> Result<Request, ParseErr> {
let xres = x.parse();
let yres = y.parse();
match (xres, yres) {
(Ok(x), Ok(y)) => Ok(Request::GetPixel { x, y }),
(_, _) => Err(ParseErr::UnknownCommand),
}
}
#[inline(always)]
fn parse_help_args(token: &str) -> Result<Request, ParseErr> {
match token {
"help" | "HELP" | "general" | "GENERAL" => Ok(Request::Help(HelpTopic::General)),
"size" | "SIZE" => Ok(Request::Help(HelpTopic::Size)),
"px" | "PX" => Ok(Request::Help(HelpTopic::Px)),
_ => Err(ParseErr::InvalidCommand),
}
}
#[inline(always)]
fn parse_px_data(x: &str, y: &str, px: &str) -> Result<Response, ParseErr> {
let xres = x.parse();
let yres = y.parse();
let cres = u32::from_str_radix(px, 16);
match (xres, yres, cres) {
(Ok(x), Ok(y), Ok(color)) => Ok(Response::PxData {
x,
y,
color: Color::from(color),
}),
(_, _, _) => Err(ParseErr::UnknownCommand),
}
}
#[inline(always)]
fn parse_size_data(width: &str, height: &str) -> Result<Response, ParseErr> {
let width = width.parse();
let height = height.parse();
match (width, height) {
(Ok(width), Ok(height)) => Ok(Response::Size { width, height }),
(_, _) => Err(ParseErr::InvalidCommand),
}
}
#[inline(always)]
fn parse_help_data(topic: &str) -> Result<Response, ParseErr> {
match topic {
"help" | "HELP" | "general" | "GENERAL" => Ok(Response::Help(HelpTopic::General)),
"size" | "SIZE" => Ok(Response::Help(HelpTopic::Size)),
"px" | "PX" => Ok(Response::Help(HelpTopic::Px)),
_ => Err(ParseErr::InvalidCommand),
}
}
struct TokBuf<'s, const MAX_TOKS: usize> {
tokens: [Option<&'s str>; MAX_TOKS],
len: usize,
}
impl<'s, const MAX_TOKS: usize> TokBuf<'s, MAX_TOKS> {
#[inline(always)]
fn tokens(&self) -> &[&'s str] {
debug_assert_eq!(self.len, self.tokens.iter().filter(|i| i.is_some()).count());
unsafe { std::mem::transmute(&self.tokens[0..self.len]) }
}
}
impl<'s, const MAX_TOKS: usize> FromIterator<&'s str> for TokBuf<'s, MAX_TOKS> {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'s str>>(iter: T) -> Self {
let mut this = Self {
tokens: [None; MAX_TOKS],
len: 0,
};
for (i, token) in iter.into_iter().take(MAX_TOKS).enumerate() {
this.tokens[i] = Some(token);
this.len += 1;
}
this
}
}
#[inline(always)]
pub fn parse_request_str(line: &str) -> Result<Request, ParseErr> {
let tokens: TokBuf<'_, 4> = line.split_whitespace().collect();
let tokens = tokens.tokens();
match tokens.len() {
4 => parse_px_set_args(tokens[1], tokens[2], tokens[3]),
3 => parse_px_get_args(tokens[1], tokens[2]),
2 => parse_help_args(tokens[1]),
1 => match tokens[0] {
"SIZE" | "size" => Ok(Request::GetSize),
"HELP" | "help" => Ok(Request::Help(HelpTopic::General)),
_ => Err(ParseErr::UnknownCommand),
},
0 => Err(ParseErr::InvalidCommand),
_ => unreachable!(),
}
}
#[inline(always)]
pub fn parse_request_bin(line: &[u8]) -> anyhow::Result<Request> {
if line.is_ascii() {
let str = unsafe { std::str::from_utf8_unchecked(line) };
Ok(parse_request_str(str)?)
} else {
Err(anyhow!("request buffer does not contain an ascii string"))
}
}
#[inline(always)]
pub fn parse_response_str(line: &str) -> Result<Response, ParseErr> {
let tokens: TokBuf<'_, 4> = line.split_whitespace().collect();
let tokens = tokens.tokens();
match tokens.len() {
4 => parse_px_data(tokens[1], tokens[2], tokens[3]),
3 => parse_size_data(tokens[1], tokens[2]),
2 => parse_help_data(tokens[1]),
_ => Err(ParseErr::UnknownCommand),
}
}
#[inline(always)]
pub fn parse_response_bin(line: &[u8]) -> anyhow::Result<Response> {
if line.is_ascii() {
let str = unsafe { std::str::from_utf8_unchecked(line) };
Ok(parse_response_str(str)?)
} else {
Err(anyhow!("response buffer does not contain an ascii string"))
}
}
#[cfg(test)]
mod test {
use super::*;
use ::test::Bencher;
use std::hint::black_box;
#[test]
fn test_parse_commands() {
fn run_test(line: &str, res: Request) {
let req = parse_request_str(line);
assert_eq!(req, Ok(res), "{:06x?} != Ok({:06x?})", req, res);
}
run_test("HELP", Request::Help(HelpTopic::General));
run_test("SIZE", Request::GetSize);
run_test(
"PX 42 128 AABBCC",
Request::SetPixel {
x: 42,
y: 128,
color: Color::from((0xAA, 0xBB, 0xCC)),
},
);
run_test(
"PX 0 0 AABBCC",
Request::SetPixel {
x: 0,
y: 0,
color: Color::from((0xAA, 0xBB, 0xCC)),
},
);
}
#[bench]
fn bench_parse_get_pixel(b: &mut Bencher) {
let cmd = black_box("PX 17 7632");
b.iter(move || parse_request_str(cmd).unwrap());
}
#[bench]
fn bench_parse_set_pixel(b: &mut Bencher) {
let cmd = "PX 17 7632 12FBA5";
b.iter(move || parse_request_str(black_box(cmd)).unwrap());
}
#[bench]
fn bench_parse_size(b: &mut Bencher) {
let cmd = "SIZE";
b.iter(move || parse_request_str(black_box(cmd)).unwrap());
}
}