#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseEvent {
ButtonEvent(u8),
Response(Vec<u8>),
}
pub struct StreamParser {
km_matched: usize,
response_buf: Vec<u8>,
prompt_matched: usize,
}
impl StreamParser {
pub fn new() -> Self {
Self {
km_matched: 0,
response_buf: Vec::with_capacity(256),
prompt_matched: 0,
}
}
pub fn feed(&mut self, byte: u8) -> Option<ParseEvent> {
const KM: &[u8] = b"km.";
if self.km_matched == 3 {
self.km_matched = 0;
if byte < 0x20 {
return Some(ParseEvent::ButtonEvent(byte));
}
self.push_response_bytes(KM);
return self.push_response_byte(byte);
}
if self.km_matched > 0 {
if byte == KM[self.km_matched] {
self.km_matched += 1;
return None;
}
let partial = &KM[..self.km_matched];
self.km_matched = 0;
self.push_response_bytes(partial);
if byte == KM[0] {
self.km_matched = 1;
return None;
}
return self.push_response_byte(byte);
}
if byte == KM[0] {
self.km_matched = 1;
return None;
}
self.push_response_byte(byte)
}
fn push_response_byte(&mut self, byte: u8) -> Option<ParseEvent> {
const PROMPT_BYTES: &[u8] = b">>> ";
self.response_buf.push(byte);
if byte == PROMPT_BYTES[self.prompt_matched] {
self.prompt_matched += 1;
if self.prompt_matched == PROMPT_BYTES.len() {
let len = self.response_buf.len() - PROMPT_BYTES.len();
let response = self.response_buf[..len].to_vec();
self.response_buf.clear();
self.prompt_matched = 0;
return Some(ParseEvent::Response(response));
}
} else if byte == PROMPT_BYTES[0] {
self.prompt_matched = 1;
} else {
self.prompt_matched = 0;
}
None
}
fn push_response_bytes(&mut self, bytes: &[u8]) {
for &b in bytes {
self.response_buf.push(b);
}
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.km_matched = 0;
self.response_buf.clear();
self.prompt_matched = 0;
}
}
pub fn classify_response(raw: &[u8]) -> ResponseKind {
let body = trim_bytes(raw);
if body.is_empty() {
return ResponseKind::Executed;
}
let text = String::from_utf8_lossy(body);
if let Some(nl) = body.iter().position(|&b| b == b'\n') {
let value = String::from_utf8_lossy(&body[nl + 1..]).trim().to_string();
return ResponseKind::Value(value);
}
ResponseKind::ValueOrEcho(text.trim().to_string())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResponseKind {
Executed,
Value(String),
ValueOrEcho(String),
}
pub fn parse_catch_event(raw: &[u8]) -> Option<crate::types::CatchEvent> {
use crate::types::Button;
let text = std::str::from_utf8(trim_bytes(raw)).ok()?;
let rest = text.strip_prefix("km.catch_m")?;
let (button, rest) = if let Some(r) = rest.strip_prefix("s1") {
(Button::Side1, r)
} else if let Some(r) = rest.strip_prefix("s2") {
(Button::Side2, r)
} else if let Some(r) = rest.strip_prefix('l') {
(Button::Left, r)
} else if let Some(r) = rest.strip_prefix('r') {
(Button::Right, r)
} else if let Some(r) = rest.strip_prefix('m') {
(Button::Middle, r)
} else {
return None;
};
match rest {
"(1)" => Some(crate::types::CatchEvent {
button,
pressed: true,
}),
"(2)" => Some(crate::types::CatchEvent {
button,
pressed: false,
}),
_ => None,
}
}
fn trim_bytes(b: &[u8]) -> &[u8] {
let is_ws = |&x: &u8| x == b'\r' || x == b'\n' || x == b' ';
let start = b.iter().position(|x| !is_ws(x)).unwrap_or(b.len());
let end = b
.iter()
.rposition(|x| !is_ws(x))
.map(|i| i + 1)
.unwrap_or(0);
if start >= end { &[] } else { &b[start..end] }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn button_event_basic() {
let mut parser = StreamParser::new();
assert_eq!(parser.feed(b'k'), None);
assert_eq!(parser.feed(b'm'), None);
assert_eq!(parser.feed(b'.'), None);
assert_eq!(parser.feed(0x05), Some(ParseEvent::ButtonEvent(0x05)));
}
#[test]
fn button_event_mask_0x0a() {
let mut parser = StreamParser::new();
assert_eq!(parser.feed(b'k'), None);
assert_eq!(parser.feed(b'm'), None);
assert_eq!(parser.feed(b'.'), None);
assert_eq!(parser.feed(0x0A), Some(ParseEvent::ButtonEvent(0x0A)));
}
#[test]
fn button_event_mask_0x0d() {
let mut parser = StreamParser::new();
assert_eq!(parser.feed(b'k'), None);
assert_eq!(parser.feed(b'm'), None);
assert_eq!(parser.feed(b'.'), None);
assert_eq!(parser.feed(0x0D), Some(ParseEvent::ButtonEvent(0x0D)));
}
#[test]
fn button_event_mask_zero() {
let mut parser = StreamParser::new();
for &b in b"km." {
assert_eq!(parser.feed(b), None);
}
assert_eq!(parser.feed(0x00), Some(ParseEvent::ButtonEvent(0x00)));
}
#[test]
fn command_echo_not_confused_with_button() {
let mut parser = StreamParser::new();
let input = b"km.left(1)\r\n>>> ";
let mut events = Vec::new();
for &b in input.iter() {
if let Some(ev) = parser.feed(b) {
events.push(ev);
}
}
assert_eq!(events.len(), 1);
match &events[0] {
ParseEvent::Response(data) => {
let text = String::from_utf8_lossy(data);
assert!(text.contains("km.left(1)"), "got: {}", text);
}
other => panic!("expected Response, got {:?}", other),
}
}
#[test]
fn interleaved_button_and_response() {
let mut parser = StreamParser::new();
let mut input: Vec<u8> = b"km.left(1)\r\n".to_vec();
input.extend_from_slice(b"km.");
input.push(0x05);
input.extend_from_slice(b">>> ");
let mut events = Vec::new();
for &b in &input {
if let Some(ev) = parser.feed(b) {
events.push(ev);
}
}
assert_eq!(events.len(), 2);
assert_eq!(events[0], ParseEvent::ButtonEvent(0x05));
assert!(matches!(&events[1], ParseEvent::Response(_)));
}
#[test]
fn version_response_no_echo() {
let mut parser = StreamParser::new();
let input = b"km.MAKCU\r\n>>> ";
let mut events = Vec::new();
for &b in input.iter() {
if let Some(ev) = parser.feed(b) {
events.push(ev);
}
}
assert_eq!(events.len(), 1);
match &events[0] {
ParseEvent::Response(data) => {
let text = String::from_utf8_lossy(data);
assert!(text.contains("km.MAKCU"), "got: {}", text);
}
other => panic!("expected Response, got {:?}", other),
}
}
#[test]
fn classify_executed() {
assert_eq!(classify_response(b""), ResponseKind::Executed);
assert_eq!(classify_response(b"\r\n"), ResponseKind::Executed);
}
#[test]
fn classify_value_multiline() {
let resp = b"km.left()\r\n1";
assert_eq!(
classify_response(resp),
ResponseKind::Value("1".to_string())
);
}
#[test]
fn catch_event_press() {
let event = parse_catch_event(b"km.catch_ml(1)\r\n").unwrap();
assert_eq!(event.button, crate::types::Button::Left);
assert!(event.pressed);
}
#[test]
fn catch_event_release() {
let event = parse_catch_event(b"km.catch_ml(2)\r\n").unwrap();
assert_eq!(event.button, crate::types::Button::Left);
assert!(!event.pressed);
}
#[test]
fn catch_event_right() {
let event = parse_catch_event(b"km.catch_mr(1)").unwrap();
assert_eq!(event.button, crate::types::Button::Right);
assert!(event.pressed);
}
#[test]
fn catch_event_middle() {
let event = parse_catch_event(b"km.catch_mm(2)").unwrap();
assert_eq!(event.button, crate::types::Button::Middle);
assert!(!event.pressed);
}
#[test]
fn catch_event_side1() {
let event = parse_catch_event(b"km.catch_ms1(1)").unwrap();
assert_eq!(event.button, crate::types::Button::Side1);
}
#[test]
fn catch_event_side2() {
let event = parse_catch_event(b"km.catch_ms2(2)").unwrap();
assert_eq!(event.button, crate::types::Button::Side2);
}
#[test]
fn catch_event_not_catch() {
assert!(parse_catch_event(b"km.left(1)\r\n").is_none());
assert!(parse_catch_event(b"km.lock_ml(1)\r\n").is_none());
assert!(parse_catch_event(b"").is_none());
}
#[test]
fn catch_event_enable_response_not_catch() {
assert!(parse_catch_event(b"km.catch_ml(0)\r\n").is_none());
}
#[test]
fn catch_event_through_parser() {
let mut parser = StreamParser::new();
let input = b"km.catch_ml(1)\r\n>>> ";
let mut events = Vec::new();
for &b in input.iter() {
if let Some(ev) = parser.feed(b) {
events.push(ev);
}
}
assert_eq!(events.len(), 1);
match &events[0] {
ParseEvent::Response(data) => {
let catch = parse_catch_event(data).unwrap();
assert_eq!(catch.button, crate::types::Button::Left);
assert!(catch.pressed);
}
other => panic!("expected Response, got {:?}", other),
}
}
#[test]
fn classify_single_line() {
let resp = b"km.MAKCU";
assert_eq!(
classify_response(resp),
ResponseKind::ValueOrEcho("km.MAKCU".to_string())
);
}
}