1use crate::*;
2
3use thiserror::Error;
4
5use nom::*;
6use nom::branch::alt;
7use nom::bytes::complete::{tag, take, take_until};
8use nom::combinator::{map_res, cut, peek};
9use nom::character::complete::{anychar, char, line_ending, digit1, one_of, space0, space1};
10use nom::multi::{count, many_till};
11use nom::number::complete::double;
12use nom::error::context;
13use nom::sequence::{delimited, preceded, terminated};
14
15use bstr::ByteSlice;
16
17use std::path::Path;
18use std::str::FromStr;
19
20#[derive(Error, Debug)]
21pub enum MshError {
22 #[error("IO error ({source})")]
23 Io {
24 #[from]
25 source: std::io::Error,
26 },
27 #[error("parse error:\n{source}")]
28 Parse {
29 #[from]
30 source: nom::Err<(String, nom::error::ErrorKind)>,
31 }
32}
33
34pub type MshResult<T> = std::result::Result<T, MshError>;
35
36impl Msh {
37 fn from_file<P: AsRef<Path>>(path: P) -> MshResult<Msh> {
38 let (input, header) = msh_header(&first_four_lines(path)?.as_bytes()).unwrap();
39 todo!();
43 }
44}
45
46#[derive(Debug, Copy, Clone, PartialEq, Eq)]
47pub enum Msh2Section {
48 Nodes,
49 Elements,
50 PhysicalGroups,
51 Unknown,
52}
53
54fn parse_msh2_ascii(input: &str) -> IResult<&str, Msh> {
55 let (input, _) = msh2_ascii_header(input)?;
56 let mut msh = Msh::new();
57 let mut msh_input = input;
58 while let Ok((input, section)) = peek_section(msh_input) {
59 let (rest, _) = add_section(&mut msh, section, input)?;
60 msh_input = rest;
61 }
62 Ok((msh_input, msh))
63}
64
65#[derive(Debug, Copy, Clone, PartialEq, Eq)]
66pub enum MshVersion {
67 AsciiV22,
68 AsciiV41,
69 BinaryLeV22,
70 BinaryLeV41,
71}
72
73pub fn parse_single_msh(input: &str, header: MshVersion) -> IResult<&str, Msh> {
76 match header {
77 MshVersion::AsciiV22 => parse_msh2_ascii(input),
78 MshVersion::AsciiV41 => todo!(),
79 MshVersion::BinaryLeV22 => todo!(),
80 MshVersion::BinaryLeV41 => todo!(),
81 }
82}
83
84pub fn parse_msh_file(input: &str) -> MshResult<Vec<Msh>> {
86 let mut msh_input = input;
87 let mut meshes = Vec::new();
88
89 while let Ok((input, header)) = peek_header(msh_input) {
90 match parse_single_msh(input, header) {
91 Ok((rest, msh)) => { meshes.push(msh); msh_input = rest },
92 Err(err) => return Err(err.to_owned().into()),
93 }
94 }
95
96 Ok(meshes)
97}
98
99fn peek_header(input: &str) -> IResult<&str, MshVersion> {
100 peek(alt((
101 msh2_ascii_header,
102 msh4_ascii_header,
103 )))(input)
104}
105
106fn peek_section(input: &str) -> IResult<&str, Msh2Section> {
107 peek(alt((
108 nodes_header,
109 elements_header,
110 physical_groups_header,
111 )))(input)
112}
113
114fn add_section<'a>(mesh: &mut Msh, section: Msh2Section, input: &'a str) -> IResult<&'a str, ()> {
115 use Msh2Section::*;
116 match section {
117 Nodes => {
118 let (rest, nodes) = parse_node_section_msh2(input)?;
119 mesh.nodes = nodes;
120 Ok((rest, ()))
121 },
122 Elements => {
123 let (rest, elts) = parse_elements_section_msh2(input)?;
124 mesh.elts = elts;
125 Ok((rest, ()))
126 }
127 PhysicalGroups => {
128 let (rest, pgs) = parse_physical_groups_msh2(input)?;
129 mesh.physical_groups = pgs;
130 Ok((rest, ()))
131 }
132 _ => todo!(),
133 }
134}
135
136#[test]
137fn peek_sections() {
138 assert!(peek_section("$Nodes").unwrap().1 == Msh2Section::Nodes);
139 assert!(peek_section("$Elements").unwrap().1 == Msh2Section::Elements);
140}
141
142fn nodes_header(input: &str) -> IResult<&str, Msh2Section> {
143 let (input, _) = terminated(tag("$Nodes"), end_of_line)(input)?;
144 Ok((input, Msh2Section::Nodes))
145}
146
147fn elements_header(input: &str) -> IResult<&str, Msh2Section> {
148 let (input, _) = terminated(tag("$Elements"), end_of_line)(input)?;
149 Ok((input, Msh2Section::Elements))
150}
151
152fn physical_groups_header(input: &str) -> IResult<&str, Msh2Section> {
153 let (input, _) = terminated(tag("$PhysicalNames"), end_of_line)(input)?;
154 Ok((input, Msh2Section::PhysicalGroups))
155}
156
157#[test]
158fn headers() {
159 assert!(nodes_header("$Nodes").unwrap().1 == Msh2Section::Nodes);
160 assert!(elements_header("$Elements").unwrap().1 == Msh2Section::Elements);
161}
162
163fn first_four_lines<P: AsRef<Path>>(path: P) -> std::io::Result<String> {
164 use std::io::BufRead;
165 let file = std::fs::File::open(path)?;
168 let mut reader = std::io::BufReader::new(file);
169 let mut buffer = String::new();
170 for _ in 0..=3 {
171 reader.read_line(&mut buffer)?;
172 }
173 Ok(buffer)
174}
175
176pub fn sp(input: &str) -> IResult<&str, &str> {
179 use nom::character::complete::multispace0;
180 multispace0(input)
181 }
184
185pub fn tab(input: &str) -> IResult<&str, &str> {
186 tag("\t")(input)
187}
188
189pub fn whitespace(input: &str) -> IResult<&str, &str> {
190 alt((sp, tab))(input)
191}
192
193pub fn end_of_line(input: &str) -> IResult<&str, &str> {
195 if input.is_empty() {
196 Ok((input, input))
197 } else {
198 let (input, (_, eol)) = many_till(whitespace, line_ending)(input)?;
199 Ok((input, eol))
200 }
201}
202
203pub fn format_header(input: &str) -> IResult<&str, &str> {
204 terminated(tag("$MeshFormat"), end_of_line)(input)
205}
206
207pub fn format_footer(input: &str) -> IResult<&str, &str> {
208 terminated(tag("$EndMeshFormat"), end_of_line)(input)
209}
210
211pub fn msh2_ascii_header(input: &str) -> IResult<&str, MshVersion> {
212 let ((input, _)) = format_header(input)?;
213 let ((input, _)) = terminated(tag("2.2 0 8"), end_of_line)(input)?;
214 let ((input, _)) = format_footer(input)?;
215 Ok((input, MshVersion::AsciiV22))
216}
217
218pub fn msh4_ascii_header(input: &str) -> IResult<&str, MshVersion> {
219 let ((input, _)) = format_header(input)?;
220 let ((input, _)) = terminated(tag("4.1 0 8"), end_of_line)(input)?;
221 let ((input, _)) = format_footer(input)?;
222 Ok((input, MshVersion::AsciiV41))
223}
224
225pub fn msh_header(input: &[u8]) -> IResult<&[u8], MshVersion> {
226 let (input, _) = terminated(tag("$MeshFormat"), line_ending)(input)?;
227 let (input, version) = terminated(alt((tag("2.2"), tag("4.1"))), space1)(input)?;
228 let (input, binary) = terminated(one_of("01"), space1)(input)?;
229 let (mut input, _size_t) = terminated(char('8'), terminated(space0, line_ending))(input)?;
230 if binary == '1' {
231 let (inner_input, endianness) = terminated(take(4usize), line_ending)(input)?;
232 match endianness {
233 [1, 0, 0, 0] => (),
234 [0, 0, 0, 1] => {
235 eprintln!("big-endian files are not supported");
236 return Err(Err::Error((input, nom::error::ErrorKind::Tag)));
237 },
238 _ => panic!("bad endianness byte sequence"),
239 }
240 input = inner_input;
242 }
243 let (input, _) = terminated(tag("$EndMeshFormat"), line_ending)(input)?;
244 match (version, binary) {
245 (b"2.2", '0') => Ok((input, MshVersion::AsciiV22)),
246 (b"2.2", '1') => Ok((input, MshVersion::BinaryLeV22)),
247 (b"4.1", '0') => Ok((input, MshVersion::AsciiV41)),
248 (b"4.1", '1') => Ok((input, MshVersion::BinaryLeV41)),
249 _ => Err(Err::Error((input, nom::error::ErrorKind::Tag))),
250 }
251}
252
253fn parse_node_section_msh2(input: &str) -> IResult<&str, Vec<Node>> {
254 let (input, _) = terminated(tag("$Nodes"), end_of_line)(input)?;
255 let (input, num_nodes) = cut(terminated(parse_u64, end_of_line))(input)?;
256 let (input, (nodes, _)) = many_till(parse_node_msh2, terminated(tag("$EndNodes"), end_of_line))(input)?;
257 if num_nodes != nodes.len() as u64 {
258 eprintln!("warning: node header says {} nodes, but parsed {}", num_nodes, nodes.len());
260 }
261 Ok((input, nodes))
262}
263
264fn parse_unknown_section(input: &str) -> IResult<&str, ()> {
265 let (input, section_name) = delimited(char('$'), take_until("\n"), end_of_line)(input)?;
266 eprintln!("skipping unknown section ${}", section_name);
267 let (input, _) = take_until("$End")(input)?;
269 let (input, _) = delimited(char('$'), take_until("\n"), end_of_line)(input)?;
271 Ok((input, ()))
272}
273
274fn parse_node_msh2(input: &str) -> IResult<&str, Node> {
275 do_parse!(input,
276 tag: parse_u64 >> sp >>
277 x: double >> sp >>
278 y: double >> sp >>
279 z: double >> sp >>
280 ( Node { tag, x, y, z } )
281 )
282}
283
284fn parse_physical_groups_msh2(input: &str) -> IResult<&str, Vec<PhysicalGroup>> {
285 let (input, _) = terminated(tag("$PhysicalNames"), end_of_line)(input)?;
286 let (input, num_groups) = cut(terminated(parse_u64, end_of_line))(input)?;
287 let (input, (groups, _)) = many_till(parse_physical_group_msh2, terminated(tag("$EndPhysicalNames"), end_of_line))(input)?;
288 if num_groups != groups.len() as u64 {
289 eprintln!("warning: header says {} physical groups, but read {}", num_groups, groups.len());
291 }
292 Ok((input, groups))
293}
294
295fn parse_physical_group_msh2(input: &str) -> IResult<&str, PhysicalGroup> {
296 let (input, dim) = parse_dimension(input)?;
297 let (input, _) = sp(input)?;
298 let (input, tag) = parse_u64(input)?;
299 let (input, _) = sp(input)?;
300 let (input, name) = delimited(char('"'), take_until("\""), char('"'))(input)?;
301 let (input, _) = end_of_line(input)?;
302 Ok((input, PhysicalGroup { dim, tag, name: name.to_string() }))
303}
304
305fn parse_dimension(input: &str) -> IResult<&str, Dim> {
306 let (input, dim) = one_of("0123")(input)?;
307 Ok((input, Dim::from_u8_unchecked(u8::from_str(&dim.to_string()).unwrap())))
308}
309
310fn parse_u64(input: &str) -> IResult<&str, u64> {
311 map_res(digit1, u64::from_str)(input)
312}
313
314fn parse_u64_sp(input: &str) -> IResult<&str, u64> {
315 terminated(map_res(digit1, u64::from_str), sp)(input)
316}
317
318fn parse_elements_section_msh2(input: &str) -> IResult<&str, Vec<MeshElt>> {
319 let (input, _) = terminated(tag("$Elements"), end_of_line)(input)?;
320 let (input, num_elts) = cut(terminated(parse_u64, end_of_line))(input)?;
321 let (input, (elts, _)) = many_till(parse_element_msh2, terminated(tag("$EndElements"), end_of_line))(input)?;
322 if num_elts != elts.len() as u64 {
323 eprintln!("warning: header says {} elements, but read {}", num_elts, elts.len());
325 }
326 Ok((input, elts))
327}
328
329fn parse_element_msh2(input: &str) -> IResult<&str, MeshElt> {
330 let (input, tag) = terminated(parse_u64, sp)(input)?;
331 let (input, label) = terminated(digit1, sp)(input)?;
332 let elt_type = match MeshShape::from_gmsh_label(label) {
333 Some(ty) => ty,
334 None => panic!(format!("unknown mesh element type: {}", label)),
335 };
336
337 let (input, elt_info) = parse_elt_info(input)?;
338 let (input, nodes) = count(parse_u64_sp, elt_type.num_nodes() as usize)(input)?;
339
340 let uint_to_tag = |uint| if uint != 0 { Some(uint) } else { None };
341 Ok((input, MeshElt {
342 tag,
343 ty: elt_type,
344 nodes,
345 physical_group: uint_to_tag(elt_info.physical_group),
347 geometry: uint_to_tag(elt_info.geometry),
348 }))
349}
350
351struct EltInfo {
352 pub physical_group: Tag,
353 pub geometry: Tag,
354 }
361
362fn parse_elt_info(input: &str) -> IResult<&str, EltInfo> {
363 let (input, num_info) = parse_u64_sp(input)?;
364 if num_info > 2 {
365 eprintln!("warning: only reading physical group and geometry information and skipping partitions, ghost elements...");
366 }
367 let (input, elt_info) = count(parse_u64_sp, num_info as usize)(input)?;
368 Ok((input, EltInfo { physical_group: elt_info[0], geometry: elt_info[1] }))
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use std::path::PathBuf;
375 use insta::{assert_debug_snapshot, assert_display_snapshot};
376
377 #[test]
378 fn trailing_spaces() {
379 let inp = "101 0 1 100.0 \t\n";
380 assert_debug_snapshot!(parse_node_msh2(inp).unwrap().1);
381 }
382
383mod msh2 {
384
385 use super::*;
386
387 #[test]
388 fn msh2_ascii() {
389 let msh = std::fs::read_to_string("props/v2/basic.msh").unwrap();
390 assert_debug_snapshot!(parse_msh2_ascii(&msh).unwrap().1);
391 }
392
393 #[test]
394 fn concatenated_mesh_files() {
395 let mut msh = std::fs::read_to_string("props/v2/basic.msh").unwrap();
397 let msh = msh.clone() + &msh;
398 assert_debug_snapshot!(parse_msh_file(&msh).unwrap());
399 }
400
401 #[test]
402 fn unknown_section() {
403 assert_debug_snapshot!(parse_unknown_section("$Comments\nhi there\nfinished in 10.2seconds\n$EndComments\n").unwrap().1);
404 }
405
406 #[test]
407 fn section_failure() {
408 let input =
410 "$Elements\n\
411 1 15 2 0 0 5\n\
412 500 1 2 1 2 30 31\n\
413 $EndElements";
414 let mut msh_input = input;
415 let res = parse_msh2_ascii(msh_input);
416 assert!(res.is_err());
417 }
418
419 #[test]
420 fn node_elt() {
421 assert_debug_snapshot!(parse_element_msh2("1 15 2 0 0 5\n").unwrap().1);
422 }
423
424 #[test]
425 fn line_elt() {
426 assert_debug_snapshot!(parse_element_msh2("500 1 2 1 2 30 31\n").unwrap().1);
427 }
428
429 #[test]
430 fn tri_elt() {
431 assert_debug_snapshot!(parse_element_msh2("10 2 2 5 1 1 2 3\n").unwrap().1);
432 }
433
434 #[test]
435 fn tetra_elt() {
436 assert_debug_snapshot!(parse_element_msh2("41 4 2 0 1 1 2 3 4\n").unwrap().1);
437 }
438
439 #[test]
440 fn elt_extra_fields() {
441 assert_debug_snapshot!(parse_element_msh2("41 4 5 0 1 1 2 3 41 42 43 44\n").unwrap().1);
442 }
443
444 #[test]
445 fn node_msh2() {
446 let i = "1201 0 0. 1.";
447 assert_debug_snapshot!(parse_node_msh2(i).unwrap().1);
448 }
449
450 #[test]
451 fn pgroup_msh2() {
452 let i = r#"3 1 "Water cube""#;
453 assert_debug_snapshot!(parse_physical_group_msh2(i).unwrap().1);
454 }
455
456 #[test]
457 fn pgroups_section() {
458 assert_debug_snapshot!(parse_physical_groups_msh2(
459 &r#"$PhysicalNames
4604
4610 1 "a point"
4620 2 "hi"
4633 3 "Water-cube"
4642 4 "fuselage"
465$EndPhysicalNames"#).unwrap().1);
466 }
467
468 #[test]
469 fn bad_physical_group_dimension() {
470 let res = parse_physical_group_msh2(&r#"4 1 "Water cube"#);
471 assert!(res.is_err());
472 if let Err(trace) = res {
473 assert_display_snapshot!(trace);
474 }
475 }
476
477 #[test]
478 fn some_nodes() {
479 let i = "$Nodes\n3\n1 0. 0. 1.\n2 1. 1 1\n100 1 1 1\n$EndNodes\n";
480 assert_debug_snapshot!(parse_node_section_msh2(i).unwrap().1);
481 }
482
483 #[test]
484 fn nodes_len_mismatch() {
485 let inp = "$Nodes\n0\n1 0. 0. 1.\n2 1. 1 1\n100 1 1 1\n$EndNodes\n";
486 assert_debug_snapshot!(parse_node_section_msh2(inp).unwrap().1);
487 }
488
489 #[test]
490 fn empty_nodes() {
491 let inp = "$Nodes\n0\n$EndNodes\n";
492 assert_debug_snapshot!(parse_node_section_msh2(inp).unwrap().1);
493 }
494
495 #[test]
496 fn msh_header_test() {
497 let header = "$MeshFormat\n2.2 0 8\n$EndMeshFormat\n";
498 match msh_header(header.as_bytes()) {
499 Ok((_, MshVersion::AsciiV22)) => (),
500 _else => {std::dbg!(_else); panic!("bad mesh header")},
501 };
502 let header = "$MeshFormat\n2.2 1 8\n\u{1}\u{0}\u{0}\u{0}\n$EndMeshFormat\n";
503 match msh_header(header.as_bytes()) {
504 Ok((_, MshVersion::BinaryLeV22)) => (),
505 _else => {std::dbg!(_else); panic!("bad mesh header")},
506 };
507 }
508
509 #[test]
510 fn msh2_ascii_header() {
511 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
512 path.push("props");
513 path.push("v2");
514 path.push("empty.msh");
515 assert_debug_snapshot!(msh_header(&first_four_lines(path).unwrap().as_bytes()).unwrap());
516 }
517
518 #[test]
519 fn unix_line_endings() {
520 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
521 path.push("props");
522 path.push("v2");
523 path.push("unix-empty.msh");
524 assert_debug_snapshot!(msh_header(&first_four_lines(path).unwrap().as_bytes()).unwrap());
525 }
526
527 #[test]
528 fn msh2_binary_header() {
529 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
530 path.push("props");
531 path.push("v2");
532 path.push("empty-bin.msh");
533 assert_debug_snapshot!(msh_header(&first_four_lines(path).unwrap().as_bytes()).unwrap());
534 }
535
536 #[test]
537 fn bad_header() {
538 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
539 path.push("props");
540 path.push("v2");
541 path.push("bad-header.msh");
542 let header_str = first_four_lines(path).unwrap();
543 let res = msh_header(&header_str.as_bytes());
544 assert!(res.is_err());
545 if let Err(trace) = res {
546 assert_display_snapshot!(trace);
547 }
548 }
549}
550
551mod msh4 {
552 use super::*;
553
554 #[test]
555 fn msh4_ascii_header() {
556 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
557 path.push("props");
558 path.push("v4");
559 path.push("empty.msh");
560 assert_debug_snapshot!(msh_header(&first_four_lines(path).unwrap().as_bytes()).unwrap());
561 }
562
563 #[test]
564 fn msh4_binary_header() {
565 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
566 path.push("props");
567 path.push("v4");
568 path.push("empty-bin.msh");
569 assert_debug_snapshot!(msh_header(&first_four_lines(path).unwrap().as_bytes()).unwrap());
570 }
571}
572}