• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

djeedai / weldr / 4257424771

pending completion
4257424771

push

github

GitHub
nom 7.0 and code cleanup, fixes #21 (#22)

303 of 303 new or added lines in 6 files covered. (100.0%)

1641 of 1738 relevant lines covered (94.42%)

1.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.7
/lib/src/lib.rs
1
//! # weldr
2
//!
3
//! weldr is a Rust library to manipulate [LDraw](https://www.ldraw.org/) files
4
//! ([format specification](https://www.ldraw.org/article/218.html)), which are files describing
5
//! 3D models of [LEGO®](http://www.lego.com)* pieces.
6
//!
7
//! weldr allows building command-line tools and applications leveraging
8
//! [the fantastic database of pieces](https://www.ldraw.org/cgi-bin/ptlist.cgi) contributed by
9
//! the LDraw community.
10
//!
11
//! ## Example
12
//!
13
//! Parse a single LDraw file containing 2 commands:
14
//! - A comment : "this is a comment"
15
//! - A segment command to draw a segment between 2 vertices
16
//!
17
//! ```rust
18
//! extern crate weldr;
19
//!
20
//! use weldr::{parse_raw, Command, CommentCmd, LineCmd, Vec3};
21
//!
22
//! fn main() {}
23
//!
24
//! #[test]
25
//! fn parse_ldr() {
26
//!   let ldr = b"0 this is a comment\n2 16 0 0 0 1 1 1";
27
//!   let cmds = parse_raw(ldr);
28
//!   let cmd0 = Command::Comment(CommentCmd::new("this is a comment"));
29
//!   let cmd1 = Command::Line(LineCmd{
30
//!     color: 16,
31
//!     vertices: [
32
//!       Vec3{ x: 0.0, y: 0.0, z: 0.0 },
33
//!       Vec3{ x: 1.0, y: 1.0, z: 1.0 }
34
//!     ]
35
//!   });
36
//!   assert_eq!(cmds, vec![cmd0, cmd1]);
37
//! }
38
//! ```
39
//!
40
//! A slightly more involved but more powerful approach is to load and resolve a file and all its
41
//! sub-file references recursively using the [`parse()`] function. This requires implementing the
42
//! [`FileRefResolver`] trait to load file content by reference filename.
43
//!
44
//! The code is available on [GitHub](https://github.com/djeedai/weldr).
45
//!
46
//! ## Technical features
47
//!
48
//! weldr leverages the [nom parser combinator library](https://crates.io/crates/nom) to efficiently
49
//! and reliably parse LDraw files, and transform them into in-memory data structures for consumption.
50
//! All parsing is done on `&[u8]` input expected to contain [specification](https://www.ldraw.org/article/218.html)-compliant
51
//! LDraw content. In particular, this means:
52
//!
53
//! - UTF-8 encoded input
54
//! - Both DOS/Windows `<CR><LF>` and Unix `<LF>` line termination accepted
55
//!
56
//! ## Copyrights
57
//!
58
//! The current code repository is licensed under the MIT license.
59
//!
60
//! LDrawâ„¢ is a trademark owned and licensed by the Estate of James Jessiman, which does not sponsor, endorse, or authorize this project.
61
//!
62
//! *LEGO® is a registered trademark of the LEGO Group, which does not sponsor, endorse, or authorize this project.
63

64
// TEMP
65
#![allow(dead_code)]
66

67
#[macro_use]
68
extern crate log;
69

70
use nom::{
71
    branch::alt,
72
    bytes::complete::{tag, tag_no_case, take_while, take_while1, take_while_m_n},
73
    character::{
74
        complete::{digit1, line_ending as eol},
75
        is_alphanumeric, is_digit,
76
    },
77
    combinator::{complete, map, map_res, opt},
78
    error::ErrorKind,
79
    multi::{many0, separated_list1},
80
    number::complete::float,
81
    sequence::{terminated, tuple},
82
    IResult, InputTakeAtPosition,
83
};
84
use std::{
85
    collections::{HashMap, HashSet},
86
    str,
87
};
88

89
pub type Vec3 = cgmath::Vector3<f32>;
90
pub type Vec4 = cgmath::Vector4<f32>;
91
pub type Mat4 = cgmath::Matrix4<f32>;
92

93
#[cfg(feature = "cgmath")]
94
pub type Vec4 = cgmath::Vector4<f32>;
95

96
#[cfg(feature = "cgmath")]
97
pub type Mat4 = cgmath::Matrix4<f32>;
98

99
pub mod error;
100

101
pub use error::{Error, ParseError, ResolveError};
102

103
// LDraw File Format Specification
104
// https://www.ldraw.org/article/218.html
105

106
fn nom_error(i: &[u8], kind: ErrorKind) -> nom::Err<nom::error::Error<&[u8]>> {
1✔
107
    nom::Err::Error(nom::error::Error::new(i, kind))
1✔
108
}
109

110
// "Whitespace is defined as one or more spaces (#32), tabs (#9), or combination thereof."
111
fn is_space(chr: u8) -> bool {
3✔
112
    chr == b'\t' || chr == b' '
3✔
113
}
114

115
fn take_spaces(i: &[u8]) -> IResult<&[u8], &[u8]> {
×
116
    take_while(is_space)(i)
×
117
}
118

119
// "All lines in the file must use the standard DOS/Windows line termination of <CR><LF>
120
// (carriage return/line feed). The file is permitted (but not required) to end with a <CR><LF>.
121
// It is recommended that all LDraw-compliant programs also be capable of reading files with the
122
// standard Unix line termination of <LF> (line feed)."
123
fn end_of_line(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
124
    if i.is_empty() {
1✔
125
        Ok((i, i))
1✔
126
    } else {
127
        eol(i)
1✔
128
    }
129
}
130

131
// Detect a *potential* end of line <CR><LF> or <LF> by testing for either of <CR>
132
// and <LF>. Note that this doesn't necessarily means a proper end of line if <CR>
133
// is not followed by <LF>, but we assume this doesn't happen.
134
#[inline]
135
fn is_cr_or_lf(chr: u8) -> bool {
3✔
136
    chr == b'\n' || chr == b'\r'
3✔
137
}
138

139
// Parse any character which is not <CR> or <LF>, potentially until the end of input.
140
fn take_not_cr_or_lf(i: &[u8]) -> IResult<&[u8], &[u8]> {
2✔
141
    i.split_at_position_complete(is_cr_or_lf)
2✔
142
}
143

144
// Parse a single comma ',' character.
145
fn single_comma(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
146
    if !i.is_empty() && (i[0] == b',') {
2✔
147
        Ok((&i[1..], &i[..1]))
1✔
148
    } else {
149
        // To work with separated_list!(), must return an Err::Error
150
        // when the separator doesn't parse anymore (and therefore
151
        // the list ends).
152
        Err(nom_error(i, nom::error::ErrorKind::Tag))
1✔
153
    }
154
}
155

156
// Parse any character which is not a comma ',' or <CR> or <LF>, potentially until the end of input.
157
// Invalid on empty input.
158
fn take_not_comma_or_eol(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
159
    take_while1(|item| item != b',' && !is_cr_or_lf(item))(i)
3✔
160
}
161

162
// Parse any character which is not a space, potentially until the end of input.
163
fn take_not_space(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
164
    i.split_at_position_complete(is_space)
1✔
165
}
166

167
// Read the command ID and swallow the following space, if any.
168
fn read_cmd_id_str(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
169
    //terminated(take_while1(is_digit), sp)(i) //< This does not work if there's no space (e.g. 4-4cylo.dat)
170
    let (i, o) = i.split_at_position1_complete(|item| !is_digit(item), ErrorKind::Digit)?;
12✔
171
    let (i, _) = space0(i)?;
6✔
172
    Ok((i, o))
3✔
173
}
174

175
fn category(i: &[u8]) -> IResult<&[u8], Command> {
2✔
176
    let (i, _) = tag(b"!CATEGORY")(i)?;
4✔
177
    let (i, _) = sp(i)?;
2✔
178
    let (i, content) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
2✔
179

180
    Ok((
1✔
181
        i,
182
        Command::Category(CategoryCmd {
1✔
183
            category: content.to_string(),
1✔
184
        }),
185
    ))
186
}
187

188
fn keywords_list(i: &[u8]) -> IResult<&[u8], Vec<&str>> {
1✔
189
    separated_list1(single_comma, map_res(take_not_comma_or_eol, str::from_utf8))(i)
1✔
190
}
191

192
fn keywords(i: &[u8]) -> IResult<&[u8], Command> {
2✔
193
    let (i, (_, _, keywords)) = tuple((tag(b"!KEYWORDS"), sp, keywords_list))(i)?;
4✔
194
    Ok((
1✔
195
        i,
196
        Command::Keywords(KeywordsCmd {
1✔
197
            keywords: keywords.iter().map(|kw| kw.trim().to_string()).collect(),
4✔
198
        }),
199
    ))
200
}
201

202
/// RGB color in sRGB color space.
203
#[derive(Debug, PartialEq)]
204
pub struct Color {
205
    pub red: u8,
206
    pub green: u8,
207
    pub blue: u8,
208
}
209

210
impl Color {
211
    /// Construct a new color instance from individual RGB components.
212
    pub fn new(red: u8, green: u8, blue: u8) -> Color {
1✔
213
        Color { red, green, blue }
214
    }
215
}
216

217
fn from_hex(i: &[u8]) -> Result<u8, nom::error::ErrorKind> {
1✔
218
    match std::str::from_utf8(i) {
1✔
219
        Ok(s) => match u8::from_str_radix(s, 16) {
1✔
220
            Ok(val) => Ok(val),
1✔
221
            Err(_) => Err(ErrorKind::AlphaNumeric),
1✔
222
        },
223
        Err(_) => Err(ErrorKind::AlphaNumeric),
1✔
224
    }
225
}
226

227
fn is_hex_digit(c: u8) -> bool {
1✔
228
    (c as char).is_ascii_hexdigit()
1✔
229
}
230

231
fn hex_primary(i: &[u8]) -> IResult<&[u8], u8> {
1✔
232
    map_res(take_while_m_n(2, 2, is_hex_digit), from_hex)(i)
1✔
233
}
234

235
fn hex_color(i: &[u8]) -> IResult<&[u8], Color> {
1✔
236
    let (i, _) = tag(b"#")(i)?;
2✔
237
    let (i, (red, green, blue)) = tuple((hex_primary, hex_primary, hex_primary))(i)?;
3✔
238
    Ok((i, Color { red, green, blue }))
1✔
239
}
240

241
fn digit1_as_u8(i: &[u8]) -> IResult<&[u8], u8> {
1✔
242
    map_res(map_res(digit1, str::from_utf8), str::parse::<u8>)(i)
1✔
243
}
244

245
// ALPHA part of !COLOUR
246
fn colour_alpha(i: &[u8]) -> IResult<&[u8], Option<u8>> {
1✔
247
    opt(complete(|i| {
248
        let (i, _) = sp(i)?;
249
        let (i, _) = tag(b"ALPHA")(i)?;
250
        let (i, _) = sp(i)?;
251
        digit1_as_u8(i)
252
    }))(i)
1✔
253
}
254

255
// LUMINANCE part of !COLOUR
256
fn colour_luminance(i: &[u8]) -> IResult<&[u8], Option<u8>> {
1✔
257
    opt(complete(|i| {
258
        let (i, _) = sp(i)?;
259
        let (i, _) = tag(b"LUMINANCE")(i)?;
260
        let (i, _) = sp(i)?;
261
        digit1_as_u8(i)
262
    }))(i)
1✔
263
}
264

265
fn material_grain_size(i: &[u8]) -> IResult<&[u8], GrainSize> {
1✔
266
    alt((grain_size, grain_min_max_size))(i)
1✔
267
}
268

269
fn grain_size(i: &[u8]) -> IResult<&[u8], GrainSize> {
1✔
270
    // TODO: Create tagged float helper?
271
    let (i, (_, _, size)) = tuple((tag(b"SIZE"), sp, float))(i)?;
2✔
272
    Ok((i, GrainSize::Size(size)))
1✔
273
}
274

275
fn grain_min_max_size(i: &[u8]) -> IResult<&[u8], GrainSize> {
1✔
276
    let (i, (_, _, min_size)) = tuple((tag(b"MINSIZE"), sp, float))(i)?;
2✔
277
    let (i, _) = sp(i)?;
3✔
278
    let (i, (_, _, max_size)) = tuple((tag(b"MAXSIZE"), sp, float))(i)?;
2✔
279
    Ok((i, GrainSize::MinMaxSize((min_size, max_size))))
1✔
280
}
281

