Skip to main content

g_code/parse/compact/binary/
file.rs

1use nom::{
2    Compare, IResult, Input, Parser,
3    bytes::streaming::tag,
4    combinator::{iterator, opt},
5    number::streaming::le_u16,
6};
7
8use crate::parse::compact::binary::blocks::{
9    BlockHeader, BlockType, EncodingType, GCodeEncodingType, ThumbnailParameters, parse_block,
10};
11
12type Block<P, I> = (BlockHeader, P, I, Option<u32>);
13
14/// Ordered representation of the contents of a binary g-code file.
15#[derive(Debug)]
16pub struct File<I> {
17    pub header: FileHeader,
18    pub file_meta: Option<Block<EncodingType, I>>,
19    pub printer_meta: Block<EncodingType, I>,
20    pub thumbnails: Vec<Block<ThumbnailParameters, I>>,
21    pub print_meta: Block<EncodingType, I>,
22    pub slicer_meta: Block<EncodingType, I>,
23    pub gcode_blocks: Vec<Block<GCodeEncodingType, I>>,
24}
25
26impl<I: Input<Item = u8> + for<'a> Compare<&'a [u8]>> File<I> {
27    /// Parse a complete file into its expected format.
28    pub fn parse(input: I) -> IResult<I, File<I>> {
29        let stage = FileHeaderParser;
30        let (input, (header, stage)) = stage.parser().parse(input)?;
31        let (input, (file_meta, stage)) = stage.parser().parse(input)?;
32        let (input, (printer_meta, stage)) = stage.parser().parse(input)?;
33        let (input, (thumbnails, stage)) = stage.parser().parse(input)?;
34        let (input, (print_meta, stage)) = stage.parser().parse(input)?;
35        let (input, (slicer_meta, stage)) = stage.parser().parse(input)?;
36        let (input, gcode_blocks) = stage.parser().parse(input)?;
37
38        Ok((
39            input,
40            Self {
41                header,
42                file_meta,
43                printer_meta,
44                thumbnails,
45                print_meta,
46                slicer_meta,
47                gcode_blocks,
48            },
49        ))
50    }
51}
52
53pub struct FileHeaderParser;
54
55impl FileHeaderParser {
56    pub fn parser<I>(
57        &self,
58    ) -> impl Parser<I, Output = (FileHeader, FileMetaParser), Error = nom::error::Error<I>>
59    where
60        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
61    {
62        FileHeader::parser().map(move |header| {
63            let checksum_type = header.checksum_type;
64            (header, FileMetaParser(checksum_type))
65        })
66    }
67}
68
69pub struct FileMetaParser(ChecksumType);
70
71impl FileMetaParser {
72    pub fn parser<I>(
73        &self,
74    ) -> impl Parser<
75        I,
76        Output = (Option<Block<EncodingType, I>>, PrinterMetaParser),
77        Error = nom::error::Error<I>,
78    >
79    where
80        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
81    {
82        let checksum_type = self.0;
83        opt(move |input| {
84            parse_block(
85                BlockType::FileMetadata,
86                checksum_type,
87                EncodingType::parser(),
88                input,
89            )
90        })
91        .map(move |opt| (opt, PrinterMetaParser(checksum_type)))
92    }
93}
94
95pub struct PrinterMetaParser(ChecksumType);
96
97impl PrinterMetaParser {
98    pub fn parser<I>(
99        &self,
100    ) -> impl Parser<
101        I,
102        Output = (
103            (BlockHeader, EncodingType, I, Option<u32>),
104            ThumbnailsParser,
105        ),
106        Error = nom::error::Error<I>,
107    >
108    where
109        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
110    {
111        let checksum_type = self.0;
112        (move |input| {
113            parse_block(
114                BlockType::PrinterMetadata,
115                checksum_type,
116                EncodingType::parser(),
117                input,
118            )
119        })
120        .map(move |block| (block, ThumbnailsParser(checksum_type)))
121    }
122}
123
124pub struct ThumbnailsParser(ChecksumType);
125
126impl ThumbnailsParser {
127    pub fn parser<I>(
128        &self,
129    ) -> impl Parser<
130        I,
131        Output = (Vec<Block<ThumbnailParameters, I>>, PrintMetaParser),
132        Error = nom::error::Error<I>,
133    >
134    where
135        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
136    {
137        let checksum_type = self.0;
138        move |input| {
139            let mut it = iterator(input, move |input| {
140                parse_block(
141                    BlockType::Thumbnail,
142                    checksum_type,
143                    ThumbnailParameters::parser(),
144                    input,
145                )
146            });
147
148            let thumbnails = (&mut it).collect::<Vec<_>>();
149            it.finish()
150                .map(|(input, ())| (input, (thumbnails, PrintMetaParser(checksum_type))))
151        }
152    }
153}
154
155pub struct PrintMetaParser(ChecksumType);
156
157impl PrintMetaParser {
158    pub fn parser<I>(
159        &self,
160    ) -> impl Parser<
161        I,
162        Output = (
163            (BlockHeader, EncodingType, I, Option<u32>),
164            SlicerMetaParser,
165        ),
166        Error = nom::error::Error<I>,
167    >
168    where
169        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
170    {
171        let checksum_type = self.0;
172        (move |input| {
173            parse_block(
174                BlockType::PrintMetadata,
175                checksum_type,
176                EncodingType::parser(),
177                input,
178            )
179        })
180        .map(move |block| (block, SlicerMetaParser(checksum_type)))
181    }
182}
183
184pub struct SlicerMetaParser(ChecksumType);
185
186impl SlicerMetaParser {
187    pub fn parser<I>(
188        &self,
189    ) -> impl Parser<
190        I,
191        Output = (
192            (BlockHeader, EncodingType, I, Option<u32>),
193            GCodeBlocksParser,
194        ),
195        Error = nom::error::Error<I>,
196    >
197    where
198        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
199    {
200        let checksum_type = self.0;
201        (move |input| {
202            parse_block(
203                BlockType::SlicerMetadata,
204                checksum_type,
205                EncodingType::parser(),
206                input,
207            )
208        })
209        .map(move |block| (block, GCodeBlocksParser(checksum_type)))
210    }
211}
212
213pub struct GCodeBlocksParser(ChecksumType);
214
215impl GCodeBlocksParser {
216    pub fn parser<I>(
217        &self,
218    ) -> impl Parser<
219        I,
220        Output = Vec<(BlockHeader, GCodeEncodingType, I, Option<u32>)>,
221        Error = nom::error::Error<I>,
222    >
223    where
224        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
225    {
226        let checksum_type = self.0;
227        move |input| {
228            let mut it = iterator(input, move |input| {
229                parse_block(
230                    BlockType::Thumbnail,
231                    checksum_type,
232                    GCodeEncodingType::parser(),
233                    input,
234                )
235            });
236
237            let gcode_blocks = (&mut it).collect::<Vec<_>>();
238            it.finish().map(|(input, ())| (input, gcode_blocks))
239        }
240    }
241}
242
243/// 10 bytes at the beginning of each file.
244///
245/// <https://github.com/prusa3d/libbgcode/blob/main/doc/specifications.md#file-header>
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct FileHeader {
248    pub checksum_type: ChecksumType,
249}
250
251impl FileHeader {
252    pub fn parser<I>() -> impl Parser<I, Output = Self, Error = nom::error::Error<I>>
253    where
254        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
255    {
256        const MAGIC_NUMBER: &[u8; 4] = b"GCDE";
257        const VERSION_BYTES: [u8; 4] = 1u32.to_le_bytes();
258
259        tag(MAGIC_NUMBER.as_slice())
260            .and(tag(VERSION_BYTES.as_slice()))
261            .and(ChecksumType::parser())
262            .map(|((_magic_number, _version), checksum)| Self {
263                checksum_type: checksum,
264            })
265    }
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269#[repr(u16)]
270pub enum ChecksumType {
271    None = 0,
272    /// 32-bit checksum at the end of each block.
273    ///
274    /// <https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks#CRC-32_example>
275    CRC32 = 1,
276}
277
278impl ChecksumType {
279    pub fn parser<I>() -> impl Parser<I, Output = Self, Error = nom::error::Error<I>>
280    where
281        I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
282    {
283        le_u16.map_res(|value| match value {
284            0 => Ok(Self::None),
285            1 => Ok(Self::CRC32),
286            _other => Err(()),
287        })
288    }
289}