extern crate nom;
use std::fmt;
use thiserror::Error;
mod mnemonic;
pub use mnemonic::*;
mod parse_command;
pub use parse_command::parse_command;
mod parse_args;
pub use parse_args::parse_args;
pub use parse_args::parse_kv_arg;
mod parse_comments;
pub use parse_comments::*;
mod parse_gcode;
pub use parse_gcode::parse_gcode;
#[derive(Error, Debug)]
pub enum GCodeParseError {
#[error("Invalid GCode. GCodes must start with a letter, a number and a space. Got: {0}")]
InvalidGCode(String),
#[error("Badly formatted GCode arguments. Got: {0}")]
InvalidArguments(String),
#[error("Badly formatted GCode comment. Got: {0}")]
InvalidComment(String),
}
#[derive(Debug, PartialEq, Clone)]
pub struct Comment<'r>(
pub &'r str
);
#[derive(Debug, PartialEq, Clone)]
pub enum DocComment<'r> {
GCodeFlavor(&'r str),
PrintTime(std::time::Duration),
FilamentUsed { meters: f64 },
LayerHeight { millis: f64 },
}
#[derive(Debug, PartialEq, Clone)]
pub enum GCodeLine<'r> {
FileDemarcator,
GCode(GCode<'r>),
Comment(Comment<'r>),
DocComment(DocComment<'r>),
}
impl<'r> From<Comment<'r>> for GCodeLine<'r> {
fn from(comment: Comment<'r>) -> Self {
GCodeLine::Comment(comment)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct GCode<'r> {
pub line_number: Option<u32>,
pub mnemonic: Mnemonic,
pub major: u32,
pub minor: u32,
args_or_comments: Option<Vec<ArgOrComment<'r>>>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum ArgOrComment<'r> {
KeyValue(KeyValue),
TextArg(&'r str),
Comment(Comment<'r>),
}
pub type KeyValue = (char, Option<f32>);
impl<'r> fmt::Display for GCode<'r> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let gcode = format!("{}{}.{}", self.mnemonic, self.major, self.minor);
let mut words = vec![gcode];
let arg_words = self.arguments()
.map(|(k, v)| {
format!("{}{}", k, v.map(|f| f.to_string()).unwrap_or("".to_string()))
});
words.extend(arg_words);
if let Some(text) = self.text() {
words.push(text.to_string());
};
write!(f, "{}", words.join(" "))
}
}
impl<'r> GCode<'r> {
fn args_or_comments_iter(&self) -> impl Iterator<Item = &ArgOrComment<'r>> {
use std::convert::identity;
self.args_or_comments
.iter()
.flat_map(identity)
}
pub fn text(&self) -> Option<&'r str> {
self.args_or_comments_iter()
.find_map(|ac| {
if let ArgOrComment::TextArg(text) = ac {
Some(*text)
} else {
None
}
})
}
pub fn arguments(&self) -> impl Iterator<Item = &KeyValue> {
self.args_or_comments_iter()
.filter_map(|ac| {
if let ArgOrComment::KeyValue(arg) = ac {
Some(arg)
} else {
None
}
})
}
}