282
// GLITTER VALUE v [ALPHA a] [LUMINANCE l] FRACTION f VFRACTION vf (SIZE s | MINSIZE min MAXSIZE max)
283
fn glitter_material(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
284
    let (i, _) = tag_no_case(b"GLITTER")(i)?;
2✔
285
    let (i, _) = sp(i)?;
3✔
286
    let (i, _) = tag_no_case(b"VALUE")(i)?;
2✔
287
    let (i, _) = sp(i)?;
2✔
288
    let (i, value) = hex_color(i)?;
2✔
289
    let (i, alpha) = colour_alpha(i)?;
2✔
290
    let (i, luminance) = colour_luminance(i)?;
2✔
291
    let (i, _) = sp(i)?;
2✔
292
    let (i, _) = tag_no_case(b"FRACTION")(i)?;
2✔
293
    let (i, _) = sp(i)?;
2✔
294
    let (i, surface_fraction) = float(i)?;
2✔
295
    let (i, _) = sp(i)?;
2✔
296
    let (i, _) = tag_no_case(b"VFRACTION")(i)?;
2✔
297
    let (i, _) = sp(i)?;
2✔
298
    let (i, volume_fraction) = float(i)?;
2✔
299
    let (i, _) = sp(i)?;
2✔
300
    let (i, size) = material_grain_size(i)?;
2✔
301

302
    Ok((
1✔
303
        i,
304
        ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
305
            value,
1✔
306
            alpha,
307
            luminance,
308
            surface_fraction,
309
            volume_fraction,
310
            size,
1✔
311
        })),
312
    ))
313
}
314

315
// SPECKLE VALUE v [ALPHA a] [LUMINANCE l] FRACTION f (SIZE s | MINSIZE min MAXSIZE max)
316
fn speckle_material(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
317
    let (i, _) = tag_no_case(b"SPECKLE")(i)?;
2✔
318
    let (i, _) = sp(i)?;
3✔
319
    let (i, _) = tag_no_case(b"VALUE")(i)?;
2✔
320
    let (i, _) = sp(i)?;
2✔
321
    let (i, value) = hex_color(i)?;
2✔
322
    let (i, alpha) = colour_alpha(i)?;
2✔
323
    let (i, luminance) = colour_luminance(i)?;
2✔
324
    let (i, _) = sp(i)?;
2✔
325
    let (i, _) = tag_no_case(b"FRACTION")(i)?;
2✔
326
    let (i, _) = sp(i)?;
2✔
327
    let (i, surface_fraction) = float(i)?;
2✔
328
    let (i, _) = sp(i)?;
2✔
329
    let (i, size) = material_grain_size(i)?;
2✔
330

331
    Ok((
1✔
332
        i,
333
        ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
334
            value,
1✔
335
            alpha,
336
            luminance,
337
            surface_fraction,
338
            size,
1✔
339
        })),
340
    ))
341
}
342

343
// Other unrecognized MATERIAL definition
344
fn other_material(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
345
    let (i, content) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
1✔
346
    let finish = content.trim().to_string();
1✔
347
    Ok((i, ColorFinish::Material(MaterialFinish::Other(finish))))
1✔
348
}
349

350
// MATERIAL finish part of !COLOUR
351
fn material_finish(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
352
    let (i, _) = tag_no_case(b"MATERIAL")(i)?;
1✔
353
    alt((glitter_material, speckle_material, other_material))(i)
1✔
354
}
355

356
// Finish part of !COLOUR
357
// TODO: Avoid having the leading space in each parser?
358
fn color_finish(i: &[u8]) -> IResult<&[u8], Option<ColorFinish>> {
1✔
359
    opt(complete(|i| {
360
        let (i, _) = sp(i)?;
361
        alt((
362
            map(tag_no_case(b"CHROME"), |_| ColorFinish::Chrome),
363
            map(tag_no_case(b"PEARLESCENT"), |_| ColorFinish::Pearlescent),
364
            map(tag_no_case(b"RUBBER"), |_| ColorFinish::Rubber),
365
            map(tag_no_case(b"MATTE_METALLIC"), |_| {
366
                ColorFinish::MatteMetallic
367
            }),
368
            map(tag_no_case(b"METAL"), |_| ColorFinish::Metal),
369
            material_finish,
370
        ))(i)
371
    }))(i)
1✔
372
}
373

374
// !COLOUR extension meta-command
375
fn meta_colour(i: &[u8]) -> IResult<&[u8], Command> {
2✔
376
    let (i, _) = tag(b"!COLOUR")(i)?;
4✔
377
    let (i, _) = sp(i)?;
2✔
378
    let (i, name) = map_res(take_not_space, str::from_utf8)(i)?;
2✔
379
    let (i, _) = sp(i)?;
2✔
380
    let (i, _) = tag(b"CODE")(i)?;
2✔
381
    let (i, _) = sp(i)?;
2✔
382
    let (i, code) = color_id(i)?;
2✔
383
    let (i, _) = sp(i)?;
2✔
384
    let (i, _) = tag(b"VALUE")(i)?;
2✔
385
    let (i, _) = sp(i)?;
2✔
386
    let (i, value) = hex_color(i)?;
2✔
387
    let (i, _) = sp(i)?;
3✔
388
    let (i, _) = tag(b"EDGE")(i)?;
2✔
389
    let (i, _) = sp(i)?;
2✔
390
    let (i, edge) = hex_color(i)?;
2✔
391
    let (i, alpha) = colour_alpha(i)?;
2✔
392
    let (i, luminance) = colour_luminance(i)?;
2✔
393
    let (i, finish) = color_finish(i)?;
2✔
394

395
    Ok((
1✔
396
        i,
397
        Command::Colour(ColourCmd {
1✔
398
            name: name.to_string(),
1✔
399
            code,
400
            value,
1✔
401
            edge,
1✔
402
            alpha,
403
            luminance,
404
            finish,
1✔
405
        }),
406
    ))
407
}
408

409
fn comment(i: &[u8]) -> IResult<&[u8], Command> {
2✔
410
    let (i, comment) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
2✔
411
    Ok((i, Command::Comment(CommentCmd::new(comment))))
2✔
412
}
413

414
fn meta_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
415
    alt((
416
        complete(category),
417
        complete(keywords),
418
        complete(meta_colour),
419
        comment,
420
    ))(i)
421
}
422

423
fn read_vec3(i: &[u8]) -> IResult<&[u8], Vec3> {
3✔
424
    let (i, (x, _, y, _, z)) = tuple((float, sp, float, sp, float))(i)?;
3✔
425
    Ok((i, Vec3 { x, y, z }))
3✔
426
}
427

428
fn color_id(i: &[u8]) -> IResult<&[u8], u32> {
3✔
429
    map_res(map_res(digit1, str::from_utf8), str::parse::<u32>)(i)
3✔
430
}
431

432
#[inline]
433
fn is_filename_char(chr: u8) -> bool {
2✔
434
    is_alphanumeric(chr) || chr == b'/' || chr == b'\\' || chr == b'.' || chr == b'-'
2✔
435
}
436

437
fn filename_char(i: &[u8]) -> IResult<&[u8], &[u8]> {
2✔
438
    // TODO - Split at EOL instead and accept all characters for filename?
439
    i.split_at_position1_complete(|item| !is_filename_char(item), ErrorKind::AlphaNumeric)
6✔
440
}
441

442
fn filename(i: &[u8]) -> IResult<&[u8], &str> {
2✔
443
    map_res(filename_char, str::from_utf8)(i)
2✔
444
}
445

446
fn file_ref_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
447
    let (i, color) = color_id(i)?;
2✔
448
    let (i, _) = sp(i)?;
4✔
449
    let (i, pos) = read_vec3(i)?;
4✔
450
    let (i, _) = sp(i)?;
4✔
451
    let (i, row0) = read_vec3(i)?;
4✔
452
    let (i, _) = sp(i)?;
4✔
453
    let (i, row1) = read_vec3(i)?;
4✔
454
    let (i, _) = sp(i)?;
4✔
455
    let (i, row2) = read_vec3(i)?;
4✔
456
    let (i, _) = sp(i)?;
4✔
457
    let (i, file) = filename(i)?;
4✔
458

459
    Ok((
2✔
460
        i,
461
        Command::SubFileRef(SubFileRefCmd {
2✔
462
            color,
463
            pos,
2✔
464
            row0,
2✔
465
            row1,
2✔
466
            row2,
2✔
467
            file: SubFileRef::UnresolvedRef(file.to_string()),
2✔
468
        }),
469
    ))
470
}
471

472
fn line_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
473
    let (i, color) = color_id(i)?;
2✔
474
    let (i, _) = sp(i)?;
4✔
475
    let (i, v1) = read_vec3(i)?;
4✔
476
    let (i, _) = sp(i)?;
4✔
477
    let (i, v2) = read_vec3(i)?;
4✔
478
    let (i, _) = space0(i)?;
4✔
479

480
    Ok((
2✔
481
        i,
482
        Command::Line(LineCmd {
2✔
483
            color,
484
            vertices: [v1, v2],
2✔
485
        }),
486
    ))
487
}
488

489
fn tri_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
490
    let (i, color) = color_id(i)?;
2✔
491
    let (i, _) = sp(i)?;
4✔
492
    let (i, v1) = read_vec3(i)?;
4✔
493
    let (i, _) = sp(i)?;
4✔
494
    let (i, v2) = read_vec3(i)?;
4✔
495
    let (i, _) = sp(i)?;
4✔
496
    let (i, v3) = read_vec3(i)?;
4✔
497
    let (i, _) = space0(i)?;
4✔
498

499
    Ok((
2✔
500
        i,
501
        Command::Triangle(TriangleCmd {
2✔
502
            color,
503
            vertices: [v1, v2, v3],
2✔
504
        }),
505
    ))
506
}
507

508
fn quad_cmd(i: &[u8]) -> IResult<&[u8], Command> {
3✔
509
    let (i, color) = color_id(i)?;
3✔
510
    let (i, _) = sp(i)?;
6✔
511
    let (i, v1) = read_vec3(i)?;
6✔
512
    let (i, _) = sp(i)?;
6✔
513
    let (i, v2) = read_vec3(i)?;
6✔
514
    let (i, _) = sp(i)?;
6✔
515
    let (i, v3) = read_vec3(i)?;
6✔
516
    let (i, _) = sp(i)?;
6✔
517
    let (i, v4) = read_vec3(i)?;
6✔
518
    let (i, _) = space0(i)?;
6✔
519

520
    Ok((
3✔
521
        i,
522
        Command::Quad(QuadCmd {
3✔
523
            color,
524
            vertices: [v1, v2, v3, v4],
3✔
525
        }),
526
    ))
527
}
528

529
fn opt_line_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
530
    let (i, color) = color_id(i)?;
2✔
531
    let (i, _) = sp(i)?;
4✔
532
    let (i, v1) = read_vec3(i)?;
4✔
533
    let (i, _) = sp(i)?;
4✔
534
    let (i, v2) = read_vec3(i)?;
4✔
535
    let (i, _) = sp(i)?;
4✔
536
    let (i, v3) = read_vec3(i)?;
2✔
537
    let (i, _) = sp(i)?;
2✔
538
    let (i, v4) = read_vec3(i)?;
2✔
539
    let (i, _) = space0(i)?;
2✔
540

541
    Ok((
1✔
542
        i,
543
        Command::OptLine(OptLineCmd {
1✔
544
            color,
545
            vertices: [v1, v2],
1✔
546
            control_points: [v3, v4],
1✔
547
        }),
548
    ))
549
}
550

551
// Zero or more "spaces", as defined in LDraw standard.
552
// Valid even on empty input.
553
fn space0(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
554
    i.split_at_position_complete(|item| !is_space(item))
9✔
555
}
556

557
// One or more "spaces", as defined in LDraw standard.
558
// Valid even on empty input.
559
fn sp(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
560
    i.split_at_position1_complete(|item| !is_space(item), ErrorKind::Space)
9✔
561
}
562

563
// Zero or more "spaces", as defined in LDraw standard.
564
// Valid even on empty input.
565
fn space_or_eol0(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
566
    i.split_at_position_complete(|item| !is_space(item) && !is_cr_or_lf(item))
9✔
567
}
568

569
// An empty line made of optional spaces, and ending with an end-of-line sequence
570
// (either <CR><LF> or <LF> alone) or the end of input.
571
// Valid even on empty input.
572
fn empty_line(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
573
    terminated(space0, end_of_line)(i)
1✔
574
}
575

576
// "There is no line length restriction. Each command consists of optional leading
577
// whitespace followed by whitespace-delimited tokens. Some commands also have trailing
578
// arbitrary data which may itself include internal whitespace; such data is not tokenized,
579
// but treated as single unit according to the command."
580
//
581
// "Lines may also be empty or consist only of whitespace. Such lines have no effect."
582
//
583
// "The line type of a line is the first number on the line."
584
// "If the line type of the command is invalid, the line is ignored."
585
fn read_line(i: &[u8]) -> IResult<&[u8], Command> {
3✔
586
    let (i, _) = space_or_eol0(i)?;
3✔
587
    let (i, cmd_id) = read_cmd_id_str(i)?;
9✔
588
    let (i, cmd) = match cmd_id {
7✔
589
        b"0" => meta_cmd(i),
5✔
590
        b"1" => file_ref_cmd(i),
2✔
591
        b"2" => line_cmd(i),
2✔
592
        b"3" => tri_cmd(i),
2✔
593
        b"4" => quad_cmd(i),
3✔
594
        b"5" => opt_line_cmd(i),
2✔
595
        _ => Err(nom_error(i, ErrorKind::Switch)),
×
596
    }?;
597
    Ok((i, cmd))
3✔
598
}
599

