use arrayvec::ArrayVec;
use core::cmp;
use core::fmt::{self, Display, Formatter};
pub const MAX_ARGS: usize = 8;
type Words = [Word; MAX_ARGS];
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Gcode {
mnemonic: Mnemonic,
number: f32,
line_number: Option<u32>,
arguments: ArrayVec<Words>,
span: Span,
}
impl Gcode {
pub fn new(mnemonic: Mnemonic, number: f32, span: Span) -> Gcode {
Gcode {
mnemonic,
number,
span,
arguments: ArrayVec::default(),
line_number: None,
}
}
pub fn mnemonic(&self) -> Mnemonic {
self.mnemonic
}
pub fn span(&self) -> Span {
self.span
}
pub fn args(&self) -> &[Word] {
&self.arguments
}
pub fn line_number(&self) -> Option<u32> {
self.line_number
}
#[deprecated(
note = "You probably want the `Gcode::major_number()` and `Gcode::minor_number()` methods instead"
)]
pub fn number(&self) -> f32 {
self.number
}
pub fn major_number(&self) -> u32 {
self.number.trunc() as u32
}
pub fn minor_number(&self) -> Option<u32> {
let fraction = self.number.abs().fract();
let first_digit = (fraction / 0.1).round() as u32;
if first_digit == 0 {
None
} else {
Some(first_digit)
}
}
fn merge_span(&mut self, span: Span) {
self.span = self.span.merge(span);
}
pub fn add_argument(&mut self, mut arg: Word) {
self.merge_span(arg.span);
arg.letter = arg.letter.to_ascii_uppercase();
match self.arguments.iter().position(|w| w.letter == arg.letter) {
Some(i) => self.arguments[i] = arg,
None => {
let _ = self.arguments.try_push(arg);
}
}
}
pub fn with_argument(mut self, arg: Word) -> Self {
self.add_argument(arg);
self
}
pub fn with_line_number(mut self, number: u32, span: Span) -> Self {
self.merge_span(span);
self.line_number = Some(number);
self
}
pub fn value_for(&self, letter: char) -> Option<f32> {
let letter = letter.to_ascii_uppercase();
self.arguments
.iter()
.find(|word| letter == word.letter)
.map(|word| word.value)
}
}
impl Display for Gcode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Some(n) = self.line_number() {
write!(f, "N{} ", n)?;
}
write!(f, "{}", self.mnemonic())?;
write!(f, "{}", self.number)?;
for arg in self.args() {
write!(f, " {}", arg)?;
}
Ok(())
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct Word {
pub letter: char,
pub value: f32,
pub span: Span,
}
impl Word {
pub fn new(letter: char, value: f32, span: Span) -> Word {
Word {
letter,
value,
span,
}
}
}
impl Display for Word {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}{}", self.letter, self.value)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(C)]
pub enum Mnemonic {
ProgramNumber,
ToolChange,
MachineRoutine,
General,
}
impl Default for Mnemonic {
fn default() -> Mnemonic {
Mnemonic::General
}
}
impl Display for Mnemonic {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let c = match *self {
Mnemonic::ProgramNumber => 'O',
Mnemonic::ToolChange => 'T',
Mnemonic::MachineRoutine => 'M',
Mnemonic::General => 'G',
};
write!(f, "{}", c)
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct Span {
pub start: usize,
pub end: usize,
pub source_line: usize,
}
impl Span {
pub fn new(start: usize, end: usize, source_line: usize) -> Span {
debug_assert!(start <= end);
Span {
start,
end,
source_line,
}
}
pub fn merge(&self, other: Span) -> Span {
Span {
start: cmp::min(self.start, other.start),
end: cmp::max(self.end, other.end),
source_line: cmp::min(self.source_line, other.source_line),
}
}
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn selected_text<'input>(
&self,
src: &'input str,
) -> Option<&'input str> {
src.get(self.start..self.end)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_gcode_repr() {
let thing = Gcode::new(Mnemonic::General, 1.2, Span::default())
.with_line_number(10, Span::default())
.with_argument(Word::new('X', 500.0, Span::default()))
.with_argument(Word::new('Y', -1.23, Span::default()));
let should_be = "N10 G1.2 X500 Y-1.23";
let got = format!("{}", thing);
assert_eq!(got, should_be);
}
#[test]
fn you_can_round_trip_a_gcode() {
let original = Gcode::new(Mnemonic::General, 1.2, Span::new(0, 20, 0))
.with_line_number(10, Span::default())
.with_argument(Word::new('X', 500.0, Span::new(9, 13, 0)))
.with_argument(Word::new('Y', -1.23, Span::new(14, 20, 0)));
let serialized = format!("{}", original);
let got = ::parse(&serialized).next().unwrap();
assert_eq!(got, original);
}
#[test]
fn major_and_minor_numbers_make_sense() {
let inputs = vec![
(1.0, 1, None),
(1.1, 1, Some(1)),
(1.2, 1, Some(2)),
(1.3, 1, Some(3)),
(1.4, 1, Some(4)),
(1.5, 1, Some(5)),
(1.6, 1, Some(6)),
(1.7, 1, Some(7)),
(1.8, 1, Some(8)),
(1.9, 1, Some(9)),
(2.0, 2, None),
];
for (src, major, minor) in inputs {
let g = Gcode::new(Mnemonic::General, src, Span::default());
assert_eq!(g.major_number(), major);
assert_eq!(g.minor_number(), minor);
}
}
}