From d36ef5cdb04a7a8ef48ce003368c4f9cde35324b Mon Sep 17 00:00:00 2001 From: Carlos Barrales Ruiz Date: Fri, 3 Nov 2023 08:36:57 +0100 Subject: [PATCH 1/7] Practical productization enhancements * Use lightweight fixed decimal representation to faciliate matching and avoid precision issues. * Internal (num, ord) decimal logic reinterpretation. * Make f64 optional so it could be non fully/completely supported in baremetal. --- .gitignore | 1 + Cargo.toml | 3 +- src/lib.rs | 2 + src/parser.rs | 4 +- src/parser/test.rs | 5 ++- src/parser/values.rs | 42 +++++++++------------ src/types.rs | 89 +++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 112 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 3eded00..528fd16 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ kcov* tags **/*.rustfmt Cargo.lock +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index eb4a3f7..3daf9e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ categories = ["asynchronous", "embedded", "no-std", "parsing"] [features] default = ["std"] -std = [] +std = ["float-f64"] parse-comments = [] parse-trailing-comment = [] parse-checksum = [] @@ -19,6 +19,7 @@ parse-parameters = [] parse-expressions = [] optional-value = [] string-value = [] +float-f64 = [] [badges] maintenance = { status = "experimental" } diff --git a/src/lib.rs b/src/lib.rs index 823faa6..8666ab3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,8 @@ pub use parser::Parser; pub use types::Literal; pub use types::RealValue; +pub use types::DecimalRepr; + #[cfg(any(feature = "parse-expressions", feature = "parse-parameters"))] pub use types::expressions::Expression; diff --git a/src/parser.rs b/src/parser.rs index e36e94f..3fc985b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -237,10 +237,10 @@ where 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 { + break if ord == 0 { let b = try_await_result!(self.input.next()); Err(Error::UnexpectedByte(b).into()) - } else if ord > 10000 { + } else if ord > 5 { Err(Error::NumberOverflow.into()) } else { self.state = AsyncParserState::Segment; diff --git a/src/parser/test.rs b/src/parser/test.rs index 653725c..03755cc 100644 --- a/src/parser/test.rs +++ b/src/parser/test.rs @@ -5,6 +5,8 @@ use super::{Error, GCode, Parser, StreamExt}; #[cfg(feature = "optional-value")] use crate::types::RealValue; +use crate::DecimalRepr; + #[cfg(feature = "parse-checksum")] mod parse_checksum; #[cfg(feature = "parse-expressions")] @@ -206,7 +208,8 @@ fn word_with_number() { Ok(GCode::Word('r', (-0.098).into())), Ok(GCode::Word('s', (0.098).into())), Ok(GCode::Execute), - Ok(GCode::Word('t', (-21.33).into())) + // avoid precision loss with f64 conversion in this case + Ok(GCode::Word('t', DecimalRepr::new(-2133, 2).into())), ] ); } diff --git a/src/parser/values.rs b/src/parser/values.rs index 8659124..913f990 100644 --- a/src/parser/values.rs +++ b/src/parser/values.rs @@ -12,12 +12,7 @@ use futures::{Stream, StreamExt}; #[cfg(all(not(feature = "std"), feature = "string-value"))] use alloc::string::String; -use crate::{ - stream::PushBackable, - types::{Literal, ParseResult}, - utils::skip_whitespaces, - Error, -}; +use crate::{stream::PushBackable, types::{Literal, ParseResult}, utils::skip_whitespaces, Error, DecimalRepr}; #[cfg(not(feature = "parse-expressions"))] use crate::types::RealValue; @@ -25,22 +20,23 @@ use crate::types::RealValue; #[cfg(any(feature = "parse-parameters", feature = "parse-expressions"))] pub use crate::types::expressions::{Expression, Operator}; -pub(crate) async fn parse_number(input: &mut S) -> Option> +pub(crate) async fn parse_number(input: &mut S) -> Option> where S: Stream> + Unpin + PushBackable, { let mut n = 0; - let mut order = 1; + let mut order = 0; let res = loop { let b = match input.next().await? { Ok(b) => b, - Err(e) => return Some(Err(e)), + Err(e) => { + return Some(Err(e))}, }; match b { b'0'..=b'9' => { let digit = u32::from(b - b'0'); n = n * 10 + digit; - order *= 10; + order +=1; } _ => { input.push_back(b); @@ -51,18 +47,16 @@ where Some(res) } -async fn parse_real_literal(input: &mut S) -> Option> +async fn parse_real_literal(input: &mut S) -> Option> where S: Stream> + Unpin + PushBackable, { // extract sign: default to positiv let mut b = try_result!(input.next()); - let mut negativ = false; if b == b'-' || b == b'+' { negativ = b == b'-'; - // skip spaces after the sign try_result!(skip_whitespaces(input)); b = try_result!(input.next()); @@ -91,21 +85,21 @@ where None }; - //println!( - //"literal done: {} {:?} {:?}", - //if negativ { '-' } else { '+' }, - //int, - //dec - //); + #[cfg(feature = "defmt")] + info!("parse_real_literal_alt: int: {}, dec: {}", int, dec); let res = if int.is_none() && dec.is_none() { ParseResult::Parsing(Error::BadNumberFormat.into()) } else { - let int = int.map(f64::from).unwrap_or(0.); - let (dec, ord) = dec - .map(|(dec, ord)| (dec.into(), ord.into())) - .unwrap_or((0., 1.)); - ParseResult::Ok((if negativ { -1. } else { 1. }) * (int + dec / ord)) + let sign = if negativ { -1i32 } else { 1i32 }; + let int = int.unwrap_or(0); + let (n, ord) = dec.unwrap_or((0u32,0u8)); + let scaler = u32::pow(10, ord as u32); + ParseResult::Ok( + DecimalRepr::new( + sign * (((int * scaler) + n) as i32), ord + ) + ) }; Some(res) } diff --git a/src/types.rs b/src/types.rs index 9d085dc..9f6bfa7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -30,20 +30,90 @@ pub type Comment = (); #[cfg(feature = "parse-comments")] pub type Comment = String; +#[derive(Debug, Clone, Copy)] +pub struct DecimalRepr { + integer: i32, + scale: u8, +} + +impl PartialEq for DecimalRepr { + #[inline] + fn eq(&self, other: &Self) -> bool { + if self.scale == other.scale { + self.integer == other.integer + } + else if self.scale > other.scale { + self.integer == (other.integer * 10i32.pow((self.scale - other.scale) as u32)) + } + else { + other.integer == (self.integer * 10i32.pow((other.scale - self.scale) as u32)) + } + } +} + +impl Default for DecimalRepr { + fn default() -> Self { + Self { + integer: 0i32, + scale: 0u8, + } + } +} + +impl DecimalRepr { + pub const fn new(integer: i32, scale: u8) -> DecimalRepr { + DecimalRepr { + integer, + scale, + } + } + #[cfg(feature = "float-f64")] + /// Convert from f64 with a maximum of 8 decimal positions + pub fn from_f64(number: f64) -> DecimalRepr { + let integer = (number * 100000000.0f64).trunc() as i32; + DecimalRepr { + integer, + scale: 8, + } + } + #[cfg(feature = "float-f64")] + pub fn to_f64(&self ) -> f64 { + self.integer as f64 / (10u64.pow(self.scale as u32) as f64) + } + + #[inline] + pub fn integer_part(&self) -> i32 { + self.integer + } + + #[inline] + pub fn scale(&self) -> u8 { + self.scale + } +} + #[derive(Debug, PartialEq, Clone)] pub enum Literal { - RealNumber(f64), + RealNumber(DecimalRepr), #[cfg(feature = "string-value")] String(String), } impl Literal { - pub fn as_real_number(&self) -> Option { + pub fn as_decimal(&self) -> Option { match self { Literal::RealNumber(rn) => Some(*rn), #[cfg(feature = "string-value")] _ => None, } } + #[cfg(feature = "float-f64")] + pub fn as_real_number(&self) -> Option { + match self { + Literal::RealNumber(rn) => Some(rn.to_f64()), + #[cfg(feature = "string-value")] + _ => None, + } + } #[cfg(feature = "string-value")] pub fn as_string(&self) -> Option<&str> { match self { @@ -52,19 +122,26 @@ impl Literal { } } } + +impl From for Literal { + fn from(from: DecimalRepr) -> Self { + Self::RealNumber(from) + } +} impl From for Literal { fn from(from: i32) -> Self { - Self::RealNumber(from as f64) + Self::RealNumber(DecimalRepr::new(from, 0)) } } impl From for Literal { fn from(from: u32) -> Self { - Self::RealNumber(from as f64) + Self::RealNumber(DecimalRepr::new(from as i32, 0)) } } +#[cfg(feature = "float-f64")] impl From for Literal { fn from(from: f64) -> Self { - Self::RealNumber(from) + Self::RealNumber(DecimalRepr::from_f64(from)) } } @@ -85,7 +162,7 @@ pub enum RealValue { } impl Default for RealValue { fn default() -> Self { - Self::from(0.) + DecimalRepr::new(0, 0).into() } } impl> From for RealValue { From 391762335c047f323e9885538e21764c6e6ae11e Mon Sep 17 00:00:00 2001 From: Carlos Barrales Ruiz Date: Wed, 8 Nov 2023 00:30:56 +0100 Subject: [PATCH 2/7] Merge review --- .gitignore | 3 +-- src/lib.rs | 5 ++++- src/parser/test.rs | 34 +++++++++++++++++----------------- src/parser/values.rs | 3 --- src/types.rs | 44 ++++++++++++++++++++++++++++++++++++++------ 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 528fd16..6439a16 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ kcov* **/*.rs.bk tags **/*.rustfmt -Cargo.lock -.idea \ No newline at end of file +Cargo.lock \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 8666ab3..cd45daf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,9 +112,12 @@ pub enum Error { /// Error no the gcode syntax UnexpectedByte(u8), - /// The parsed number excedded the expected range. + /// The parsed number exceeded the expected range. NumberOverflow, + /// Incompatible number conversions. + InvalidNumberConversion, + /// Format error during number parsing. Typically a dot without digits (at least one is /// required). BadNumberFormat, diff --git a/src/parser/test.rs b/src/parser/test.rs index 03755cc..09c3ae5 100644 --- a/src/parser/test.rs +++ b/src/parser/test.rs @@ -192,24 +192,24 @@ fn word_with_number() { block_on(input), &[ Ok(GCode::Execute), - Ok(GCode::Word('g', (21.0).into())), - Ok(GCode::Word('h', (21.0).into())), - Ok(GCode::Word('i', (21.098).into())), + Ok(GCode::Word('g', (21.0).try_into().unwrap())), + Ok(GCode::Word('h', (21.0).try_into().unwrap())), + Ok(GCode::Word('i', (21.098).try_into().unwrap())), Ok(GCode::Execute), - Ok(GCode::Word('j', (-21.0).into())), - Ok(GCode::Word('k', (-21.0).into())), - Ok(GCode::Word('l', (-21.098).into())), + Ok(GCode::Word('j', (-21.0).try_into().unwrap())), + Ok(GCode::Word('k', (-21.0).try_into().unwrap())), + Ok(GCode::Word('l', (-21.098).try_into().unwrap())), Ok(GCode::Execute), - Ok(GCode::Word('m', (21.0).into())), - Ok(GCode::Word('n', (21.0).into())), - Ok(GCode::Word('p', (21.098).into())), + Ok(GCode::Word('m', (21.0).try_into().unwrap())), + Ok(GCode::Word('n', (21.0).try_into().unwrap())), + Ok(GCode::Word('p', (21.098).try_into().unwrap())), Ok(GCode::Execute), - Ok(GCode::Word('q', (0.098).into())), - Ok(GCode::Word('r', (-0.098).into())), - Ok(GCode::Word('s', (0.098).into())), + Ok(GCode::Word('q', (0.098).try_into().unwrap())), + Ok(GCode::Word('r', (-0.098).try_into().unwrap())), + Ok(GCode::Word('s', (0.098).try_into().unwrap())), Ok(GCode::Execute), // avoid precision loss with f64 conversion in this case - Ok(GCode::Word('t', DecimalRepr::new(-2133, 2).into())), + Ok(GCode::Word('t', DecimalRepr::new(-2133, 2).try_into().unwrap())), ] ); } @@ -221,11 +221,11 @@ fn word_may_not_have_a_value() { assert_eq!( block_on(input), &[ - Ok(GCode::Word('g', (75.0).into())), + Ok(GCode::Word('g', (75.0).try_into().unwrap())), Ok(GCode::Word('z', RealValue::None)), - Ok(GCode::Word('t', (48.0).into())), + Ok(GCode::Word('t', (48.0).try_into().unwrap())), Ok(GCode::Word('s', RealValue::None)), - Ok(GCode::Word('p', (0.3).into())), + Ok(GCode::Word('p', (0.3).try_into().unwrap())), Ok(GCode::Execute) ] ); @@ -240,7 +240,7 @@ fn word_may_not_have_a_value_but_ambiguous_sequence_will_error() { assert_eq!( block_on(input), &[ - Ok(GCode::Word('g', (75.0).into())), + Ok(GCode::Word('g', (75.0).try_into().unwrap())), Ok(GCode::Word('z', RealValue::None)), Err(Error::UnexpectedByte(b'4')), Ok(GCode::Execute) diff --git a/src/parser/values.rs b/src/parser/values.rs index 913f990..45f7a3d 100644 --- a/src/parser/values.rs +++ b/src/parser/values.rs @@ -85,9 +85,6 @@ where None }; - #[cfg(feature = "defmt")] - info!("parse_real_literal_alt: int: {}, dec: {}", int, dec); - let res = if int.is_none() && dec.is_none() { ParseResult::Parsing(Error::BadNumberFormat.into()) } else { diff --git a/src/types.rs b/src/types.rs index 9f6bfa7..2b08ce5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,6 +3,8 @@ any(feature = "parse-comments", feature = "string-value") ))] use alloc::string::String; +#[cfg(feature = "float-f64")] +use core::f64; #[derive(Debug)] pub(crate) enum ParseResult { @@ -128,20 +130,41 @@ impl From for Literal { Self::RealNumber(from) } } + impl From for Literal { fn from(from: i32) -> Self { Self::RealNumber(DecimalRepr::new(from, 0)) } } -impl From for Literal { - fn from(from: u32) -> Self { - Self::RealNumber(DecimalRepr::new(from as i32, 0)) +impl TryFrom for Literal { + + type Error = crate::Error; + + fn try_from(value: u32) -> Result { + Ok(Self::RealNumber( + DecimalRepr::new( + i32::try_from(value) + .map_err(|_| crate::Error::InvalidNumberConversion)?, 0 + ) + )) } } #[cfg(feature = "float-f64")] -impl From for Literal { - fn from(from: f64) -> Self { - Self::RealNumber(DecimalRepr::from_f64(from)) +impl TryFrom for Literal { + type Error = crate::Error; + + fn try_from(value: f64) -> Result { + // As parser constraints: 5 decimals max + let scaled_value = value * 100000.0; + if scaled_value > f64::from(i32::MAX) { + Err(crate::Error::InvalidNumberConversion) + } else if scaled_value < f64::from(i32::MIN) { + Err(crate::Error::InvalidNumberConversion) + } else { + Ok(Self::RealNumber( + DecimalRepr::new(scaled_value as i32, 5) + )) + } } } @@ -171,6 +194,15 @@ impl> From for RealValue { } } +#[cfg(feature = "float-f64")] +impl TryFrom for RealValue { + type Error = crate::Error; + + fn try_from(from: f64) -> Result { + Ok(RealValue::Literal(from.try_into()?)) + } +} + #[cfg(any(feature = "parse-parameters", feature = "parse-expressions"))] pub(crate) mod expressions { use super::{Literal, RealValue}; From 2601c51fdff1ddf240fc560771a16549f3ac9cf2 Mon Sep 17 00:00:00 2001 From: Carlos Barrales Ruiz Date: Wed, 13 Dec 2023 18:07:35 +0100 Subject: [PATCH 3/7] Control excess of precision while parsing to avoid overflows --- src/parser/values.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/parser/values.rs b/src/parser/values.rs index 45f7a3d..ba77d2e 100644 --- a/src/parser/values.rs +++ b/src/parser/values.rs @@ -23,6 +23,7 @@ pub use crate::types::expressions::{Expression, Operator}; pub(crate) async fn parse_number(input: &mut S) -> Option> where S: Stream> + Unpin + PushBackable, + E: core::convert::From, { let mut n = 0; let mut order = 0; @@ -35,6 +36,9 @@ where match b { b'0'..=b'9' => { let digit = u32::from(b - b'0'); + if order > 8 { + return Some(Err(crate::Error::NumberOverflow.into())); + } n = n * 10 + digit; order +=1; } @@ -50,6 +54,8 @@ where async fn parse_real_literal(input: &mut S) -> Option> where S: Stream> + Unpin + PushBackable, + E: core::convert::From, + { // extract sign: default to positiv let mut b = try_result!(input.next()); @@ -132,6 +138,7 @@ where pub(crate) async fn parse_literal(input: &mut S) -> Option> where S: Stream> + Unpin + PushBackable, + E: core::convert::From, { let b = try_result!(input.next()); Some(match b { @@ -149,6 +156,7 @@ where pub(crate) async fn parse_real_value(input: &mut S) -> Option> where S: Stream> + Unpin + PushBackable, + E: core::convert::From, { let b = try_result!(input.next()); // println!("real value: {:?}", b as char); From 7622b83ffa6787ad544a563dae616b8b43ebd9d2 Mon Sep 17 00:00:00 2001 From: Carlos Barrales Ruiz Date: Mon, 23 Sep 2024 23:12:05 +0200 Subject: [PATCH 4/7] - Experiment: Add an extra simple interface leveraging async-traits - Add support to collect and reestablish line counting --- Cargo.toml | 10 +-- examples/cli.rs | 6 +- src/lib.rs | 3 + src/parser.rs | 107 ++++++++++++++++++----------- src/parser/test.rs | 12 +++- src/parser/values.rs | 26 ++++--- src/stream.rs | 158 ++++++++++++++++++++++++++++++++++++------- src/utils.rs | 35 ++++++---- 8 files changed, 262 insertions(+), 95 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3daf9e6..55f6b1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/ithinuel/async-gcode" categories = ["asynchronous", "embedded", "no-std", "parsing"] [features] -default = ["std"] +default = ["std", "future-stream"] std = ["float-f64"] parse-comments = [] parse-trailing-comment = [] @@ -20,14 +20,16 @@ parse-expressions = [] optional-value = [] string-value = [] float-f64 = [] +# Use future::stream as input interface. If not set, async-traits will be used insted. +future-stream = ["pin-project-lite"] [badges] maintenance = { status = "experimental" } [dev-dependencies] -futures-executor = { version = "0.3.21" } +futures-executor = { version = "0.3" } [dependencies] either = {version = "^1", default-features = false } -futures = { version = "0.3.21", default-features = false } -pin-project-lite = { version = "0.2.9" } +futures = { version = "0.3", default-features = false } +pin-project-lite = { version = "0.2", optional = true } \ No newline at end of file diff --git a/examples/cli.rs b/examples/cli.rs index c93370a..61d2e4f 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -19,7 +19,11 @@ fn main() { )); while let Some(res) = parser.next().await { - println!("{:?}", res); + match res { + Ok(_r) => println!("{:?}", _r), + Err(Error::Io(_ie)) => println!("IOError {:?}", _ie), + Err(Error::Parse(_pe)) => println!("ParseError {:?}", _pe), + } } }); } diff --git a/src/lib.rs b/src/lib.rs index cd45daf..154dc80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,8 @@ mod types; mod parser; +pub use stream::{ByteStream, TryByteStream}; +pub use parser::AsyncParserState; pub use parser::Parser; pub use types::Literal; pub use types::RealValue; @@ -137,6 +139,7 @@ pub enum Error { #[derive(Debug, PartialEq, Clone)] pub enum GCode { + StatusCommand, BlockDelete, LineNumber(u32), #[cfg(feature = "parse-comments")] diff --git a/src/parser.rs b/src/parser.rs index 3fc985b..be7500d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,8 +25,6 @@ mod expressions; #[cfg(test)] mod test; -use futures::{Stream, StreamExt}; - use crate::{ stream::{MyTryStreamExt, PushBackable}, types::{Comment, ParseResult}, @@ -34,6 +32,9 @@ use crate::{ Error, GCode, }; +#[cfg(feature = "future-stream")] +use futures::StreamExt; + use values::parse_number; #[cfg(not(feature = "parse-expressions"))] @@ -41,9 +42,10 @@ use values::parse_real_value; #[cfg(feature = "parse-expressions")] use expressions::parse_real_value; +use crate::stream::{ByteStream, UnpinTrait}; #[derive(PartialEq, Debug, Clone, Copy)] -enum AsyncParserState { +pub enum AsyncParserState { Start(bool), LineNumberOrSegment, Segment, @@ -57,7 +59,7 @@ enum AsyncParserState { #[cfg(all(feature = "parse-trailing-comment", not(feature = "parse-comments")))] async fn parse_eol_comment(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable, { loop { let b = match input.next().await? { @@ -77,7 +79,7 @@ where #[cfg(all(feature = "parse-trailing-comment", feature = "parse-comments"))] async fn parse_eol_comment(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable, { let mut v = Vec::new(); loop { @@ -98,7 +100,7 @@ where #[cfg(not(feature = "parse-comments"))] async fn parse_inline_comment(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable + UnpinTrait, { loop { match try_result!(input.next()) { @@ -115,7 +117,7 @@ where #[cfg(feature = "parse-comments")] async fn parse_inline_comment(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable + UnpinTrait, { let mut v = Vec::new(); loop { @@ -145,9 +147,10 @@ type PushBack = crate::stream::xorsum_pushback::XorSumPushBack; async fn parse_eol( state: &mut AsyncParserState, input: &mut PushBack, + line_count: &mut u32, ) -> Option> where - S: Stream> + Unpin, + S: ByteStream> + UnpinTrait, { Some(loop { let b = try_result!(input.next()); @@ -158,6 +161,7 @@ where { input.reset_sum(0); } + *line_count += 1; break ParseResult::Ok(GCode::Execute); } b' ' => {} @@ -187,15 +191,16 @@ macro_rules! try_await_result { pub struct Parser where - S: Stream> + Unpin, + S: ByteStream>, { input: PushBack, state: AsyncParserState, + line_count: u32, } impl Parser where - S: Stream> + Unpin, + S: ByteStream> + UnpinTrait, E: From, { pub fn new(input: S) -> Self { @@ -205,8 +210,28 @@ where #[cfg(not(feature = "parse-checksum"))] input: input.push_backable(), state: AsyncParserState::Start(true), + line_count: 0, } } + + pub fn reset(&mut self) { + #[cfg(feature = "parse-checksum")] + self.input.reset_sum(0); + self.state = AsyncParserState::Start(true); + } + + pub fn get_state(&self) -> AsyncParserState { + self.state + } + + pub fn get_current_line(&self) -> u32 { + self.line_count + } + + pub fn update_current_line(&mut self, line: u32) { + self.line_count = line; + } + pub async fn next(&mut self) -> Option> { let res = loop { let b = match self.input.next().await? { @@ -217,9 +242,12 @@ where // println!("{:?}: {:?}", self.state, char::from(b)); match self.state { AsyncParserState::Start(ref mut first_byte) => match b { + b'?' => { + break Ok(GCode::StatusCommand); + } b'\n' => { self.input.push_back(b); - break Ok(try_await!(parse_eol(&mut self.state, &mut self.input))); + break Ok(try_await!(parse_eol(&mut self.state, &mut self.input, &mut self.line_count))); } b'/' if *first_byte => { self.state = AsyncParserState::LineNumberOrSegment; @@ -262,7 +290,7 @@ where } b'\r' | b'\n' => { self.input.push_back(b); - break Ok(try_await!(parse_eol(&mut self.state, &mut self.input))); + break Ok(try_await!(parse_eol(&mut self.state, &mut self.input, &mut self.line_count))); } // param support feature #[cfg(feature = "parse-parameters")] @@ -334,49 +362,48 @@ where } #[cfg(all(feature = "parse-trailing-comment", feature = "parse-comments"))] b';' => { - let s = try_await!(parse_eol_comment(&mut self.input)); + let s = try_await!(parse_eol_comment(&mut self.input, &mut self.line_count)); 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)); + #[cfg(all(feature = "parse-trailing-comment", feature = "parse-checksum"))] + AsyncParserState::EoLOrTrailingComment => { + #[cfg(not(feature = "parse-comments"))] + { + 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, &mut self.line_count))); + } + } } - _ => { - self.input.push_back(b); - break Ok(try_await!(parse_eol(&mut self.state, &mut self.input))); + #[cfg(feature = "parse-comments")] + { + match b { + b';' => { + let s = try_await!(parse_eol_comment(&mut self.input, &mut self.line_count)); + 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, &mut self.line_count))); + } + } } }, #[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))); + break Ok(try_await!(parse_eol(&mut self.state, &mut self.input, &mut self.line_count))); } 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))); + break Ok(try_await!(parse_eol(&mut self.state, &mut self.input, &mut self.line_count))); } _ => {} }, diff --git a/src/parser/test.rs b/src/parser/test.rs index 09c3ae5..548aede 100644 --- a/src/parser/test.rs +++ b/src/parser/test.rs @@ -1,6 +1,4 @@ -use futures::stream; - -use super::{Error, GCode, Parser, StreamExt}; +use super::{Error, GCode, Parser}; #[cfg(feature = "optional-value")] use crate::types::RealValue; @@ -17,6 +15,10 @@ mod parse_parameters; mod parse_trailing_comment; fn block_on>(it: T) -> Vec> { + + use futures::stream; + use futures::StreamExt; + let mut parser = Parser::new(stream::iter(it).map(Result::<_, Error>::Ok)); futures_executor::block_on( @@ -56,6 +58,9 @@ fn spaces_are_not_allowed_before_block_delete() { #[test] fn error_in_underlying_stream_are_passed_through_and_parser_recovers_on_execute() { + use futures::stream; + use futures::StreamExt; + #[derive(Debug, Copy, Clone, PartialEq)] enum TestError { SomeError, @@ -91,6 +96,7 @@ fn error_in_underlying_stream_are_passed_through_and_parser_recovers_on_execute( Ok(GCode::Execute) ] ) + } #[test] diff --git a/src/parser/values.rs b/src/parser/values.rs index ba77d2e..df2baef 100644 --- a/src/parser/values.rs +++ b/src/parser/values.rs @@ -7,22 +7,29 @@ //! crate shows that there's no subtantial benefit in terms of flash size from using the fixed //! point arithmetics. -use futures::{Stream, StreamExt}; - #[cfg(all(not(feature = "std"), feature = "string-value"))] use alloc::string::String; -use crate::{stream::PushBackable, types::{Literal, ParseResult}, utils::skip_whitespaces, Error, DecimalRepr}; - +use crate::{ + stream::PushBackable, + types::{Literal, ParseResult}, + utils::skip_whitespaces, + Error, + DecimalRepr +}; +use crate::stream::{ByteStream, UnpinTrait}; #[cfg(not(feature = "parse-expressions"))] use crate::types::RealValue; #[cfg(any(feature = "parse-parameters", feature = "parse-expressions"))] pub use crate::types::expressions::{Expression, Operator}; +#[cfg(feature = "future-stream")] +use futures::StreamExt; + pub(crate) async fn parse_number(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable + UnpinTrait, E: core::convert::From, { let mut n = 0; @@ -53,9 +60,8 @@ where async fn parse_real_literal(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable + UnpinTrait, E: core::convert::From, - { // extract sign: default to positiv let mut b = try_result!(input.next()); @@ -110,7 +116,7 @@ where #[cfg(feature = "string-value")] async fn parse_string_literal(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable, { #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -137,7 +143,7 @@ where pub(crate) async fn parse_literal(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable + UnpinTrait, E: core::convert::From, { let b = try_result!(input.next()); @@ -155,7 +161,7 @@ where #[cfg(not(feature = "parse-expressions"))] pub(crate) async fn parse_real_value(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable + UnpinTrait, E: core::convert::From, { let b = try_result!(input.next()); diff --git a/src/stream.rs b/src/stream.rs index 9a9bbcc..e8d2932 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,6 +1,52 @@ -use futures::TryStream; -pub(crate) trait MyTryStreamExt: TryStream { +#[cfg(feature = "future-stream")] +pub use core::marker::Unpin as UnpinTrait; + +#[cfg(feature = "future-stream")] +pub use futures::stream::Stream as ByteStream; + +#[cfg(feature = "future-stream")] +pub use futures::stream::TryStream as TryByteStream; + +#[cfg(not(feature = "future-stream"))] +pub trait UnpinTrait {} + +#[cfg(not(feature = "future-stream"))] +#[allow(async_fn_in_trait)] +pub trait ByteStream { + /// Values yielded by the stream. + type Item; + async fn next(&mut self) -> Option; +} + +#[cfg(not(feature = "future-stream"))] +impl UnpinTrait for S where S: ByteStream {} + +#[cfg(not(feature = "future-stream"))] +#[allow(async_fn_in_trait)] +pub trait TryByteStream: ByteStream { + /// The type of successful values yielded by this future + type Ok; + + /// The type of failures yielded by this future + type Error; + async fn try_next(&mut self) -> Option>; +} + +#[cfg(not(feature = "future-stream"))] +impl TryByteStream for S +where + S: ?Sized + ByteStream> + UnpinTrait, +{ + type Ok = T; + type Error = E; + + async fn try_next(&mut self) -> Option> { + self.next().await + } +} + +pub(crate) trait MyTryStreamExt: TryByteStream { #[cfg(not(feature = "parse-checksum"))] fn push_backable(self) -> pushback::PushBack where @@ -21,7 +67,7 @@ pub(crate) trait MyTryStreamExt: TryStream { xorsum_pushback::XorSumPushBack::new(self, initial_sum) } } -impl MyTryStreamExt for T where T: TryStream {} +impl MyTryStreamExt for T where T: TryByteStream {} pub(crate) trait PushBackable { type Item; @@ -30,30 +76,40 @@ pub(crate) trait PushBackable { #[cfg(not(feature = "parse-checksum"))] pub(crate) mod pushback { - use futures::{Stream, TryStream}; - use pin_project_lite::pin_project; - - use core::pin::Pin; - use core::task::{Context, Poll}; + use super::{ByteStream, TryByteStream}; use super::PushBackable; - - pin_project! { - pub(crate) struct PushBack { + #[cfg(feature = "future-stream")] + use core::pin::Pin; + #[cfg(feature = "future-stream")] + use futures::task::Poll; + #[cfg(feature = "future-stream")] + use futures::task::Context; + + #[cfg(feature = "future-stream")] + pin_project_lite::pin_project! { + pub(crate) struct PushBack { #[pin] stream: S, val: Option, } } - impl PushBack { + + #[cfg(not(feature = "future-stream"))] + pub(crate) struct PushBack { + stream: S, + val: Option, + } + + impl PushBack { pub fn new(stream: S) -> Self { Self { stream, val: None } } } impl PushBackable for PushBack where - S: TryStream, + S: TryByteStream, { type Item = S::Ok; fn push_back(&mut self, v: S::Ok) -> Option { @@ -61,9 +117,10 @@ pub(crate) mod pushback { } } - impl Stream for PushBack + #[cfg(feature = "future-stream")] + impl ByteStream for PushBack where - S: TryStream, + S: TryByteStream, { type Item = Result; fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { @@ -76,6 +133,23 @@ pub(crate) mod pushback { } } + #[cfg(not(feature = "future-stream"))] + impl ByteStream for PushBack + where + S: TryByteStream, + { + type Item = Result; + + async fn next(&mut self) -> Option { + if let Some(v) = self.val.take() { + Some(Ok(v)) + } + else { + self.stream.try_next().await + } + } + } + #[cfg(test)] mod test { use super::{PushBack, PushBackable}; @@ -124,16 +198,18 @@ pub(crate) mod pushback { #[cfg(feature = "parse-checksum")] pub(crate) mod xorsum_pushback { - use futures::{Stream, TryStream}; - use pin_project_lite::pin_project; + use super::PushBackable; + use super::{ByteStream, TryByteStream}; + + #[cfg(feature = "future-stream")] use core::pin::Pin; + #[cfg(feature = "future-stream")] use core::task::{Context, Poll}; - use super::PushBackable; - - pin_project! { - pub(crate) struct XorSumPushBack { + #[cfg(feature = "future-stream")] + pin_project_lite::pin_project! { + pub(crate) struct XorSumPushBack { #[pin] stream: S, head: Option, @@ -141,9 +217,17 @@ pub(crate) mod xorsum_pushback { } } + #[cfg(not(feature = "future-stream"))] + pub(crate) struct XorSumPushBack { + stream: S, + head: Option, + sum: S::Ok + } + + impl XorSumPushBack where - S: TryStream, + S: TryByteStream, S::Ok: core::ops::BitXorAssign + Copy, { pub fn new(stream: S, initial_sum: S::Ok) -> Self { @@ -165,7 +249,7 @@ pub(crate) mod xorsum_pushback { impl PushBackable for XorSumPushBack where - S: TryStream, + S: TryByteStream, S::Ok: core::ops::BitXorAssign + Copy, { type Item = S::Ok; @@ -175,9 +259,10 @@ pub(crate) mod xorsum_pushback { } } - impl Stream for XorSumPushBack + #[cfg(feature = "future-stream")] + impl ByteStream for XorSumPushBack where - S: TryStream, + S: TryByteStream, S::Ok: core::ops::BitXorAssign + Copy, { type Item = Result; @@ -197,6 +282,29 @@ pub(crate) mod xorsum_pushback { } } + + #[cfg(not(feature = "future-stream"))] + impl ByteStream for XorSumPushBack + where + S: TryByteStream, + S::Ok: core::ops::BitXorAssign + Copy, + { + type Item = Result; + + async fn next(&mut self) -> Option { + let item = if let Some(item) = self.head.take() { + item + } else { + match self.stream.try_next().await { + Some(Ok(item)) => item, + other => return other, + } + }; + self.sum ^= item; + Some(Ok(item)) + } + } + #[cfg(test)] mod test { use super::{PushBackable, XorSumPushBack}; diff --git a/src/utils.rs b/src/utils.rs index d7dc5b4..ba384af 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ -use crate::stream::PushBackable; -use futures::{Stream, StreamExt}; +use crate::stream::{ByteStream, PushBackable, UnpinTrait}; + +#[cfg(feature = "future-stream")] +use futures::StreamExt; #[doc(hidden)] #[macro_export] @@ -26,7 +28,7 @@ macro_rules! try_result { pub(crate) async fn skip_whitespaces(input: &mut S) -> Option> where - S: Stream> + Unpin + PushBackable, + S: ByteStream> + PushBackable + UnpinTrait, { loop { let b = match input.next().await? { @@ -41,23 +43,32 @@ where Some(Ok(())) } -#[cfg(itest)] +#[cfg(test)] mod test { - use super::{skip_whitespaces, stream, StreamExt}; - - #[cfg(not(feature = "parse-checksum"))] - use crate::stream::pushback::PushBack; - #[cfg(feature = "parse-checksum")] - use crate::stream::xorsum_pushback::XorSumPushBack; + use super::{skip_whitespaces}; + use futures::stream; + use futures::StreamExt; #[test] fn skips_white_spaces_and_pushes_back_the_first_non_space_byte() { - let mut data = PushBack::new(stream::iter( + + #[cfg(not(feature = "parse-checksum"))] + use crate::stream::pushback::PushBack as PushBack; + #[cfg(feature = "parse-checksum")] + use crate::stream::xorsum_pushback::XorSumPushBack as PushBack; + + let iter = stream::iter( b" d" .iter() .copied() .map(Result::<_, core::convert::Infallible>::Ok), - )); + ); + + #[cfg(not(feature = "parse-checksum"))] + let mut data = PushBack::new(iter); + #[cfg(feature = "parse-checksum")] + let mut data = PushBack::new(iter, 0); + futures_executor::block_on(async move { skip_whitespaces(&mut data).await; assert_eq!(Some(Ok(b'd')), data.next().await); From 79850eb5c0cd604e0fe82d879796959060c3bb85 Mon Sep 17 00:00:00 2001 From: Carlos Barrales Ruiz Date: Wed, 22 Jan 2025 08:42:07 +0100 Subject: [PATCH 5/7] Allow negative line numbers for some gcode producers requesting for instance: "N-1 M110 N-1" (Printrun) Fix wrong signature from previous commit --- src/lib.rs | 2 +- src/parser.rs | 39 +++++++++++++++++++++++++++------------ src/parser/test.rs | 14 ++++++-------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 154dc80..b92ed67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,7 +141,7 @@ pub enum Error { pub enum GCode { StatusCommand, BlockDelete, - LineNumber(u32), + LineNumber(Option), #[cfg(feature = "parse-comments")] Comment(String), Word(char, RealValue), diff --git a/src/parser.rs b/src/parser.rs index be7500d..49693e4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -29,12 +29,13 @@ use crate::{ stream::{MyTryStreamExt, PushBackable}, types::{Comment, ParseResult}, utils::skip_whitespaces, - Error, GCode, + Error, GCode, Literal, RealValue }; #[cfg(feature = "future-stream")] use futures::StreamExt; +#[cfg(feature = "parse-checksum")] use values::parse_number; #[cfg(not(feature = "parse-expressions"))] @@ -77,7 +78,7 @@ where } #[cfg(all(feature = "parse-trailing-comment", feature = "parse-comments"))] -async fn parse_eol_comment(input: &mut S) -> Option> +async fn parse_eol_comment(input: &mut S, line_count: &mut u32) -> Option> where S: ByteStream> + PushBackable, { @@ -87,6 +88,7 @@ where match b { b'\r' | b'\n' => { input.push_back(b); + *line_count += 1; break Some(match String::from_utf8(v) { Ok(s) => ParseResult::Ok(s), Err(_) => Error::InvalidUTF8String.into(), @@ -264,16 +266,29 @@ where 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 == 0 { - let b = try_await_result!(self.input.next()); - Err(Error::UnexpectedByte(b).into()) - } else if ord > 5 { - Err(Error::NumberOverflow.into()) - } else { - self.state = AsyncParserState::Segment; - Ok(GCode::LineNumber(n)) - }; + let rv = try_await!(parse_real_value(&mut self.input)); + match rv { + RealValue::Literal(Literal::RealNumber(_rn)) => { + if _rn.scale() > 0 { + break Err(Error::UnexpectedByte(b'.').into()); + } + else { + let lnum = match _rn.integer_part().try_into() { + Ok(_i) => Some(_i), + Err(_) => None + }; + break Ok(GCode::LineNumber(lnum)); + } + } + #[cfg(feature = "optional-value")] + RealValue::None => { + break Ok(GCode::LineNumber(None)); + } + #[cfg(feature = "optional-value")] + _ => { + break Err(Error::InvalidNumberConversion.into()); + } + } } _ => { self.input.push_back(b); diff --git a/src/parser/test.rs b/src/parser/test.rs index 548aede..cbf9bac 100644 --- a/src/parser/test.rs +++ b/src/parser/test.rs @@ -104,25 +104,25 @@ fn line_number() { let input = "n23\n".bytes(); assert_eq!( block_on(input), - &[Ok(GCode::LineNumber(23)), Ok(GCode::Execute)] + &[Ok(GCode::LineNumber(Some(23))), Ok(GCode::Execute)] ); let input = "N0023\n".bytes(); assert_eq!( block_on(input), - &[Ok(GCode::LineNumber(23)), Ok(GCode::Execute)] + &[Ok(GCode::LineNumber(Some(23))), Ok(GCode::Execute)] ); let input = " N 0023 \n".bytes(); assert_eq!( block_on(input), - &[Ok(GCode::LineNumber(23)), Ok(GCode::Execute)] + &[Ok(GCode::LineNumber(Some(23))), Ok(GCode::Execute)] ); } #[test] -fn line_number_with_more_than_5_digits_are_not_ok() { - let input = "N000009\n".bytes(); +fn line_number_with_more_than_9_digits_are_not_ok() { + let input = "N9999999999\n".bytes(); assert_eq!( block_on(input), &[Err(Error::NumberOverflow), Ok(GCode::Execute)] @@ -135,7 +135,6 @@ fn line_number_can_only_be_intergers() { assert_eq!( block_on(input), &[ - Ok(GCode::LineNumber(0)), Err(Error::UnexpectedByte(b'.')), Ok(GCode::Execute) ] @@ -144,8 +143,7 @@ fn line_number_can_only_be_intergers() { assert_eq!( block_on(input), &[ - Ok(GCode::LineNumber(9)), - Err(Error::UnexpectedByte(b'.')), + Ok(GCode::LineNumber(Some(9))), Ok(GCode::Execute) ] ); From bf8e266a7d04737503c5fd2aa3cac2ee781d7391 Mon Sep 17 00:00:00 2001 From: Carlos Barrales Ruiz Date: Tue, 28 Jan 2025 11:52:28 +0100 Subject: [PATCH 6/7] Experimental recovery check --- src/parser.rs | 4 +++- src/stream.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 49693e4..870a518 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -216,9 +216,11 @@ where } } - pub fn reset(&mut self) { + pub async fn reset(&mut self) { #[cfg(feature = "parse-checksum")] self.input.reset_sum(0); + #[cfg(not(feature = "future-stream"))] + self.input.recovery_check().await; self.state = AsyncParserState::Start(true); } diff --git a/src/stream.rs b/src/stream.rs index e8d2932..35ab405 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -17,6 +17,9 @@ pub trait ByteStream { /// Values yielded by the stream. type Item; async fn next(&mut self) -> Option; + + // Used to notify inner stream when there is a reset due timeout or something + async fn recovery_check(&mut self); } #[cfg(not(feature = "future-stream"))] @@ -148,6 +151,10 @@ pub(crate) mod pushback { self.stream.try_next().await } } + + async fn reset(&mut self) { + self.stream.recovery_check().await; + } } #[cfg(test)] @@ -303,6 +310,11 @@ pub(crate) mod xorsum_pushback { self.sum ^= item; Some(Ok(item)) } + + #[cfg(not(feature = "future-stream"))] + async fn recovery_check(&mut self) { + self.stream.recovery_check().await; + } } #[cfg(test)] From 29a1acd7a04a1d7395f81c7d7b827555754c8769 Mon Sep 17 00:00:00 2001 From: Carlos Barrales Ruiz Date: Mon, 10 Feb 2025 17:19:27 +0100 Subject: [PATCH 7/7] Experimental support for string values without word of quotes to support (relaxing standard), for instance M118 Hello World --- Cargo.toml | 4 ++-- src/lib.rs | 2 ++ src/parser.rs | 18 ++++++++++++++++++ src/parser/values.rs | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 55f6b1c..1579274 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ optional-value = [] string-value = [] float-f64 = [] # Use future::stream as input interface. If not set, async-traits will be used insted. -future-stream = ["pin-project-lite"] +future-stream = ["pin-project-lite", "futures"] [badges] maintenance = { status = "experimental" } @@ -31,5 +31,5 @@ futures-executor = { version = "0.3" } [dependencies] either = {version = "^1", default-features = false } -futures = { version = "0.3", default-features = false } +futures = { version = "0.3", optional = true, default-features = false } pin-project-lite = { version = "0.2", optional = true } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b92ed67..d57c504 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,6 +145,8 @@ pub enum GCode { #[cfg(feature = "parse-comments")] Comment(String), Word(char, RealValue), + #[cfg(feature = "string-value")] + Text(RealValue), #[cfg(feature = "parse-parameters")] /// When `optional-value` is enabled, the index cannot be `RealValue::None`. ParameterSet(RealValue, RealValue), diff --git a/src/parser.rs b/src/parser.rs index 870a518..e0a1344 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -41,6 +41,9 @@ use values::parse_number; #[cfg(not(feature = "parse-expressions"))] use values::parse_real_value; +#[cfg(feature = "string-value")] +use crate::parser::values::parse_text; + #[cfg(feature = "parse-expressions")] use expressions::parse_real_value; use crate::stream::{ByteStream, UnpinTrait}; @@ -49,6 +52,8 @@ use crate::stream::{ByteStream, UnpinTrait}; pub enum AsyncParserState { Start(bool), LineNumberOrSegment, + #[cfg(feature = "string-value")] + TextMode, Segment, ErrorRecovery, #[cfg(all(feature = "parse-trailing-comment", feature = "parse-checksum"))] @@ -228,6 +233,11 @@ where self.state } + #[cfg(feature = "string-value")] + pub fn switch_to_text(&mut self) { + self.state = AsyncParserState::TextMode; + } + pub fn get_current_line(&self) -> u32 { self.line_count } @@ -297,6 +307,14 @@ where self.state = AsyncParserState::Segment; } }, + #[cfg(feature="string-value")] + AsyncParserState::TextMode => { + self.input.push_back(b); + try_await_result!(skip_whitespaces(&mut self.input)); + let rv = try_await!(parse_text(&mut self.input)); + self.state = AsyncParserState::EoLOrTrailingComment; + break Ok(GCode::Text(RealValue::Literal(Literal::String(rv)))); + }, AsyncParserState::Segment => match b.to_ascii_lowercase() { b' ' => {} letter @ b'a'..=b'z' => { diff --git a/src/parser/values.rs b/src/parser/values.rs index df2baef..75296d8 100644 --- a/src/parser/values.rs +++ b/src/parser/values.rs @@ -158,6 +158,43 @@ where }) } +#[cfg(feature = "string-value")] +pub(crate) async fn parse_text(input: &mut S) -> Option> +where + S: ByteStream> + PushBackable + UnpinTrait, + E: core::convert::From, +{ + #[cfg(not(feature = "std"))] + use alloc::vec::Vec; + + let mut array = Vec::new(); + loop { + match input.next().await { + Some(Ok(b';')) => { + input.push_back(b';'); + break; + } + Some(Ok(b'\n')) => { + input.push_back(b'\n'); + break; + } + Some(Ok(b)) => { + array.push(b) + } + Some(Err(e)) => { + return Some(Err(e).into()) + } + None => { + break; + } + } + } + match String::from_utf8(array) { + Ok(string) => Some(ParseResult::Ok(string)), + Err(_) => Some(Error::InvalidUTF8String.into()), + } +} + #[cfg(not(feature = "parse-expressions"))] pub(crate) async fn parse_real_value(input: &mut S) -> Option> where