600
/// Parse raw LDR content without sub-file resolution.
601
///
602
/// Parse the given LDR data passed in `ldr_content` and return the list of parsed commands.
603
/// Sub-file references (Line Type 1) are not resolved, and returned as [`SubFileRef::UnresolvedRef`].
604
///
605
/// The input LDR content must comply to the LDraw standard. In particular this means:
606
/// - UTF-8 encoded, without Byte Order Mark (BOM)
607
/// - Both DOS/Windows <CR><LF> and Unix <LF> line termination accepted
608
///
609
/// ```rust
610
/// use weldr::{parse_raw, Command, CommentCmd, LineCmd, Vec3};
611
///
612
/// let cmd0 = Command::Comment(CommentCmd::new("this is a comment"));
613
/// let cmd1 = Command::Line(LineCmd{
614
///   color: 16,
615
///   vertices: [
616
///     Vec3{ x: 0.0, y: 0.0, z: 0.0 },
617
///     Vec3{ x: 1.0, y: 1.0, z: 1.0 }
618
///   ]
619
/// });
620
/// assert_eq!(parse_raw(b"0 this is a comment\n2 16 0 0 0 1 1 1").unwrap(), vec![cmd0, cmd1]);
621
/// ```
622
pub fn parse_raw(ldr_content: &[u8]) -> Result<Vec<Command>, Error> {
1✔
623
    parse_raw_with_filename("", ldr_content)
1✔
624
}
625

626
fn parse_raw_with_filename(filename: &str, ldr_content: &[u8]) -> Result<Vec<Command>, Error> {
3✔
627
    // "An LDraw file consists of one command per line."
628
    many0(read_line)(ldr_content).map_or_else(
6✔
629
        |e| Err(Error::Parse(ParseError::new_from_nom(filename, &e))),
3✔
630
        |(_, cmds)| Ok(cmds),
6✔
631
    )
632
}
633

634
struct QueuedFileRef {
635
    /// Filename of unresolved source file.
636
    filename: String,
637
    /// Referer source file which requested the resolution.
638
    referer: SourceFileRef,
639
}
640

641
struct ResolveQueue {
642
    /// Queue of pending items to resolve and load.
643
    queue: Vec<QueuedFileRef>,
644
    /// Number of pending items in the queue for each filename.
645
    pending_count: HashMap<String, u32>,
646
}
647

648
/// Drawing context used when iterating over all drawing commands of a file via [`SourceFile::iter()`].
649
#[derive(Debug, Copy, Clone)]
650
pub struct DrawContext {
651
    /// Current transformation matrix for the drawing command. This is the accumulated transformation
652
    /// of all parent files.
653
    ///
654
    /// When drawing a primitive (line, triangle, quad), the actual position of vertices is obtained
655
    /// by transforming the local-space positions of the drawing command by this transformation matrix.
656
    ///
657
    /// ```rustc,ignore
658
    /// let v0 = draw_ctx.transform * cmd.vertices[0];
659
    /// ```
660
    pub transform: Mat4,
661

662
    /// Current color for substitution of color 16.
663
    pub color: u32,
664
}
665

666
/// Iterator over all drawing commands of a [`SourceFile`] and all its referenced sub-files.
667
///
668
/// Sub-file reference commands are not yielded, but instead the drawing commands of those
669
/// sub-files are iterated over. Comment commands are skipped.
670
pub struct CommandIterator<'a> {
671
    stack: Vec<(&'a SourceFile, usize, DrawContext)>,
672
    source_map: &'a SourceMap,
673
}
674

675
/// Iterator over all local commands of a [`SourceFile`].
676
///
677
/// Sub-file reference commands and comment commands are yielded like all other commands.
678
/// No command from any other file is yielded.
679
pub struct LocalCommandIterator<'a> {
680
    stack: Vec<&'a SourceFile>,
681
    index: usize,
682
    source_map: &'a SourceMap,
683
}
684

685
// impl std::iter::IntoIterator for SourceFile {
686
//     type Item = &'a Command;
687
//     type IntoIter = &'a CommandIterator;
688

689
//     fn into_iter(self) -> Self::IntoIter {
690
//         CommandIterator {
691
//             stack: vec![self.clone()],
692
//             index: 0,
693
//         }
694
//     }
695
// }
696

697
impl SourceFile {
698
    /// Return an iterator over all drawing commands, recursively stepping into sub-file references
699
    /// without returning the corresponding [`SubFileRefCmd`] command nor any comment command.
700
    pub fn iter<'a>(&'a self, source_map: &'a SourceMap) -> CommandIterator<'a> {
2✔
701
        let draw_ctx = DrawContext {
702
            transform: Mat4::from_scale(1.0),
2✔
703
            color: 16,
704
        };
705
        CommandIterator {
706
            stack: vec![(self, 0, draw_ctx)],
2✔
707
            source_map,
708
        }
709
    }
710

711
    /// Return an iterator over all commands local to this source file, including sub-file references
712
    /// and comments. Unlike [`SourceFile::iter()`], this doesn't step into those sub-file references
713
    /// but remains in the local source file.
714
    pub fn local_iter<'a>(&'a self, source_map: &'a SourceMap) -> LocalCommandIterator<'a> {
1✔
715
        LocalCommandIterator {
716
            stack: vec![self],
1✔
717
            index: 0,
718
            source_map,
719
        }
720
    }
721
}
722

723
impl<'a> Iterator for CommandIterator<'a> {
724
    type Item = (DrawContext, &'a Command);
725

726
    fn next(&mut self) -> Option<(DrawContext, &'a Command)> {
2✔
727
        while let Some(entry) = self.stack.last_mut() {
2✔
728
            let cmds = &entry.0.cmds;
2✔
729
            let index = &mut entry.1;
2✔
730
            let draw_ctx = &entry.2;
2✔
731
            if *index < cmds.len() {
2✔
732
                let cmd = &cmds[*index];
2✔
733
                *index += 1;
2✔
734
                if let Command::SubFileRef(sfr_cmd) = &cmd {
3✔
735
                    if let SubFileRef::ResolvedRef(resolved_ref) = &sfr_cmd.file {
2✔
736
                        let source_file_2 = resolved_ref.get(self.source_map);
1✔
737
                        let local_transform = Mat4::from_cols(
738
                            Vec4::new(sfr_cmd.row0.x, sfr_cmd.row1.x, sfr_cmd.row2.x, 0.0),
1✔
739
                            Vec4::new(sfr_cmd.row0.y, sfr_cmd.row1.y, sfr_cmd.row2.y, 0.0),
1✔
740
                            Vec4::new(sfr_cmd.row0.z, sfr_cmd.row1.z, sfr_cmd.row2.z, 0.0),
1✔
741
                            Vec4::new(sfr_cmd.pos.x, sfr_cmd.pos.y, sfr_cmd.pos.z, 1.0),
1✔
742
                        );
743
                        let draw_ctx = DrawContext {
744
                            transform: draw_ctx.transform * local_transform,
1✔
745
                            color: 16,
746
                        };
747
                        self.stack.push((source_file_2, 0, draw_ctx));
1✔
748
                        continue;
×
749
                    }
750
                } else if let Command::Comment(_) = &cmd {
2✔
751
                    // Skip comments
752
                    continue;
×
753
                }
754
                return Some((*draw_ctx, cmd));
2✔
755
            }
756
            self.stack.pop();
2✔
757
        }
758
        None
2✔
759
    }
760
}
761

762
impl<'a> Iterator for LocalCommandIterator<'a> {
763
    type Item = &'a Command;
764

765
    fn next(&mut self) -> Option<&'a Command> {
1✔
766
        while let Some(source_file) = self.stack.last() {
1✔
767
            let cmds = &source_file.cmds;
1✔
768
            if self.index < cmds.len() {
1✔
769
                let index = self.index;
1✔
770
                self.index += 1;
3✔
771
                return Some(&cmds[index]);
1✔
772
            }
773
            self.index = 0;
1✔
774
            self.stack.pop();
1✔
775
        }
776
        None
1✔
777
    }
778
}
779

780
impl ResolveQueue {
781
    fn new() -> ResolveQueue {
2✔
782
        ResolveQueue {
783
            queue: vec![],
2✔
784
            pending_count: HashMap::new(),
2✔
785
        }
786
    }
787

788
    fn push(&mut self, filename: &str, referer_filename: &str, referer: SourceFileRef) {
1✔
789
        if let Some(num_pending) = self.pending_count.get_mut(referer_filename) {
2✔
790
            assert!(*num_pending > 0); // should not make it to the queue if already resolved
1✔
791
            *num_pending += 1;
2✔
792
        } else {
793
            self.pending_count.insert(referer_filename.to_string(), 1);
1✔
794
        }
795
        self.queue.push(QueuedFileRef {
2✔
796
            filename: filename.to_string(),
1✔
797
            referer,
×
798
        });
799
    }
800

801
    fn pop(&mut self, source_map: &SourceMap) -> Option<(QueuedFileRef, u32)> {
2✔
802
        match self.queue.pop() {
2✔
803
            Some(qfr) => {
1✔
804
                let num_pending = self
1✔
805
                    .pending_count
×
806
                    .get_mut(&qfr.referer.get(source_map).filename)
1✔
807
                    .unwrap();
808
                *num_pending -= 1;
1✔
809
                Some((qfr, *num_pending))
1✔
810
            }
811
            None => None,
2✔
812
        }
813
    }
814

815
    fn reset(&mut self) {
×
816
        self.queue.clear();
×
817
        self.pending_count.clear();
×
818
    }
819
}
820

821
fn load_and_parse_single_file(
2✔
822
    filename: &str,
823
    resolver: &dyn FileRefResolver,
824
) -> Result<SourceFile, Error> {
825
    //match resolver.resolve(filename) {}
826
    let raw_content = resolver.resolve(filename)?;
3✔
827
    let mut source_file = SourceFile {
828
        filename: filename.to_string(),
2✔
829
        raw_content,
830
        cmds: Vec::new(),
2✔
831
    };
832
    let cmds = parse_raw_with_filename(&source_file.filename[..], &source_file.raw_content[..])?;
4✔
833
    source_file.cmds = cmds;
4✔
834
    Ok(source_file)
2✔
835
}
836

837
/// Parse a single file and its sub-file references recursively.
838
///
839
/// Attempt to load the content of `filename` via the given `resolver`, and parse it.
840
/// Then recursiverly look for sub-file commands inside that root file, and try to resolve
841
/// the content of those sub-files and parse them too. All the loaded and parsed files end
842
/// up populating the given `source_map`, which can be pre-populated manually or from a
843
/// previous call with already loaded and parsed files.
844
/// ```rust
845
/// use weldr::{ FileRefResolver, parse, ResolveError, SourceMap };
846
///
847
/// struct MyCustomResolver;
848
///
849
/// impl FileRefResolver for MyCustomResolver {
850
///   fn resolve(&self, filename: &str) -> Result<Vec<u8>, ResolveError> {
851
///     Ok(vec![]) // replace with custom impl
852
///   }
853
/// }
854
///
855
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
856
///   let resolver = MyCustomResolver{};
857
///   let mut source_map = SourceMap::new();
858
///   let root_file_ref = parse("root.ldr", &resolver, &mut source_map)?;
859
///   let root_file = root_file_ref.get(&source_map);
860
///   assert_eq!(root_file.filename, "root.ldr");
861
///   Ok(())
862
/// }
863
/// ```
864
pub fn parse(
2✔
865
    filename: &str,
866
    resolver: &dyn FileRefResolver,
867
    source_map: &mut SourceMap,
868
) -> Result<SourceFileRef, Error> {
869
    if let Some(existing_file) = source_map.find_filename(filename) {
2✔
870
        return Ok(existing_file);
×
871
    }
872
    debug!("Processing root file '{}'", filename);
4✔
873
    let root_file = load_and_parse_single_file(filename, resolver)?;
5✔
874
    trace!(
6✔
875
        "Post-loading resolving subfile refs of root file: {}",
876
        filename
877
    );
878
    let root_file_ref = source_map.insert(root_file);
4✔
879
    let mut queue = ResolveQueue::new();
2✔
880
    source_map.resolve_file_refs(root_file_ref, &mut queue);
4✔
881
    while let Some(queued_file) = queue.pop(source_map) {
5✔
882
        let num_pending_left = queued_file.1;
1✔
883
        let filename = &queued_file.0.filename;
1✔
884
        debug!("Processing sub-file: '{}'", filename);
3✔
885
        match source_map.find_filename(filename) {
2✔
886
            Some(_) => trace!("Already parsed; reusing sub-file: {}", filename),
2✔
887
            None => {
888
                trace!("Not yet parsed; parsing sub-file: {}", filename);
3✔
889
                let source_file = load_and_parse_single_file(&filename[..], resolver)?;
2✔
890
                let source_file_ref = source_map.insert(source_file);
2✔
891
                trace!(
2✔
892
                    "Post-loading resolving subfile refs of sub-file: {}",
893
                    filename
894
                );
895
                source_map.resolve_file_refs(source_file_ref, &mut queue);
1✔
896
            }
897
        }
898
        // Re-resolve the source file that triggered this sub-file loading to update its subfile refs
899
        // if there is no more sub-file references enqueued.
900
        if num_pending_left == 0 {
1✔
901
            trace!(
3✔
902
                "Re-resolving referer file on last resolved ref: {}",
903
                queued_file.0.referer.get(source_map).filename
×
904
            );
905
            source_map.resolve_file_refs(queued_file.0.referer, &mut queue);
2✔
906
        }
907
    }
908
    Ok(root_file_ref)
2✔
909
}
910

