1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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> {
    /// The first non-blank line of a file may contain nothing but a percent sign, %, possibly
    /// surrounded by white space, and later in the file (normally at the end of the file) there
    /// may be a similar line.
    ///
    /// http://linuxcnc.org/docs/html/gcode/overview.html
    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> {
    // #[inline(always)]
    fn args_or_comments_iter(&self) -> impl Iterator<Item = &ArgOrComment<'r>> {
        use std::convert::identity;

        self.args_or_comments
            .iter()
            .flat_map(identity)
    }

    // #[inline(always)]
    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
                }
            })
    }

    // #[inline(always)]
    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
                }
            })
    }
}