WIP: mx-sanebot: first pass at formalizing a parser
This commit is contained in:
parent
6f7b7ddb84
commit
b0c68308b7
|
@ -1,4 +1,5 @@
|
|||
mod msg_handler;
|
||||
mod parsing;
|
||||
|
||||
use std::env;
|
||||
|
||||
|
|
|
@ -3,6 +3,41 @@ use std::fmt;
|
|||
use std::process;
|
||||
use std::str;
|
||||
|
||||
use super::parsing;
|
||||
|
||||
|
||||
mod tt {
|
||||
pub(super) use super::parsing::{
|
||||
Either,
|
||||
Lit,
|
||||
Then,
|
||||
};
|
||||
|
||||
// grammar:
|
||||
// REQUEST = <!> (HELP | BT | BT-ADD)
|
||||
// HELP = <help>
|
||||
// BT = <bt>
|
||||
// BT-ADD = <bt-add> MAYBE_ARGS
|
||||
// MAYBE_ARGS = [SPACE [ARGS]]
|
||||
// ARGS = ARG MAYBE_ARGS
|
||||
// ARG = (not SPACE) [ARG]
|
||||
pub(super) type Request = Then<Bang, Either<Help, Bt>>;
|
||||
|
||||
pub(super) type Bang = Lit<{ '!' as u8 }>;
|
||||
|
||||
pub(super) type Help = Then<
|
||||
Lit<{ 'H' as u8 }>, Then<
|
||||
Lit<{ 'E' as u8 }>, Then<
|
||||
Lit<{ 'L' as u8 }>,
|
||||
Lit<{ 'P' as u8 }>,
|
||||
>>>;
|
||||
|
||||
pub(super) type Bt = Then<
|
||||
Lit<{ 'B' as u8 }>,
|
||||
Lit<{ 'T' as u8 }>,
|
||||
>;
|
||||
}
|
||||
|
||||
pub struct MessageHandler;
|
||||
|
||||
impl MessageHandler {
|
||||
|
@ -31,6 +66,15 @@ enum Request {
|
|||
Bt,
|
||||
}
|
||||
|
||||
impl From<tt::Request> for Request {
|
||||
fn from(t: tt::Request) -> Self {
|
||||
match t {
|
||||
tt::Then(_bang, tt::Either::A(_help)) => Self::Help,
|
||||
tt::Then(_bang, tt::Either::B(_bt)) => Self::Bt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
fn evaluate(self) -> Response {
|
||||
match self {
|
||||
|
|
79
pkgs/mx-sanebot/src/parsing.rs
Normal file
79
pkgs/mx-sanebot/src/parsing.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
/// for internal use.
|
||||
/// parses only if the parser has no more bytes to yield.
|
||||
struct Eof;
|
||||
|
||||
/// literal byte (character).
|
||||
pub struct Lit<const BYTE: u8>;
|
||||
|
||||
/// the two-item sequence of A followed by B.
|
||||
pub struct Then<A, B>(pub A, pub B);
|
||||
|
||||
/// if A parses, then A, else parse B.
|
||||
pub enum Either<A, B> {
|
||||
A(A),
|
||||
B(B),
|
||||
}
|
||||
|
||||
// case-insensitive u8 character.
|
||||
// type ILit<const BYTE: u8> = Either<Lit<{ BYTE.to_ascii_lowercase() }>, Lit<{ BYTE.to_ascii_uppercase() }>>;
|
||||
|
||||
|
||||
pub type PResult<P, C> = std::result::Result<(C, P), P>;
|
||||
pub trait Parser: Sized {
|
||||
fn expect_byte(self, b: Option<u8>) -> PResult<Self, ()>;
|
||||
fn expect<C: Parse>(self) -> PResult<Self, C>;
|
||||
// {
|
||||
// // support backtracking; i.e. don't modify `self` on failed parse
|
||||
// match C::consume(self.clone()) {
|
||||
// Ok(res) => res,
|
||||
// Err(_) => self,
|
||||
// }
|
||||
// }
|
||||
fn parse_all<C: Parse>(self) -> Result<C, ()> {
|
||||
match self.expect::<Then<C, Eof>>() {
|
||||
Ok((Then(c, _eof), _p)) => Ok(c),
|
||||
Err(_p) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Parse: Sized {
|
||||
fn consume<P: Parser>(p: P) -> PResult<P, Self>;
|
||||
}
|
||||
|
||||
impl Parse for Eof {
|
||||
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||
let (_, p) = p.expect_byte(None)?;
|
||||
Ok((Self, p))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const BYTE: u8> Parse for Lit<BYTE> {
|
||||
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||
let (_, p) = p.expect_byte(Some(BYTE))?;
|
||||
Ok((Self, p))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Parse, B: Parse> Parse for Then<A, B> {
|
||||
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||
let (a, p) = p.expect()?;
|
||||
let (b, p) = p.expect()?;
|
||||
Ok((Self(a, b), p))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Parse, B: Parse> Parse for Either<A, B> {
|
||||
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||
let p = match p.expect() {
|
||||
Ok((a, p)) => { return Ok((Self::A(a), p)); },
|
||||
Err(p) => p,
|
||||
};
|
||||
let p = match p.expect() {
|
||||
Ok((b, p)) => { return Ok((Self::B(b), p)); },
|
||||
Err(p) => p,
|
||||
};
|
||||
Err(p)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user