911
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
912
/// [!CATEGORY language extension](https://www.ldraw.org/article/340.html#category).
913
#[derive(Debug, PartialEq)]
914
pub struct CategoryCmd {
915
    /// Category name.
916
    pub category: String,
917
}
918

919
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
920
/// [!KEYWORDS language extension](https://www.ldraw.org/article/340.html#keywords).
921
#[derive(Debug, PartialEq)]
922
pub struct KeywordsCmd {
923
    /// List of keywords.
924
    pub keywords: Vec<String>,
925
}
926

927
/// Finish for color definitions ([!COLOUR language extension](https://www.ldraw.org/article/299.html)).
928
#[derive(Debug, PartialEq)]
929
pub enum ColorFinish {
930
    Chrome,
931
    Pearlescent,
932
    Rubber,
933
    MatteMetallic,
934
    Metal,
935
    Material(MaterialFinish),
936
}
937

938
/// Finish for optional MATERIAL part of color definition
939
/// ([!COLOUR language extension](https://www.ldraw.org/article/299.html)).
940
#[derive(Debug, PartialEq)]
941
pub enum MaterialFinish {
942
    Glitter(GlitterMaterial),
943
    Speckle(SpeckleMaterial),
944
    Other(String),
945
}
946

947
/// Grain size variants for the optional MATERIAL part of color definition
948
/// ([!COLOUR language extension](https://www.ldraw.org/article/299.html)).
949
#[derive(Debug, PartialEq)]
950
pub enum GrainSize {
951
    Size(f32),
952
    MinMaxSize((f32, f32)),
953
}
954

955
/// Glitter material definition of a color definition
956
/// ([!COLOUR language extension](https://www.ldraw.org/article/299.html)).
957
#[derive(Debug, PartialEq)]
958
pub struct GlitterMaterial {
959
    /// Primary color value of the material.
960
    pub value: Color,
961
    /// Optional alpha (opacity) value.
962
    pub alpha: Option<u8>,
963
    /// Optional brightness value.
964
    pub luminance: Option<u8>,
965
    /// Fraction of the surface using the alternate color.
966
    pub surface_fraction: f32,
967
    /// Fraction of the volume using the alternate color.
968
    pub volume_fraction: f32,
969
    /// Size of glitter grains.
970
    pub size: GrainSize,
971
}
972

973
/// Speckle material definition of a color definition
974
/// ([!COLOUR language extension](https://www.ldraw.org/article/299.html)).
975
#[derive(Debug, PartialEq)]
976
pub struct SpeckleMaterial {
977
    /// Primary color value of the material.
978
    pub value: Color,
979
    /// Optional alpha (opacity) value.
980
    pub alpha: Option<u8>,
981
    /// Optional brightness value.
982
    pub luminance: Option<u8>,
983
    /// Fraction of the surface using the alternate color.
984
    pub surface_fraction: f32,
985
    /// Size of speckle grains.
986
    pub size: GrainSize,
987
}
988

989
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
990
/// [!COLOUR language extension](https://www.ldraw.org/article/299.html).
991
#[derive(Debug, PartialEq)]
992
pub struct ColourCmd {
993
    /// Name of the color.
994
    pub name: String,
995
    /// Color code uniquely identifying this color. Codes 16 and 24 are reserved.
996
    pub code: u32,
997
    /// Primary value of the color.
998
    pub value: Color,
999
    /// Contrasting edge value of the color.
1000
    pub edge: Color,
1001
    /// Optional alpha (opacity) value.
1002
    pub alpha: Option<u8>,
1003
    /// Optional ["brightness for colors that glow"](https://www.ldraw.org/article/299.html#luminance).
1004
    pub luminance: Option<u8>,
1005
    /// Finish/texture of the object for high-fidelity rendering.
1006
    pub finish: Option<ColorFinish>,
1007
}
1008

1009
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) comment.
1010
#[derive(Debug, PartialEq)]
1011
pub struct CommentCmd {
1012
    /// Comment content, excluding the command identififer `0` and the optional comment marker `//`.
1013
    pub text: String,
1014
}
1015

1016
/// Single LDraw source file loaded and optionally parsed.
1017
#[derive(Debug, PartialEq)]
1018
pub struct SourceFile {
1019
    /// The relative filename of the file as resolved.
1020
    pub filename: String,
1021
    /// Raw UTF-8 file content (without BOM) loaded from the resolved file. Line ending can be
1022
    /// Unix style `\n` or Windows style `\r\n`. As a convenience, parsing handles both indifferently,
1023
    /// so the file can contain a mix of both, although this is not recommended.
1024
    pub raw_content: Vec<u8>,
1025
    /// LDraw commands parsed from the raw text content of the file.
1026
    pub cmds: Vec<Command>,
1027
}
1028

1029
/// Collection of [`SourceFile`] accessible from their reference filename.
1030
#[derive(Debug)]
1031
pub struct SourceMap {
1032
    /// Array of source files in the collection.
1033
    source_files: Vec<SourceFile>,
1034

1035
    /// Map table of source files indices into [`SourceMap::source_files`] from
1036
    /// their reference filename, as it appears in [`SubFileRefCmd`].
1037
    filename_map: HashMap<String, usize>,
1038
}
1039

1040
/// Reference to a single [`SourceFile`] instance in a given [`SourceMap`].
1041
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)]
1042
pub struct SourceFileRef {
1043
    index: usize,
1044
    // TODO: ref to SourceMap?
1045
}
1046

1047
impl CommentCmd {
1048
    pub fn new(text: &str) -> CommentCmd {
2✔
1049
        CommentCmd {
1050
            text: text.to_string(),
2✔
1051
        }
1052
    }
1053
}
1054

1055
impl SourceFileRef {
1056
    pub fn get<'a>(&'a self, source_map: &'a SourceMap) -> &'a SourceFile {
3✔
1057
        source_map.get(*self)
3✔
1058
    }
1059
}
1060

1061
impl SourceMap {
1062
    /// Construct a new empty source map.
1063
    pub fn new() -> SourceMap {
3✔
1064
        SourceMap {
1065
            source_files: vec![],
3✔
1066
            filename_map: HashMap::new(),
3✔
1067
        }
1068
    }
1069

1070
    /// Get a reference to the source file corresponding to a [`SourceFileRef`].
1071
    fn get(&self, source_file_ref: SourceFileRef) -> &SourceFile {
3✔
1072
        &self.source_files[source_file_ref.index]
3✔
1073
    }
1074

1075
    /// Get a mutable reference to the source file corresponding to a [`SourceFileRef`].
1076
    fn get_mut(&mut self, source_file_ref: SourceFileRef) -> &mut SourceFile {
2✔
1077
        &mut self.source_files[source_file_ref.index]
2✔
1078
    }
1079

1080
    /// Find a source file by its reference filename.
1081
    fn find_filename(&self, filename: &str) -> Option<SourceFileRef> {
2✔
1082
        self.filename_map
2✔
1083
            .get(filename)
×
1084
            .map(|&index| SourceFileRef { index })
2✔
1085
    }
1086

1087
    /// Insert a new source file into the collection.
1088
    fn insert(&mut self, source_file: SourceFile) -> SourceFileRef {
3✔
1089
        if let Some(&index) = self.filename_map.get(&source_file.filename) {
9✔
1090
            SourceFileRef { index }
1091
        } else {
1092
            let index = self.source_files.len();
3✔
1093
            self.filename_map
3✔
1094
                .insert(source_file.filename.clone(), index);
3✔
1095
            self.source_files.push(source_file);
3✔
1096
            SourceFileRef { index }
1097
        }
1098
    }
1099

1100
    /// Attempt to resolve the sub-file references of a given source file, and insert unresolved
1101
    /// references into the given [`ResolveQueue`].
1102
    fn resolve_file_refs(&mut self, source_file_ref: SourceFileRef, queue: &mut ResolveQueue) {
2✔
1103
        // Steal filename map to decouple its lifetime from the one of the source files vector,
1104
        // and allow lookup while mutably iterating over the source files and their commands
1105
        let mut filename_map = HashMap::new();
2✔
1106
        std::mem::swap(&mut filename_map, &mut self.filename_map);
2✔
1107

1108
        // Iterate over the commands of the current file and try to enqueue all its unresolved
1109
        // sub-file reference commands for deferred resolution.
1110
        let source_file = self.get_mut(source_file_ref);
2✔
1111
        let referer_filename = source_file.filename.clone();
2✔
1112
        let mut subfile_set = HashSet::new();
2✔
1113
        for cmd in &mut source_file.cmds {
6✔
1114
            if let Command::SubFileRef(sfr_cmd) = cmd {
2✔
1115
                // All subfile refs come out of parse_raw() as unresolved by definition,
1116
                // since parse_raw() doesn't have access to the source map nor the resolver.
1117
                // See if we can resolve some of them now.
1118
                if let SubFileRef::UnresolvedRef(subfilename) = &sfr_cmd.file {
2✔
1119
                    let subfilename = subfilename.clone();
1✔
1120
                    // Check the source map
1121
                    if let Some(existing_source_file) = filename_map
2✔
1122
                        .get(&subfilename)
×
1123
                        .map(|&index| SourceFileRef { index })
2✔
1124
                    {
1125
                        // If already parsed, reuse
1126
                        trace!(
3✔
1127
                            "Updating resolved subfile ref in {} -> {}",
1128
                            referer_filename,
×
1129
                            subfilename
×
1130
                        );
1131
                        sfr_cmd.file = SubFileRef::ResolvedRef(existing_source_file);
1✔
1132
                    } else if subfile_set.contains(&subfilename) {
1✔
1133
                        trace!(
2✔
1134
                            "Ignoring already-queued unresolved subfile ref in {} -> {}",
1135
                            referer_filename,
×
1136
                            subfilename
×
1137
                        );
1138
                    } else {
1139
                        // If not, push to queue for later parsing, but only once
1140
                        trace!(
3✔
1141
                            "Queuing unresolved subfile ref in {} -> {}",
1142
                            referer_filename,
×
1143
                            subfilename
×
1144
                        );
1145
                        queue.push(&subfilename[..], &referer_filename[..], source_file_ref);
2✔
1146
                        subfile_set.insert(subfilename);
1✔
1147
                    }
1148
                }
1149
            }
1150
        }
1151

1152
        // Restore the filename map
1153
        std::mem::swap(&mut filename_map, &mut self.filename_map);
2✔
1154
    }
1155
}
1156

1157
impl Default for SourceMap {
1158
    fn default() -> Self {
×
1159
        Self::new()
×
1160
    }
1161
}
1162

1163
/// Reference to a sub-file from inside another file.
1164
#[derive(Debug, PartialEq)]
1165
pub enum SubFileRef {
1166
    /// Resolved reference pointing to the given loaded/parsed sub-file.
1167
    ResolvedRef(SourceFileRef),
1168
    /// Unresolved reference containing the raw reference filename.
1169
    UnresolvedRef(String),
1170
}
1171

1172
/// [Line Type 1](https://www.ldraw.org/article/218.html#lt1) LDraw command:
1173
/// Reference a sub-file from the current file.
1174
#[derive(Debug, PartialEq)]
1175
pub struct SubFileRefCmd {
1176
    /// Color code of the part.
1177
    pub color: u32,
1178
    /// Position.
1179
    pub pos: Vec3,
1180
    /// First row of rotation+scaling matrix part.
1181
    pub row0: Vec3,
1182
    /// Second row of rotation+scaling matrix part.
1183
    pub row1: Vec3,
1184
    /// Third row of rotation+scaling matrix part.
1185
    pub row2: Vec3,
1186
    /// Referenced sub-file.
1187
    pub file: SubFileRef,
1188
}
1189

