#[cfg(all(not(feature = "std"), feature = "parse-comments"))]
use alloc::string::String;
#[cfg(all(not(feature = "std"), any(feature = "parse-comments")))]
use alloc::vec::Vec;
mod values;
#[cfg(feature = "parse-expressions")]
mod expressions;
#[cfg(test)]
mod test;
use futures::{Stream, StreamExt};
use crate::{
stream::{MyTryStreamExt, PushBackable},
types::{Comment, ParseResult},
utils::skip_whitespaces,
Error, GCode,
};
use values::parse_number;
#[cfg(not(feature = "parse-expressions"))]
use values::parse_real_value;
#[cfg(feature = "parse-expressions")]
use expressions::parse_real_value;
#[derive(PartialEq, Debug, Clone, Copy)]
enum AsyncParserState {
Start(bool),
LineNumberOrSegment,
Segment,
ErrorRecovery,
#[cfg(all(feature = "parse-trailing-comment", feature = "parse-checksum"))]
EoLOrTrailingComment,
#[cfg(any(feature = "parse-trailing-comment", feature = "parse-checksum"))]
EndOfLine,
}
#[cfg(all(feature = "parse-trailing-comment", not(feature = "parse-comments")))]
async fn parse_eol_comment<S, E>(input: &mut S) -> Option<Result<Comment, E>>
where
S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
{
loop {
let b = match input.next().await? {
Ok(o) => o,
Err(e) => return Some(Err(e)),
};
match b {
b'\r' | b'\n' => {
input.push_back(b);
break Some(Ok(()));
}
_ => {}
}
}
}
#[cfg(all(feature = "parse-trailing-comment", feature = "parse-comments"))]
async fn parse_eol_comment<S, E>(input: &mut S) -> Option<ParseResult<Comment, E>>
where
S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
{
let mut v = Vec::new();
loop {
let b = try_result!(input.next());
match b {
b'\r' | b'\n' => {
input.push_back(b);
break Some(match String::from_utf8(v) {
Ok(s) => ParseResult::Ok(s),
Err(_) => Error::InvalidUTF8String.into(),
});
}
b => v.push(b),
}
}
}
#[cfg(not(feature = "parse-comments"))]
async fn parse_inline_comment<S, E>(input: &mut S) -> Option<ParseResult<Comment, E>>
where
S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
{
loop {
match try_result!(input.next()) {
b'\\' => {
try_result!(input.next());
}
b'(' => break Some(Error::UnexpectedByte(b'(').into()),
b')' => break Some(ParseResult::Ok(())),
_ => {}
}
}
}
#[cfg(feature = "parse-comments")]
async fn parse_inline_comment<S, E>(input: &mut S) -> Option<ParseResult<Comment, E>>
where
S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
{
let mut v = Vec::new();
loop {
let b = try_result!(input.next());
match b {
b'\\' => {
v.push(try_result!(input.next()));
}
b'(' => break Some(Error::UnexpectedByte(b'(').into()),
b')' => {
break Some(match String::from_utf8(v) {
Ok(s) => ParseResult::Ok(s),
Err(_) => Error::InvalidUTF8String.into(),
})
}
b => v.push(b),
}
}
}
#[cfg(not(feature = "parse-checksum"))]
use crate::stream::pushback::PushBack;
#[cfg(feature = "parse-checksum")]
type PushBack<T> = crate::stream::xorsum_pushback::XorSumPushBack<T>;
async fn parse_eol<S, E>(
state: &mut AsyncParserState,
input: &mut PushBack<S>,
) -> Option<ParseResult<GCode, E>>
where
S: Stream<Item = Result<u8, E>> + Unpin,
{
Some(loop {
let b = try_result!(input.next());
match b {
b'\r' | b'\n' => {
*state = AsyncParserState::Start(true);
#[cfg(feature = "parse-checksum")]
{
input.reset_sum(0);
}
break ParseResult::Ok(GCode::Execute);
}
b' ' => {}
b => break Error::UnexpectedByte(b).into(),
}
})
}
macro_rules! try_await {
($input:expr) => {
match $input.await? {
ParseResult::Ok(ok) => ok,
ParseResult::Parsing(err) => break Err(err.into()),
ParseResult::Input(err) => break Err(err.into()),
}
};
}
macro_rules! try_await_result {
($input:expr) => {
match $input.await? {
Ok(ok) => ok,
Err(err) => break Err(err.into()),
}
};
}
pub struct Parser<S, E>
where
S: Stream<Item = Result<u8, E>> + Unpin,
{
input: PushBack<S>,
state: AsyncParserState,
}
impl<S, E> Parser<S, E>
where
S: Stream<Item = Result<u8, E>> + Unpin,
E: From<Error>,
{
pub fn new(input: S) -> Self {
Self {
#[cfg(feature = "parse-checksum")]
input: input.xor_summed_push_backable(0),
#[cfg(not(feature = "parse-checksum"))]
input: input.push_backable(),
state: AsyncParserState::Start(true),
}
}
pub async fn next(&mut self) -> Option<Result<GCode, E>> {
let res = loop {
let b = match self.input.next().await? {
Ok(b) => b,
Err(err) => return Some(Err(err)),
};
match self.state {
AsyncParserState::Start(ref mut first_byte) => match b {
b'\n' => {
self.input.push_back(b);
break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
}
b'/' if *first_byte => {
self.state = AsyncParserState::LineNumberOrSegment;
break Ok(GCode::BlockDelete);
}
b' ' => {
*first_byte = false;
}
_ => {
self.input.push_back(b);
self.state = AsyncParserState::LineNumberOrSegment
}
},
AsyncParserState::LineNumberOrSegment => match b.to_ascii_lowercase() {
b'n' => {
try_await_result!(skip_whitespaces(&mut self.input));
let (n, ord) = try_await_result!(parse_number(&mut self.input));
break if ord == 1 {
let b = try_await_result!(self.input.next());
Err(Error::UnexpectedByte(b).into())
} else if ord > 10000 {
Err(Error::NumberOverflow.into())
} else {
self.state = AsyncParserState::Segment;
Ok(GCode::LineNumber(n))
};
}
_ => {
self.input.push_back(b);
self.state = AsyncParserState::Segment;
}
},
AsyncParserState::Segment => match b.to_ascii_lowercase() {
b' ' => {}
letter @ b'a'..=b'z' => {
try_await_result!(skip_whitespaces(&mut self.input));
let rv = try_await!(parse_real_value(&mut self.input));
break Ok(GCode::Word(letter.into(), rv));
}
b'\r' | b'\n' => {
self.input.push_back(b);
break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
}
#[cfg(feature = "parse-parameters")]
b'#' => {
try_await_result!(skip_whitespaces(&mut self.input));
#[allow(clippy::match_single_binding)]
let param_id = match try_await!(parse_real_value(&mut self.input)) {
#[cfg(feature = "optional-value")]
crate::RealValue::None => {
let b = try_await_result!((&mut self.input).next());
break Err(Error::UnexpectedByte(b).into());
}
id => id,
};
try_await_result!(skip_whitespaces(&mut self.input));
let b = try_await_result!((&mut self.input).next());
if b'=' != b {
break Err(Error::UnexpectedByte(b).into());
}
try_await_result!(skip_whitespaces(&mut self.input));
let value = try_await!(parse_real_value(&mut self.input));
break Ok(GCode::ParameterSet(param_id, value));
}
#[cfg(feature = "parse-checksum")]
b'*' => {
let sum = self.input.sum() ^ b'*';
try_await_result!(skip_whitespaces(&mut self.input));
let (n, _) = try_await_result!(parse_number(&mut self.input));
if n >= 256 {
break Err(Error::NumberOverflow.into());
} else if (n as u8) != sum {
break Err(Error::BadChecksum(sum).into());
} else {
try_await_result!(skip_whitespaces(&mut self.input));
#[cfg(not(feature = "parse-trailing-comment"))]
{
self.state = AsyncParserState::EndOfLine;
}
#[cfg(feature = "parse-trailing-comment")]
{
self.state = AsyncParserState::EoLOrTrailingComment;
}
}
}
#[cfg(not(feature = "parse-comments"))]
b'(' => {
try_await!(parse_inline_comment(&mut self.input));
}
#[cfg(feature = "parse-comments")]
b'(' => {
break Ok(GCode::Comment(try_await!(parse_inline_comment(
&mut self.input
))));
}
#[cfg(all(
feature = "parse-trailing-comment",
not(feature = "parse-comments")
))]
b';' => {
try_await_result!(parse_eol_comment(&mut self.input));
self.state = AsyncParserState::EndOfLine;
}
#[cfg(all(feature = "parse-trailing-comment", feature = "parse-comments"))]
b';' => {
let s = try_await!(parse_eol_comment(&mut self.input));
self.state = AsyncParserState::EndOfLine;
break Ok(GCode::Comment(s));
}
_ => break Err(Error::UnexpectedByte(b).into()),
},
#[cfg(all(
feature = "parse-trailing-comment",
not(feature = "parse-comments"),
feature = "parse-checksum"
))]
AsyncParserState::EoLOrTrailingComment => match b {
b';' => try_await_result!(parse_eol_comment(&mut self.input)),
_ => {
self.input.push_back(b);
break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
}
},
#[cfg(all(
feature = "parse-trailing-comment",
feature = "parse-comments",
feature = "parse-checksum"
))]
AsyncParserState::EoLOrTrailingComment => match b {
b';' => {
let s = try_await!(parse_eol_comment(&mut self.input));
self.state = AsyncParserState::EndOfLine;
break Ok(GCode::Comment(s));
}
_ => {
self.input.push_back(b);
break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
}
},
#[cfg(any(feature = "parse-trailing-comment", feature = "parse-checksum"))]
AsyncParserState::EndOfLine => {
self.input.push_back(b);
break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
}
AsyncParserState::ErrorRecovery => match b {
b'\r' | b'\n' => {
self.input.push_back(b);
break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
}
_ => {}
},
}
};
if res.is_err() {
self.state = AsyncParserState::ErrorRecovery;
}
Some(res)
}
}