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
use nom::{
    character::complete::*,
    error::VerboseError,
};
use nom::branch::*;
use nom::combinator::*;
use nom::sequence::*;

use super::{
    parse_command,
    parse_args,
    comment,
    doc_comment,
    GCodeParseError,
    // GCode,
    GCodeLine,
    M,
    GCodeParseError::*,
};

const STRING_ARG_MCODES: [u32; 7] = [
    23,
    28,
    30,
    // 32, Unsupported: Marlin M32 is a whole different kind of weird
    36,
    // M37 Unsupported: RepRap M37 is a whole different kind of weird
    38,
    117,
    118,
];

// #[inline(always)]
pub fn parse_gcode(input: &str) -> Result<(&str, Option<GCodeLine>), GCodeParseError> {
    let original_input = input;
    let demarcator = map(
        pair(char('%'), not_line_ending),
        |_: (char, &str)| GCodeLine::FileDemarcator,
    );

    // Strip leading whitespace
    let (input, _) = space0::<_, VerboseError<&str>>(input)
        .map_err(|_|
            InvalidGCode(original_input.to_string())
        )?;


    // empty lines without a newline character
    if input.is_empty() {
        return Ok((input, None));
    };

    // Parse and return a non-gcode (empty line, demarcator or comment)
    let mut non_gcode_line= alt((
        // Empty line
        map(newline, |_| None),
        map(
            alt((
                // Demarcator (eg. "%")
                demarcator,
                // Doc Comment Line (eg. ";TIME:3600")
                map(doc_comment, |doc| GCodeLine::DocComment(doc)),
                // Comment Line (eg. "; Comment")
                map(comment, |comment| GCodeLine::Comment(comment))
            )),
            |line| Some(line),
        ),
    ));

    if let Ok((input, gcode_line)) = non_gcode_line(input) {
        return Ok((input, gcode_line));
    };

    /*
     * Parse the GCode command (eg. this would parse "G1" out of "G1 X10")
     */

    let (input, mut gcode) = parse_command(input)
        .map_err(|_|
            GCodeParseError::InvalidGCode(original_input.to_string())
        )?;

    /*
     * Parse the GCode args (eg. this would parse "X10" out of "G1 X10")
     */

    let string_arg_mcode =
        gcode.mnemonic == M
        && gcode.minor == 0
        && STRING_ARG_MCODES.contains(&gcode.major);

    let (input, args_or_comments) = parse_args(
        string_arg_mcode,
        input,
    )
        .map_err(|_| InvalidArguments(original_input.to_string()))?;

    gcode.args_or_comments = args_or_comments;
    Ok((input, Some(GCodeLine::GCode(gcode))))
}