1190
/// [Line Type 2](https://www.ldraw.org/article/218.html#lt2) LDraw command:
1191
/// Draw a segment between 2 vertices.
1192
#[derive(Debug, PartialEq)]
1193
pub struct LineCmd {
1194
    /// Color code of the primitive.
1195
    pub color: u32,
1196
    /// Vertices of the segment.
1197
    pub vertices: [Vec3; 2],
1198
}
1199

1200
/// [Line Type 3](https://www.ldraw.org/article/218.html#lt3) LDraw command:
1201
/// Draw a triangle between 3 vertices.
1202
#[derive(Debug, PartialEq)]
1203
pub struct TriangleCmd {
1204
    /// Color code of the primitive.
1205
    pub color: u32,
1206
    /// Vertices of the triangle.
1207
    pub vertices: [Vec3; 3],
1208
}
1209

1210
/// [Line Type 4](https://www.ldraw.org/article/218.html#lt4) LDraw command:
1211
/// Draw a quad between 4 vertices.
1212
#[derive(Debug, PartialEq)]
1213
pub struct QuadCmd {
1214
    /// Color code of the primitive.
1215
    pub color: u32,
1216
    /// Vertices of the quad. In theory they are guaranteed to be coplanar according to the LDraw
1217
    /// specification, although no attempt is made to validate this property.
1218
    pub vertices: [Vec3; 4],
1219
}
1220

1221
/// [Line Type 5](https://www.ldraw.org/article/218.html#lt5) LDraw command:
1222
/// Draw an optional segment between two vertices, aided by 2 control points.
1223
#[derive(Debug, PartialEq)]
1224
pub struct OptLineCmd {
1225
    /// Color code of the primitive.
1226
    pub color: u32,
1227
    /// Vertices of the segment.
1228
    pub vertices: [Vec3; 2],
1229
    /// Control points of the segment.
1230
    pub control_points: [Vec3; 2],
1231
}
1232

1233
/// Types of commands contained in a LDraw file.
1234
#[derive(Debug, PartialEq)]
1235
pub enum Command {
1236
    /// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
1237
    /// [!CATEGORY language extension](https://www.ldraw.org/article/340.html#category).
1238
    Category(CategoryCmd),
1239
    /// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
1240
    /// [!KEYWORDS language extension](https://www.ldraw.org/article/340.html#keywords).
1241
    Keywords(KeywordsCmd),
1242
    /// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
1243
    /// [!COLOUR language extension](https://www.ldraw.org/article/299.html).
1244
    Colour(ColourCmd),
1245
    /// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) comment.
1246
    /// Note: any line type 0 not otherwise parsed as a known meta-command is parsed as a generic comment.
1247
    Comment(CommentCmd),
1248
    /// [Line Type 1](https://www.ldraw.org/article/218.html#lt1) sub-file reference.
1249
    SubFileRef(SubFileRefCmd),
1250
    /// [Line Type 2](https://www.ldraw.org/article/218.html#lt2) segment.
1251
    Line(LineCmd),
1252
    /// [Line Type 3](https://www.ldraw.org/article/218.html#lt3) triangle.
1253
    Triangle(TriangleCmd),
1254
    /// [Line Type 4](https://www.ldraw.org/article/218.html#lt4) quadrilateral.
1255
    Quad(QuadCmd),
1256
    /// [Line Type 5](https://www.ldraw.org/article/218.html#lt5) optional line.
1257
    OptLine(OptLineCmd),
1258
}
1259

1260
/// Resolver trait for sub-file references ([Line Type 1](https://www.ldraw.org/article/218.html#lt1) LDraw command).
1261
///
1262
/// An implementation of this trait must be passed to [`parse()`] to allow resolving sub-file references recursively,
1263
/// and parsing all dependent sub-files of the top-level file provided.
1264
///
1265
/// When loading parts and primitives from the official LDraw catalog, implementations are free to decide how to retrieve
1266
/// the file content, but must ensure that all canonical paths are in scope, as sub-file references can be relative to
1267
/// any of those:
1268
/// - `/p/`       - Parts primitives
1269
/// - `/p/48/`    - High-resolution primitives
1270
/// - `/parts/`   - Main catalog of parts
1271
/// - `/parts/s/` - Catalog of sub-parts commonly used
1272
pub trait FileRefResolver {
1273
    /// Resolve the given file reference `filename`, given as it appears in a sub-file reference, and return
1274
    /// the content of the file as a UTF-8 encoded buffer of bytes, without BOM. Line ending can be indifferently
1275
    /// Unix style `\n` or Windows style `\r\n`.
1276
    ///
1277
    /// See [`parse()`] for usage.
1278
    fn resolve(&self, filename: &str) -> Result<Vec<u8>, ResolveError>;
1279
}
1280

1281
#[cfg(test)]
1282
mod tests {
1283
    use nom::error::ErrorKind;
1284

1285
    use super::*;
1286

1287
    #[test]
1288
    fn test_color_id() {
3✔
1289
        assert_eq!(color_id(b""), Err(nom_error(&b""[..], ErrorKind::Digit)));
1✔
1290
        assert_eq!(color_id(b"1"), Ok((&b""[..], 1)));
1✔
1291
        assert_eq!(color_id(b"16 "), Ok((&b" "[..], 16)));
1✔
1292
    }
1293

1294
    #[test]
1295
    fn test_from_hex() {
3✔
1296
        assert_eq!(from_hex(b"0"), Ok(0));
1✔
1297
        assert_eq!(from_hex(b"1"), Ok(1));
1✔
1298
        assert_eq!(from_hex(b"a"), Ok(10));
1✔
1299
        assert_eq!(from_hex(b"F"), Ok(15));
1✔
1300
        assert_eq!(from_hex(b"G"), Err(ErrorKind::AlphaNumeric));
1✔
1301
        assert_eq!(from_hex(b"10"), Ok(16));
1✔
1302
        assert_eq!(from_hex(b"FF"), Ok(255));
1✔
1303
        assert_eq!(from_hex(b"1G"), Err(ErrorKind::AlphaNumeric));
1✔
1304
        assert_eq!(from_hex(b"100"), Err(ErrorKind::AlphaNumeric));
1✔
1305
        assert_eq!(from_hex(b"\xFF"), Err(ErrorKind::AlphaNumeric));
1✔
1306
    }
1307

1308
    #[test]
1309
    fn test_hex_color() {
3✔
1310
        assert_eq!(hex_color(b""), Err(nom_error(&b""[..], ErrorKind::Tag)));
1✔
1311
        assert_eq!(
1✔
1312
            hex_color(b"#"),
1✔
1313
            Err(nom_error(&b""[..], ErrorKind::TakeWhileMN))
1✔
1314
        );
1315
        assert_eq!(
1✔
1316
            hex_color(b"#1"),
1✔
1317
            Err(nom_error(&b"1"[..], ErrorKind::TakeWhileMN))
1✔
1318
        );
1319
        assert_eq!(
1✔
1320
            hex_color(b"#12345Z"),
1✔
1321
            Err(nom_error(&b"5Z"[..], ErrorKind::TakeWhileMN))
1✔
1322
        );
1323
        assert_eq!(
1✔
1324
            hex_color(b"#123456"),
1✔
1325
            Ok((&b""[..], Color::new(0x12, 0x34, 0x56)))
1✔
1326
        );
1327
        assert_eq!(
1✔
1328
            hex_color(b"#ABCDEF"),
1✔
1329
            Ok((&b""[..], Color::new(0xAB, 0xCD, 0xEF)))
1✔
1330
        );
1331
        assert_eq!(
1✔
1332
            hex_color(b"#8E5cAf"),
1✔
1333
            Ok((&b""[..], Color::new(0x8E, 0x5C, 0xAF)))
1✔
1334
        );
1335
        assert_eq!(
1✔
1336
            hex_color(b"#123456e"),
1✔
1337
            Ok((&b"e"[..], Color::new(0x12, 0x34, 0x56)))
1✔
1338
        );
1339
    }
1340

1341
    #[test]
1342
    fn test_colour_alpha() {
3✔
1343
        assert_eq!(colour_alpha(b""), Ok((&b""[..], None)));
1✔
1344
        assert_eq!(colour_alpha(b" ALPHA 0"), Ok((&b""[..], Some(0))));
1✔
1345
        assert_eq!(colour_alpha(b" ALPHA 1"), Ok((&b""[..], Some(1))));
1✔
1346
        assert_eq!(colour_alpha(b" ALPHA 128"), Ok((&b""[..], Some(128))));
1✔
1347
        assert_eq!(colour_alpha(b" ALPHA 255"), Ok((&b""[..], Some(255))));
1✔
1348
        assert_eq!(colour_alpha(b" ALPHA 34 "), Ok((&b" "[..], Some(34))));
1✔
1349
        // TODO - Should fail on partial match, but succeeds because of opt!()
1350
        assert_eq!(colour_alpha(b" ALPHA"), Ok((&b" ALPHA"[..], None))); // Err(Err::Incomplete(Needed::Size(1)))
1✔
1351
        assert_eq!(colour_alpha(b" ALPHA 256"), Ok((&b" ALPHA 256"[..], None)));
1✔
1352
        // Err(Err::Incomplete(Needed::Size(1)))
1353
    }
1354

1355
    #[test]
1356
    fn test_colour_luminance() {
3✔
1357
        assert_eq!(colour_luminance(b""), Ok((&b""[..], None)));
1✔
1358
        assert_eq!(colour_luminance(b" LUMINANCE 0"), Ok((&b""[..], Some(0))));
1✔
1359
        assert_eq!(colour_luminance(b" LUMINANCE 1"), Ok((&b""[..], Some(1))));
1✔
1360
        assert_eq!(
1✔
1361
            colour_luminance(b" LUMINANCE 128"),
1✔
1362
            Ok((&b""[..], Some(128)))
1✔
1363
        );
1364
        assert_eq!(
1✔
1365
            colour_luminance(b" LUMINANCE 255"),
1✔
1366
            Ok((&b""[..], Some(255)))
1✔
1367
        );
1368
        assert_eq!(
1✔
1369
            colour_luminance(b" LUMINANCE 34 "),
1✔
1370
            Ok((&b" "[..], Some(34)))
1✔
1371
        );
1372
        // TODO - Should fail on partial match, but succeeds because of opt!()
1373
        assert_eq!(
1✔
1374
            colour_luminance(b" LUMINANCE"),
1✔
1375
            Ok((&b" LUMINANCE"[..], None))
1✔
1376
        ); // Err(Err::Incomplete(Needed::Size(1)))
1377
        assert_eq!(
1✔
1378
            colour_luminance(b" LUMINANCE 256"),
1✔
1379
            Ok((&b" LUMINANCE 256"[..], None))
1✔
1380
        ); // Err(Err::Incomplete(Needed::Size(1)))
1381
    }
1382

1383
    #[test]
1384
    fn test_material_grain_size() {
3✔
1385
        assert_eq!(
1✔
1386
            material_grain_size(b""),
1✔
1387
            Err(nom_error(&b""[..], ErrorKind::Tag))
1✔
1388
        );
1389
        assert_eq!(
1✔
1390
            material_grain_size(b"SIZE"),
1✔
1391
            Err(nom_error(&b"SIZE"[..], ErrorKind::Tag))
1✔
1392
        );
1393
        assert_eq!(
1✔
1394
            material_grain_size(b"SIZE 1"),
1✔
1395
            Ok((&b""[..], GrainSize::Size(1.0)))
1✔
1396
        );
1397
        assert_eq!(
1✔
1398
            material_grain_size(b"SIZE 0.02"),
1✔
1399
            Ok((&b""[..], GrainSize::Size(0.02)))
1✔
1400
        );
1401
        assert_eq!(
1✔
1402
            material_grain_size(b"MINSIZE"),
1✔
1403
            Err(nom_error(&b""[..], ErrorKind::Space))
1✔
1404
        );
1405
        assert_eq!(
1✔
1406
            material_grain_size(b"MINSIZE 0.02"),
1✔
1407
            Err(nom_error(&b""[..], ErrorKind::Space))
1✔
1408
        );
1409
        assert_eq!(
1✔
1410
            material_grain_size(b"MINSIZE 0.02 MAXSIZE 0.04"),
1✔
1411
            Ok((&b""[..], GrainSize::MinMaxSize((0.02, 0.04))))
1✔
1412
        );
1413
    }
1414

1415
    #[test]
1416
    fn test_glitter_material() {
3✔
1417
        assert_eq!(
2✔
1418
            glitter_material(b""),
1✔
1419
            Err(nom_error(&b""[..], ErrorKind::Tag))
2✔
1420
        );
1421
        assert_eq!(
1✔
1422
            glitter_material(b"GLITTER"),
1✔
1423
            Err(nom_error(&b""[..], ErrorKind::Space))
2✔
1424
        );
1425
        assert_eq!(
1✔
1426
            glitter_material(b"GLITTER VALUE #123456 FRACTION 1.0 VFRACTION 0.3 SIZE 1"),
1✔
1427
            Ok((
1✔
1428
                &b""[..],
1✔
1429
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
1430
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1431
                    alpha: None,
1✔
1432
                    luminance: None,
1✔
1433
                    surface_fraction: 1.0,
1434
                    volume_fraction: 0.3,
1435
                    size: GrainSize::Size(1.0)
1✔
1436
                }))
1437
            ))
