g-code 0.0.1

GCode parsing and emission
Documentation
use crate::parse::ast;
use crate::parse::lexer;
use crate::parse::token;
use lexer::LexicalError;
use num_rational::Ratio;

grammar<'input>(input: &'input str);

extern {
    type Location = usize;
    type Error = LexicalError;

    enum lexer::LexTok<'input> {
        TokNewline => lexer::LexTok::Newline,
        TokDot => lexer::LexTok::Dot,
        TokStar => lexer::LexTok::Star,
        TokMinus => lexer::LexTok::Minus,
        TokPercent => lexer::LexTok::Percent,
        TokString => lexer::LexTok::String(<&'input str>),
        TokInlineComment => lexer::LexTok::InlineComment(<&'input str>),
        TokComment => lexer::LexTok::Comment(<&'input str>),
        TokInteger => lexer::LexTok::Integer(<&'input str>),
        TokLetters => lexer::LexTok::Letters(<&'input str>),
        TokWhitespace => lexer::LexTok::Whitespace(<&'input str>)
    }
}

Checksum: token::Checksum = <left:@L> <star:TokStar> <checksum:TokInteger> <right:@R> =>? Ok(token::Checksum {
    inner: checksum.parse::<u8>().map_err(|e| LexicalError::ParseIntError(e, left + 1, right))?,
    span: ast::Span(left, right)
});

Field: token::Field<'input> = {
    <left:@L> <letters:TokLetters> <value:TokInteger> <right:@R> =>? {
        Ok(token::Field {
            letters,
            value: token::Value::Integer(value.parse::<usize>().map_err(|e| LexicalError::ParseIntError(e, left + letters.len(), right))?),
            raw_value: vec![value],
            span: ast::Span(left, right)
        })
    },

    <left:@L> <letters:TokLetters> <neg:TokMinus> <value:TokInteger> <right:@R> =>? {
        let value_start = left + letters.len() + 1;
        let value_end = right;
        Ok(token::Field {
            letters,
            value: token::Value::Rational(-value.parse::<Ratio<i64>>().map_err(|e| LexicalError::ParseRatioError(e, value_start, value_end))?),
            raw_value: vec!["-", value],
            span: ast::Span(left, right)
        })
    },

    <left:@L> <letters:TokLetters> <neg:TokMinus?> <lhs:TokInteger> <dot:TokDot> <rhs:TokInteger?> <right:@R> =>? {
        let lhs_start = left + letters.len() + neg.as_ref().map(|_| 1).unwrap_or(0);
        let lhs_end = lhs_start + lhs.len();
        let rhs_start = lhs_end + 1;
        let rhs_end = rhs_start + rhs.map(|x| x.len()).unwrap_or(0);
        Ok(token::Field {
            letters,
            value: token::Value::Rational(lhs.parse::<Ratio<i64>>()
                .map_err(|e| LexicalError::ParseRatioError(e, lhs_start, lhs_end))
                .and_then(|lhs| if let Some(rhs_str) = rhs {
                    rhs_str.parse::<i64>()
                           .map(|rhs| Ratio::new(rhs, 10i64.pow(rhs_str.len() as u32)))
                           .map(|rhs| lhs + rhs)
                           .map(|value| if neg.is_some() { -value } else { value })
                           .map_err(|e| LexicalError::ParseIntError(e, rhs_start, rhs_end))
                } else {
                    Ok(lhs)
                })?),
            raw_value: if neg.is_some() { vec!["-", lhs, ".", rhs.unwrap_or("")] } else { vec![".", rhs.unwrap_or("")] },
            span: ast::Span(left, right)
        })
    },

    <left:@L> <letters:TokLetters> <neg:TokMinus?> <dot:TokDot> <rhs_str:TokInteger> <right:@R> =>? {
        let rhs_start = left + letters.len() + neg.as_ref().map(|_| 1).unwrap_or(0) + 1;
        let rhs_end = right;
        Ok(token::Field {
            letters,
            value: token::Value::Rational(rhs_str.parse::<i64>()
                .map(|rhs| Ratio::new(rhs, 10i64.pow(rhs_str.len() as u32)))
                .map(|rhs| if neg.is_some() { -rhs } else { rhs })
                .map_err(|e| LexicalError::ParseIntError(e, rhs_start, rhs_end))?),
            raw_value: if neg.is_some() { vec!["-", ".", rhs_str] } else { vec![".", rhs_str] },
            span: ast::Span(left, right)
        })
    },

    <left:@L> <letters:TokLetters> <string:TokString> <right:@R> => token::Field {
        letters,
        value: token::Value::String(string),
        raw_value: vec![string],
        span: ast::Span(left, right)
    }
};

Comment: token::Comment<'input> = <left:@L> <comment:TokComment> => token::Comment {
    inner: comment,
    pos: left,
};

InlineComment: token::InlineComment<'input> = <left:@L> <comment:TokInlineComment> => token::InlineComment {
    inner: comment,
    pos: left,
};

Whitespace: token::Whitespace<'input> = <left:@L> <whitespace:TokWhitespace> => token::Whitespace {
    inner: whitespace,
    pos: left,
};

Line: ast::Line<'input> = <left:@L> <fields:(InlineComment* (Whitespace InlineComment*)* Field)*> <checksum:(InlineComment* (Whitespace InlineComment*)* Checksum)?> <comment:(InlineComment* (Whitespace InlineComment*)* Comment)?> <whitespace:((Whitespace InlineComment*)* Whitespace)?> <inline_comment:InlineComment*> <right:@R> => ast::Line {
    fields,
    checksum,
    comment,
    whitespace,
    inline_comment,
    span: ast::Span(left, right)
};

Newline: token::Newline = <left:@L> TokNewline => token::Newline { pos: left };

pub File: ast::File<'input> = {
    <left:@L> <lines:(Line Newline)*> <last_line:Line> <right:@R> => ast::File {
        start_percent: false,
        lines,
        last_line: if last_line.fields.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() && last_line.inline_comment.is_empty() {
            None
        } else {
            Some(last_line)
        },
        end_percent: false,
        span: ast::Span(left, right)
    },

    <left:@L> <start_percent:TokPercent> <lines:(Line Newline)*> <last_line:Line> <end_percent:TokPercent> <right:@R> => ast::File {
        start_percent: false,
        lines,
        last_line: if last_line.fields.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() && last_line.inline_comment.is_empty() {
            None
        } else {
            Some(last_line)
        },
        end_percent: false,
        span: ast::Span(left, right)
    },
};

pub Snippet: ast::Snippet<'input> = {
    <left:@L> <lines:(Line Newline)*> <last_line:Line> <right:@R> => ast::Snippet {
        lines,
        last_line: if last_line.fields.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() && last_line.inline_comment.is_empty() {
            None
        } else {
            Some(last_line)
        },
        span: ast::Span(left, right)
    },
};