1438
        );
1439
        assert_eq!(
1✔
1440
            glitter_material(b"GLITTER VALUE #123456 ALPHA 128 FRACTION 1.0 VFRACTION 0.3 SIZE 1"),
1✔
1441
            Ok((
1✔
1442
                &b""[..],
1✔
1443
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
1444
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1445
                    alpha: Some(128),
1✔
1446
                    luminance: None,
1✔
1447
                    surface_fraction: 1.0,
1448
                    volume_fraction: 0.3,
1449
                    size: GrainSize::Size(1.0)
1✔
1450
                }))
1451
            ))
1452
        );
1453
        assert_eq!(
1✔
1454
            glitter_material(
1✔
1455
                b"GLITTER VALUE #123456 LUMINANCE 32 FRACTION 1.0 VFRACTION 0.3 SIZE 1"
1456
            ),
1457
            Ok((
1✔
1458
                &b""[..],
1✔
1459
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
1460
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1461
                    alpha: None,
1✔
1462
                    luminance: Some(32),
1✔
1463
                    surface_fraction: 1.0,
1464
                    volume_fraction: 0.3,
1465
                    size: GrainSize::Size(1.0)
1✔
1466
                }))
1467
            ))
1468
        );
1469
        assert_eq!(
1✔
1470
            glitter_material(
1✔
1471
                b"GLITTER VALUE #123456 FRACTION 1.0 VFRACTION 0.3 MINSIZE 0.02 MAXSIZE 0.04"
1472
            ),
1473
            Ok((
1✔
1474
                &b""[..],
1✔
1475
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
1476
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1477
                    alpha: None,
1✔
1478
                    luminance: None,
1✔
1479
                    surface_fraction: 1.0,
1480
                    volume_fraction: 0.3,
1481
                    size: GrainSize::MinMaxSize((0.02, 0.04))
1✔
1482
                }))
1483
            ))
1484
        );
1485
    }
1486

1487
    #[test]
1488
    fn test_speckle_material() {
3✔
1489
        assert_eq!(
2✔
1490
            speckle_material(b""),
1✔
1491
            Err(nom_error(&b""[..], ErrorKind::Tag))
2✔
1492
        );
1493
        assert_eq!(
1✔
1494
            speckle_material(b"SPECKLE"),
1✔
1495
            Err(nom_error(&b""[..], ErrorKind::Space))
2✔
1496
        );
1497
        assert_eq!(
1✔
1498
            speckle_material(b"SPECKLE VALUE #123456 FRACTION 1.0 SIZE 1"),
1✔
1499
            Ok((
1✔
1500
                &b""[..],
1✔
1501
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
1502
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1503
                    alpha: None,
1✔
1504
                    luminance: None,
1✔
1505
                    surface_fraction: 1.0,
1506
                    size: GrainSize::Size(1.0)
1✔
1507
                }))
1508
            ))
1509
        );
1510
        assert_eq!(
1✔
1511
            speckle_material(b"SPECKLE VALUE #123456 ALPHA 128 FRACTION 1.0 SIZE 1"),
1✔
1512
            Ok((
1✔
1513
                &b""[..],
1✔
1514
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
1515
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1516
                    alpha: Some(128),
1✔
1517
                    luminance: None,
1✔
1518
                    surface_fraction: 1.0,
1519
                    size: GrainSize::Size(1.0)
1✔
1520
                }))
1521
            ))
1522
        );
1523
        assert_eq!(
1✔
1524
            speckle_material(b"SPECKLE VALUE #123456 LUMINANCE 32 FRACTION 1.0 SIZE 1"),
1✔
1525
            Ok((
1✔
1526
                &b""[..],
1✔
1527
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
1528
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1529
                    alpha: None,
1✔
1530
                    luminance: Some(32),
1✔
1531
                    surface_fraction: 1.0,
1532
                    size: GrainSize::Size(1.0)
1✔
1533
                }))
1534
            ))
1535
        );
1536
        assert_eq!(
1✔
1537
            speckle_material(b"SPECKLE VALUE #123456 FRACTION 1.0 MINSIZE 0.02 MAXSIZE 0.04"),
1✔
1538
            Ok((
1✔
1539
                &b""[..],
1✔
1540
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
1541
                    value: Color::new(0x12, 0x34, 0x56),
1✔
1542
                    alpha: None,
1✔
1543
                    luminance: None,
1✔
1544
                    surface_fraction: 1.0,
1545
                    size: GrainSize::MinMaxSize((0.02, 0.04))
1✔
1546
                }))
1547
            ))
1548
        );
1549
    }
1550

1551
    #[test]
1552
    fn test_color_finish() {
3✔
1553
        assert_eq!(color_finish(b""), Ok((&b""[..], None)));
2✔
1554
        assert_eq!(color_finish(b"CHROME"), Ok((&b"CHROME"[..], None)));
1✔
1555
        assert_eq!(
1✔
1556
            color_finish(b" CHROME"),
1✔
1557
            Ok((&b""[..], Some(ColorFinish::Chrome)))
2✔
1558
        );
1559
        assert_eq!(
1✔
1560
            color_finish(b" PEARLESCENT"),
1✔
1561
            Ok((&b""[..], Some(ColorFinish::Pearlescent)))
2✔
1562
        );
1563
        assert_eq!(
1✔
1564
            color_finish(b" RUBBER"),
1✔
1565
            Ok((&b""[..], Some(ColorFinish::Rubber)))
2✔
1566
        );
1567
        assert_eq!(
1✔
1568
            color_finish(b" MATTE_METALLIC"),
1✔
1569
            Ok((&b""[..], Some(ColorFinish::MatteMetallic)))
2✔
1570
        );
1571
        assert_eq!(
1✔
1572
            color_finish(b" METAL"),
1✔
1573
            Ok((&b""[..], Some(ColorFinish::Metal)))
2✔
1574
        );
1575
        // TODO - Should probably ensure <SPACE> or <EOF> after keyword, not *anything*
1576
        assert_eq!(
1✔
1577
            color_finish(b" CHROMEas"),
1✔
1578
            Ok((&b"as"[..], Some(ColorFinish::Chrome)))
2✔
1579
        );
1580
        assert_eq!(
1✔
1581
            color_finish(b" MATERIAL custom values"),
1✔
1582
            Ok((
1✔
1583
                &b""[..],
1✔
1584
                Some(ColorFinish::Material(MaterialFinish::Other(
1✔
1585
                    "custom values".to_string()
1✔
1586
                )))
1587
            ))
1588
        );
1589
    }
1590

1591
    #[test]
1592
    fn test_digit1_as_u8() {
3✔
1593
        assert_eq!(
1✔
1594
            digit1_as_u8(b""),
1✔
1595
            Err(nom_error(&b""[..], ErrorKind::Digit))
1✔
1596
        );
1597
        assert_eq!(digit1_as_u8(b"0"), Ok((&b""[..], 0u8)));
1✔
1598
        assert_eq!(digit1_as_u8(b"1"), Ok((&b""[..], 1u8)));
1✔
1599
        assert_eq!(digit1_as_u8(b"255"), Ok((&b""[..], 255u8)));
1✔
1600
        assert_eq!(
1✔
1601
            digit1_as_u8(b"256"),
1✔
1602
            Err(nom_error(&b"256"[..], ErrorKind::MapRes))
1✔
1603
        );
1604
        assert_eq!(digit1_as_u8(b"32 "), Ok((&b" "[..], 32u8)));
1✔
1605
    }
1606

1607
    #[test]
1608
    fn test_meta_colour() {
3✔
1609
        assert_eq!(meta_colour(b""), Err(nom_error(&b""[..], ErrorKind::Tag)));
2✔
1610
        assert_eq!(
1✔
1611
            meta_colour(b"!COLOUR test_col CODE 20 VALUE #123456"),
1✔
1612
            Err(nom_error(&b""[..], ErrorKind::Space))
2✔
1613
        );
1614
        assert_eq!(
1✔
1615
            meta_colour(b"!COLOUR test_col CODE 20 VALUE #123456 EDGE #abcdef"),
1✔
1616
            Ok((
1✔
1617
                &b""[..],
1✔
1618
                Command::Colour(ColourCmd {
1✔
1619
                    name: "test_col".to_string(),
1✔
1620
                    code: 20,
1621
                    value: Color::new(0x12, 0x34, 0x56),
2✔
1622
                    edge: Color::new(0xAB, 0xCD, 0xEF),
1✔
1623
                    alpha: None,
1✔
1624
                    luminance: None,
1✔
1625
                    finish: None
1✔
1626
                })
1627
            ))
1628
        );
1629
        assert_eq!(
1✔
1630
            meta_colour(b"!COLOUR test_col CODE 20 VALUE #123456 EDGE #abcdef ALPHA 128"),
1✔
1631
            Ok((
1✔
1632
                &b""[..],
1✔
1633
                Command::Colour(ColourCmd {
1✔
1634
                    name: "test_col".to_string(),
1✔
1635
                    code: 20,
1636
                    value: Color::new(0x12, 0x34, 0x56),
2✔
1637
                    edge: Color::new(0xAB, 0xCD, 0xEF),
1✔
1638
                    alpha: Some(128),
1✔
1639
                    luminance: None,
1✔
1640
                    finish: None
1✔
1641
                })
1642
            ))
1643
        );
1644
        assert_eq!(
1✔
1645
            meta_colour(b"!COLOUR test_col CODE 20 VALUE #123456 EDGE #abcdef LUMINANCE 32"),
1✔
1646
            Ok((
1✔
1647
                &b""[..],
1✔
1648
                Command::Colour(ColourCmd {
1✔
1649
                    name: "test_col".to_string(),
1✔
1650
                    code: 20,
1651
                    value: Color::new(0x12, 0x34, 0x56),
2✔
1652
                    edge: Color::new(0xAB, 0xCD, 0xEF),
1✔
1653
                    alpha: None,
1✔
1654
                    luminance: Some(32),
1✔
1655
                    finish: None
1✔
1656
                })
1657
            ))
1658
        );
1659
        assert_eq!(
1✔
1660
            meta_colour(
1✔
1661
                b"!COLOUR test_col CODE 20 VALUE #123456 EDGE #abcdef ALPHA 64 LUMINANCE 32"
1662
            ),
1663
            Ok((
1✔
1664
                &b""[..],
1✔
1665
                Command::Colour(ColourCmd {
1✔
1666
                    name: "test_col".to_string(),
1✔
1667
                    code: 20,
1668
                    value: Color::new(0x12, 0x34, 0x56),
2✔
1669
                    edge: Color::new(0xAB, 0xCD, 0xEF),
1✔
1670
                    alpha: Some(64),
1✔
1671
                    luminance: Some(32),
1✔
1672
                    finish: None
1✔
1673
                })
1674
            ))
1675
        );
1676
        assert_eq!(
1✔
1677
            meta_colour(b"!COLOUR test_col CODE 20 VALUE #123456 EDGE #abcdef CHROME"),
1✔
1678
            Ok((
1✔
1679
                &b""[..],
1✔
1680
                Command::Colour(ColourCmd {
1✔
1681
                    name: "test_col".to_string(),
1✔
1682
                    code: 20,
1683
                    value: Color::new(0x12, 0x34, 0x56),
2✔
1684
                    edge: Color::new(0xAB, 0xCD, 0xEF),
1✔
1685
                    alpha: None,
1✔
1686
                    luminance: None,
1✔
1687
                    finish: Some(ColorFinish::Chrome)
1✔
1688
                })
1689
            ))
1690
        );
1691
        assert_eq!(
1✔
1692
            meta_colour(b"!COLOUR test_col CODE 20 VALUE #123456 EDGE #abcdef ALPHA 128 RUBBER"),
1✔
1693
            Ok((
1✔
1694
                &b""[..],
1✔
1695
                Command::Colour(ColourCmd {
1✔
1696
                    name: "test_col".to_string(),
1✔
1697
                    code: 20,
1698
                    value: Color::new(0x12, 0x34, 0x56),
2✔
1699
                    edge: Color::new(0xAB, 0xCD, 0xEF),
1✔
1700
                    alpha: Some(128),
1✔
1701
                    luminance: None,
1✔
1702
                    finish: Some(ColorFinish::Rubber)
1✔
1703
                })
1704
            ))
1705
        );
1706
    }
1707

1708
    #[test]
1709
    fn test_vec3() {
3✔
1710
        assert_eq!(
1✔
1711
            read_vec3(b"0 0 0"),
1✔
1712
            Ok((&b""[..], Vec3::new(0.0, 0.0, 0.0)))
1✔
1713
        );
1714
        assert_eq!(
1✔
1715
            read_vec3(b"0 0 0 1"),
1✔
1716
            Ok((&b" 1"[..], Vec3::new(0.0, 0.0, 0.0)))
1✔
1717
        );
1718
        assert_eq!(
1✔
1719
            read_vec3(b"2 5 -7"),
1✔
1720
            Ok((&b""[..], Vec3::new(2.0, 5.0, -7.0)))
1✔
1721
        );
1722
        assert_eq!(
1✔
1723
            read_vec3(b"2.3 5 -7.4"),
1✔
1724
            Ok((&b""[..], Vec3::new(2.3, 5.0, -7.4)))
1✔
1725
        );
1726
    }
1727

1728
    #[test]
1729
    fn test_read_cmd_id_str() {
3✔
1730
        assert_eq!(read_cmd_id_str(b"0"), Ok((&b""[..], &b"0"[..])));
1✔
1731
        assert_eq!(read_cmd_id_str(b"0 "), Ok((&b""[..], &b"0"[..])));
1✔
1732
        assert_eq!(read_cmd_id_str(b"0   "), Ok((&b""[..], &b"0"[..])));
1✔
1733
        assert_eq!(read_cmd_id_str(b"0   e"), Ok((&b"e"[..], &b"0"[..])));
1✔
1734
        assert_eq!(
1✔
1735
            read_cmd_id_str(b"4547    ssd"),
1✔
1736
            Ok((&b"ssd"[..], &b"4547"[..]))
1✔
1737
        );
1738
    }
1739

1740
    #[test]
1741
    fn test_end_of_line() {
3✔
1742
        assert_eq!(end_of_line(b""), Ok((&b""[..], &b""[..])));
1✔
1743
        assert_eq!(end_of_line(b"\n"), Ok((&b""[..], &b"\n"[..])));
1✔
1744
        assert_eq!(end_of_line(b"\r\n"), Ok((&b""[..], &b"\r\n"[..])));
1✔
1745
    }
1746

1747
    #[test]
1748
    fn test_take_not_cr_or_lf() {
3✔
1749
        assert_eq!(take_not_cr_or_lf(b""), Ok((&b""[..], &b""[..])));
1✔
1750
        assert_eq!(take_not_cr_or_lf(b"\n"), Ok((&b"\n"[..], &b""[..])));
1✔
1751
        assert_eq!(take_not_cr_or_lf(b"\r\n"), Ok((&b"\r\n"[..], &b""[..])));
1✔
1752
        assert_eq!(take_not_cr_or_lf(b"\n\n\n"), Ok((&b"\n\n\n"[..], &b""[..])));
1✔
1753
        assert_eq!(
1✔
1754
            take_not_cr_or_lf(b"\r\n\r\n\r\n"),
1✔
1755
            Ok((&b"\r\n\r\n\r\n"[..], &b""[..]))
1✔
1756
        );
1757
        assert_eq!(take_not_cr_or_lf(b" a \n"), Ok((&b"\n"[..], &b" a "[..])));
1✔
1758
        assert_eq!(take_not_cr_or_lf(b"test"), Ok((&b""[..], &b"test"[..])));
1✔
1759
    }
1760

1761
    #[test]
1762
    fn test_single_comma() {
3✔
1763
        assert_eq!(single_comma(b""), Err(nom_error(&b""[..], ErrorKind::Tag)));
1✔
1764
        assert_eq!(single_comma(b","), Ok((&b""[..], &b","[..])));
1✔
1765
        assert_eq!(single_comma(b",s"), Ok((&b"s"[..], &b","[..])));
1✔
1766
        assert_eq!(
1✔
1767
            single_comma(b"w,s"),
1✔
1768
            Err(nom_error(&b"w,s"[..], ErrorKind::Tag))
1✔
1769
        );
1770
    }
1771

1772
    #[test]
1773
    fn test_keywords_list() {
3✔
1774
        assert_eq!(
2✔
1775
            keywords_list(b""),
1✔
1776
            Err(nom_error(&b""[..], ErrorKind::TakeWhile1))
2✔
1777
        );
1778
        assert_eq!(keywords_list(b"a"), Ok((&b""[..], vec!["a"])));
1✔
1779
        assert_eq!(keywords_list(b"a,b,c"), Ok((&b""[..], vec!["a", "b", "c"])));
1✔
1780
    }
1781

1782
    #[test]
1783
    fn test_filename_char() {
3✔
1784
        assert_eq!(
1✔
1785
            filename_char(b""),
1✔
1786
            Err(nom_error(&b""[..], ErrorKind::AlphaNumeric))
1✔
1787
        );
1788
        assert_eq!(filename_char(b"a"), Ok((&b""[..], &b"a"[..])));
1✔
1789
        assert_eq!(filename_char(b"a-"), Ok((&b""[..], &b"a-"[..])));
1✔
1790
        assert_eq!(
1✔
1791
            filename_char(b"a/sad.bak\\ww.dat"),
1✔
1792
            Ok((&b""[..], &b"a/sad.bak\\ww.dat"[..]))
1✔
1793
        );
1794
    }
1795

1796
    #[test]
1797
    fn test_filename() {
3✔
1798
        assert_eq!(filename(b"asd\\kw/l.ldr"), Ok((&b""[..], "asd\\kw/l.ldr")));
1✔
1799
        assert_eq!(filename(b"asdkwl.ldr"), Ok((&b""[..], "asdkwl.ldr")));
1✔
1800
        assert_eq!(
1✔
1801
            filename(b"asd\\kw/l.ldr\n"),
1✔
1802
            Ok((&b"\n"[..], "asd\\kw/l.ldr"))
1✔
1803
        );
1804
        assert_eq!(filename(b"asdkwl.ldr\n"), Ok((&b"\n"[..], "asdkwl.ldr")));
1✔
1805
        assert_eq!(
1✔
1806
            filename(b"asd\\kw/l.ldr\r\n"),
1✔
1807
            Ok((&b"\r\n"[..], "asd\\kw/l.ldr"))
1✔
1808
        );
1809
        assert_eq!(
1✔
1810
            filename(b"asdkwl.ldr\r\n"),
1✔
1811
            Ok((&b"\r\n"[..], "asdkwl.ldr"))
1✔
1812
        );
1813
    }
1814

1815
    #[test]
1816
    fn test_category_cmd() {
3✔
1817
        let res = Command::Category(CategoryCmd {
2✔
1818
            category: "Figure Accessory".to_string(),
1✔
1819
        });
1820
        assert_eq!(category(b"!CATEGORY Figure Accessory"), Ok((&b""[..], res)));
3✔
1821
    }
1822

1823
    #[test]
1824
    fn test_keywords_cmd() {
3✔
1825
        let res = Command::Keywords(KeywordsCmd {
2✔
1826
            keywords: vec![
1✔
1827
                "western".to_string(),
1✔
1828
                "wild west".to_string(),
1✔
1829
                "spaghetti western".to_string(),
1✔
1830
                "horse opera".to_string(),
1✔
1831
                "cowboy".to_string(),
1✔
1832
            ],
1833
        });
1834
        assert_eq!(
1✔
1835
            keywords(b"!KEYWORDS western, wild west, spaghetti western, horse opera, cowboy"),
1✔
1836
            Ok((&b""[..], res))
2✔
1837
        );
1838
    }
1839

1840
    #[test]
1841
    fn test_comment_cmd() {
3✔
1842
        let comment = b"test of comment, with \"weird\" characters";
1✔
1843
        let res = Command::Comment(CommentCmd::new(std::str::from_utf8(comment).unwrap()));
1✔
1844
        assert_eq!(meta_cmd(comment), Ok((&b""[..], res)));
3✔
1845
        // Match empty comment too (e.g. "0" line without anything else, or "0   " with only spaces)
1846
        assert_eq!(
1✔
1847
            meta_cmd(b""),
1✔
1848
            Ok((&b""[..], Command::Comment(CommentCmd::new(""))))
2✔
1849
        );
1850
    }
1851

1852
    #[test]
1853
    fn test_file_ref_cmd() {
3✔
1854
        let res = Command::SubFileRef(SubFileRefCmd {
2✔
1855
            color: 16,
1856
            pos: Vec3::new(0.0, 0.0, 0.0),
1✔
1857
            row0: Vec3::new(1.0, 0.0, 0.0),
1✔
1858
            row1: Vec3::new(0.0, 1.0, 0.0),
1✔
1859
            row2: Vec3::new(0.0, 0.0, 1.0),
1✔
1860
            file: SubFileRef::UnresolvedRef("aaaaaaddd".to_string()),
1✔
1861
        });
1862
        assert_eq!(
2✔
1863
            file_ref_cmd(b"16 0 0 0 1 0 0 0 1 0 0 0 1 aaaaaaddd"),
1✔
1864
            Ok((&b""[..], res))
2✔
1865
        );
1866
    }
1867

1868
    #[test]
1869
    fn test_space0() {
3✔
1870
        assert_eq!(space0(b""), Ok((&b""[..], &b""[..])));
1✔
1871
        assert_eq!(space0(b" "), Ok((&b""[..], &b" "[..])));
1✔
1872
        assert_eq!(space0(b"   "), Ok((&b""[..], &b"   "[..])));
1✔
1873
        assert_eq!(space0(b"  a"), Ok((&b"a"[..], &b"  "[..])));
1✔
1874
        assert_eq!(space0(b"a  "), Ok((&b"a  "[..], &b""[..])));
1✔
1875
    }
1876

1877
    #[test]
1878
    fn test_space_or_eol0() {
3✔
1879
        assert_eq!(space_or_eol0(b""), Ok((&b""[..], &b""[..])));
1✔
1880
        assert_eq!(space_or_eol0(b" "), Ok((&b""[..], &b" "[..])));
1✔
1881
        assert_eq!(space_or_eol0(b"   "), Ok((&b""[..], &b"   "[..])));
1✔
1882
        assert_eq!(space_or_eol0(b"  a"), Ok((&b"a"[..], &b"  "[..])));
1✔
1883
        assert_eq!(space_or_eol0(b"a  "), Ok((&b"a  "[..], &b""[..])));
1✔
1884
        assert_eq!(space_or_eol0(b"\n"), Ok((&b""[..], &b"\n"[..])));
1✔
1885
        assert_eq!(space_or_eol0(b"\n\n\n"), Ok((&b""[..], &b"\n\n\n"[..])));
1✔
1886
        assert_eq!(space_or_eol0(b"\n\r\n"), Ok((&b""[..], &b"\n\r\n"[..])));
1✔
1887
        // Unfortunately <LF> alone is not handled well, but we assume this needs to be ignored too
1888
        assert_eq!(
1✔
1889
            space_or_eol0(b"\n\r\r\r\n"),
1✔
1890
            Ok((&b""[..], &b"\n\r\r\r\n"[..]))
1✔
1891
        );
1892
        assert_eq!(space_or_eol0(b"  \n"), Ok((&b""[..], &b"  \n"[..])));
1✔
1893
        assert_eq!(space_or_eol0(b"  \n   "), Ok((&b""[..], &b"  \n   "[..])));
1✔
1894
        assert_eq!(
1✔
1895
            space_or_eol0(b"  \n   \r\n"),
1✔
1896
            Ok((&b""[..], &b"  \n   \r\n"[..]))
1✔
1897
        );
1898
        assert_eq!(
1✔
1899
            space_or_eol0(b"  \n   \r\n "),
1✔
1900
            Ok((&b""[..], &b"  \n   \r\n "[..]))
1✔
1901
        );
1902
        assert_eq!(space_or_eol0(b"  \nsa"), Ok((&b"sa"[..], &b"  \n"[..])));
1✔
1903
        assert_eq!(
1✔
1904
            space_or_eol0(b"  \n  \r\nsa"),
1✔
1905
            Ok((&b"sa"[..], &b"  \n  \r\n"[..]))
1✔
1906
        );
1907
    }
1908

1909
    #[test]
1910
    fn test_empty_line() {
3✔
1911
        assert_eq!(empty_line(b""), Ok((&b""[..], &b""[..])));
1✔
1912
        assert_eq!(empty_line(b" "), Ok((&b""[..], &b" "[..])));
1✔
1913
        assert_eq!(empty_line(b"   "), Ok((&b""[..], &b"   "[..])));
1✔
1914
        assert_eq!(
1✔
1915
            empty_line(b"  a"),
1✔
1916
            Err(nom_error(&b"a"[..], ErrorKind::CrLf))
1✔
1917
        );
1918
        assert_eq!(
1✔
1919
            empty_line(b"a  "),
1✔
1920
            Err(nom_error(&b"a  "[..], ErrorKind::CrLf))
1✔
1921
        );
1922
    }
1923

1924
    #[test]
1925
    fn test_read_cmd() {
3✔
1926
        let res = Command::Comment(CommentCmd::new("this doesn't matter"));
1✔
1927
        assert_eq!(read_line(b"0 this doesn't matter"), Ok((&b""[..], res)));
3✔
1928
    }
1929

1930
    #[test]
1931
    fn test_read_line_cmd() {
3✔
1932
        let res = Command::Line(LineCmd {
2✔
1933
            color: 16,
1934
            vertices: [Vec3::new(1.0, 1.0, 0.0), Vec3::new(0.9239, 1.0, 0.3827)],
1✔
1935
        });
1936
        assert_eq!(
2✔
1937
            read_line(b"2 16 1 1 0 0.9239 1 0.3827"),
1✔
1938
            Ok((&b""[..], res))
2✔
1939
        );
1940
    }
1941

1942
    #[test]
1943
    fn test_read_tri_cmd() {
3✔
1944
        let res = Command::Triangle(TriangleCmd {
2✔
1945
            color: 16,
1946
            vertices: [
1✔
1947
                Vec3::new(1.0, 1.0, 0.0),
1✔
1948
                Vec3::new(0.9239, 1.0, 0.3827),
1✔
1949
                Vec3::new(0.9239, 0.0, 0.3827),
1✔
1950
            ],
1951
        });
1952
        assert_eq!(
2✔
1953
            read_line(b"3 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827"),
1✔
1954
            Ok((&b""[..], res))
2✔
1955
        );
1956
        let res = Command::Triangle(TriangleCmd {
1✔
1957
            color: 16,
1958
            vertices: [
1✔
1959
                Vec3::new(1.0, 1.0, 0.0),
1✔
1960
                Vec3::new(0.9239, 1.0, 0.3827),
1✔
1961
                Vec3::new(0.9239, 0.0, 0.3827),
1✔
1962
            ],
1963
        });
1964
        assert_eq!(
1✔
1965
            // Note: extra spaces at end
1966
            read_line(b"3 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827  "),
1✔
1967
            Ok((&b""[..], res))
2✔
1968
        );
1969
    }
1970

1971
    #[test]
1972
    fn test_read_quad_cmd() {
3✔
1973
        let res = Command::Quad(QuadCmd {
2✔
1974
            color: 16,
1975
            vertices: [
1✔
1976
                Vec3::new(1.0, 1.0, 0.0),
1✔
1977
                Vec3::new(0.9239, 1.0, 0.3827),
1✔
1978
                Vec3::new(0.9239, 0.0, 0.3827),
1✔
1979
                Vec3::new(1.0, 0.0, 0.0),
1✔
1980
            ],
1981
        });
1982
        assert_eq!(
2✔
1983
            read_line(b"4 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827 1 0 0"),
1✔
1984
            Ok((&b""[..], res))
2✔
1985
        );
1986
    }
1987

1988
    #[test]
1989
    fn test_read_opt_line_cmd() {
3✔
1990
        let res = Command::OptLine(OptLineCmd {
2✔
1991
            color: 16,
1992
            vertices: [Vec3::new(1.0, 1.0, 0.0), Vec3::new(0.9239, 1.0, 0.3827)],
1✔
1993
            control_points: [Vec3::new(0.9239, 0.0, 0.3827), Vec3::new(1.0, 0.0, 0.0)],
1✔
1994
        });
1995
        assert_eq!(
2✔
1996
            read_line(b"5 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827 1 0 0"),
1✔
1997
            Ok((&b""[..], res))
2✔
1998
        );
1999
    }
2000

2001
    #[test]
2002
    fn test_read_line_subfileref() {
3✔
2003
        let res = Command::SubFileRef(SubFileRefCmd {
2✔
2004
            color: 16,
2005
            pos: Vec3::new(0.0, 0.0, 0.0),
1✔
2006
            row0: Vec3::new(1.0, 0.0, 0.0),
1✔
2007
            row1: Vec3::new(0.0, 1.0, 0.0),
1✔
2008
            row2: Vec3::new(0.0, 0.0, 1.0),
1✔
2009
            file: SubFileRef::UnresolvedRef("aa/aaaaddd".to_string()),
1✔
2010
        });
2011
        assert_eq!(
2✔
2012
            read_line(b"1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd"),
1✔
2013
            Ok((&b""[..], res))
2✔
2014
        );
2015
    }
2016

2017
    #[test]
2018
    fn test_parse_raw() {
3✔
2019
        let cmd0 = Command::Comment(CommentCmd::new("this is a comment"));
1✔
2020
        let cmd1 = Command::Line(LineCmd {
1✔
2021
            color: 16,
2022
            vertices: [Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0)],
2✔
2023
        });
2024
        assert_eq!(
2✔
2025
            parse_raw(b"0 this is a comment\n2 16 0 0 0 1 1 1").unwrap(),
2✔
2026
            vec![cmd0, cmd1]
2✔
2027
        );
2028

2029
        let cmd0 = Command::Comment(CommentCmd::new("this doesn't matter"));
1✔
2030
        let cmd1 = Command::SubFileRef(SubFileRefCmd {
1✔
2031
            color: 16,
2032
            pos: Vec3::new(0.0, 0.0, 0.0),
1✔
2033
            row0: Vec3::new(1.0, 0.0, 0.0),
1✔
2034
            row1: Vec3::new(0.0, 1.0, 0.0),
1✔
2035
            row2: Vec3::new(0.0, 0.0, 1.0),
1✔
2036
            file: SubFileRef::UnresolvedRef("aa/aaaaddd".to_string()),
1✔
2037
        });
2038
        assert_eq!(
1✔
2039
            parse_raw(b"\n0 this doesn't matter\n\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd")
2✔
2040
                .unwrap(),
2041
            vec![cmd0, cmd1]
2✔
2042
        );
2043

2044
        let cmd0 = Command::Comment(CommentCmd::new("this doesn't \"matter\""));
1✔
2045
        let cmd1 = Command::SubFileRef(SubFileRefCmd {
1✔
2046
            color: 16,
2047
            pos: Vec3::new(0.0, 0.0, 0.0),
1✔
2048
            row0: Vec3::new(1.0, 0.0, 0.0),
1✔
2049
            row1: Vec3::new(0.0, 1.0, 0.0),
1✔
2050
            row2: Vec3::new(0.0, 0.0, 1.0),
1✔
2051
            file: SubFileRef::UnresolvedRef("aa/aaaaddd".to_string()),
1✔
2052
        });
2053
        assert_eq!(
1✔
2054
            parse_raw(
2✔
2055
                b"\r\n0 this doesn't \"matter\"\r\n\r\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd\n"
2056
            )
2057
            .unwrap(),
2058
            vec![cmd0, cmd1]
2✔
2059
        );
2060

2061
        let cmd0 = Command::SubFileRef(SubFileRefCmd {
1✔
2062
            color: 16,
2063
            pos: Vec3::new(0.0, 0.0, 0.0),
1✔
2064
            row0: Vec3::new(1.0, 0.0, 0.0),
1✔
2065
            row1: Vec3::new(0.0, 1.0, 0.0),
1✔
2066
            row2: Vec3::new(0.0, 0.0, 1.0),
1✔
2067
            file: SubFileRef::UnresolvedRef("aa/aaaaddd".to_string()),
1✔
2068
        });
2069
        let cmd1 = Command::SubFileRef(SubFileRefCmd {
1✔
2070
            color: 16,
2071
            pos: Vec3::new(0.0, 0.0, 0.0),
1✔
2072
            row0: Vec3::new(1.0, 0.0, 0.0),
1✔
2073
            row1: Vec3::new(0.0, 1.0, 0.0),
1✔
2074
            row2: Vec3::new(0.0, 0.0, 1.0),
1✔
2075
            file: SubFileRef::UnresolvedRef("aa/aaaaddd".to_string()),
1✔
2076
        });
2077
        assert_eq!(
1✔
2078
            parse_raw(
2✔
2079
                b"1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd"
2080
            )
2081
            .unwrap(),
2082
            vec![cmd0, cmd1]
2✔
2083
        );
2084
    }
2085

2086
    #[test]
2087
    fn test_source_file_iter() {
3✔
2088
        let mut source_map = SourceMap::new();
1✔
2089
        let source_file = SourceFile {
2090
            filename: "tata".to_string(),
1✔
2091
            raw_content: vec![],
1✔
2092
            cmds: vec![Command::Triangle(TriangleCmd {
2✔
2093
                color: 2,
2094
                vertices: [
2095
                    Vec3::new(0.0, 0.0, 0.0),
2096
                    Vec3::new(1.0, 0.0, 0.0),
2097
                    Vec3::new(0.0, 1.0, 0.0),
2098
                ],
2099
            })],
2100
        };
2101
        let source_file_ref = source_map.insert(source_file);
1✔
2102
        let s = SourceFile {
2103
            filename: "toto".to_string(),
1✔
2104
            raw_content: vec![],
1✔
2105
            cmds: vec![
2✔
2106
                Command::Triangle(TriangleCmd {
2107
                    color: 16,
2108
                    vertices: [
2109
                        Vec3::new(0.0, 0.0, 1.0),
2110
                        Vec3::new(1.0, 0.0, 1.0),
2111
                        Vec3::new(0.0, 1.0, 1.0),
2112
                    ],
2113
                }),
2114
                Command::Comment(CommentCmd::new("my comment")),
2115
                Command::SubFileRef(SubFileRefCmd {
2116
                    color: 24,
2117
                    pos: Vec3::new(0.0, 0.0, 0.0),
2118
                    row0: Vec3::new(0.0, 0.0, 0.0),
2119
                    row1: Vec3::new(0.0, 0.0, 0.0),
2120
                    row2: Vec3::new(0.0, 0.0, 0.0),
2121
                    file: SubFileRef::ResolvedRef(source_file_ref),
2122
                }),
2123
                Command::Quad(QuadCmd {
2124
                    color: 1,
2125
                    vertices: [
2126
                        Vec3::new(0.0, 1.0, 0.0),
2127
                        Vec3::new(0.0, 1.0, 1.0),
2128
                        Vec3::new(1.0, 1.0, 1.0),
2129
                        Vec3::new(1.0, 1.0, 0.0),
2130
                    ],
2131
                }),
2132
            ],
2133
        };
2134
        let source_file_ref = source_map.insert(s);
1✔
2135
        let source_file = source_file_ref.get(&source_map);
1✔
2136
        for c in source_file.iter(&source_map) {
1✔
2137
            trace!("cmd: {:?}", c);
2✔
2138
        }
2139

2140
        let cmds: Vec<_> = source_file.iter(&source_map).map(|(_, cmd)| cmd).collect();
3✔
2141
        assert_eq!(3, cmds.len());
2✔
2142
        assert!(matches!(&cmds[0], Command::Triangle(_)));
2✔
2143
        assert!(matches!(&cmds[1], Command::Triangle(_)));
2✔
2144
        assert!(matches!(&cmds[2], Command::Quad(_)));
2✔
2145
        if let Command::Triangle(tri_cmd) = &cmds[0] {
2✔
2146
            assert_eq!(16, tri_cmd.color);
1✔
2147
        }
2148
        if let Command::Triangle(tri_cmd) = &cmds[1] {
2✔
2149
            assert_eq!(2, tri_cmd.color);
1✔
2150
        }
2151
        if let Command::Quad(quad_cmd) = &cmds[2] {
2✔
2152
            assert_eq!(1, quad_cmd.color);
1✔
2153
        }
2154

2155
        let cmds: Vec<_> = source_file.local_iter(&source_map).collect();
2✔
2156
        assert_eq!(4, cmds.len());
2✔
2157
        assert!(matches!(&cmds[0], Command::Triangle(_)));
2✔
2158
        assert!(matches!(&cmds[1], Command::Comment(_)));
2✔
2159
        assert!(matches!(&cmds[2], Command::SubFileRef(_)));
2✔
2160
        assert!(matches!(&cmds[3], Command::Quad(_)));
2✔
2161
        if let Command::Triangle(tri_cmd) = &cmds[0] {
2✔
2162
            assert_eq!(16, tri_cmd.color);
1✔
2163
        }
2164
        if let Command::Comment(comment_cmd) = &cmds[1] {
2✔
2165
            assert_eq!("my comment", comment_cmd.text);
2✔
2166
        }
2167
        if let Command::SubFileRef(sfr_cmd) = &cmds[2] {
2✔
2168
            assert_eq!(24, sfr_cmd.color);
1✔
2169
        }
2170
        if let Command::Quad(quad_cmd) = &cmds[3] {
2✔
2171
            assert_eq!(1, quad_cmd.color);
1✔
2172
        }
2173
    }
2174
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc