diff --git a/src/parser.rs b/src/parser.rs index 5df5332..433ff61 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1943,7 +1943,10 @@ fn parse_file_attribute(line: Chars) -> Result { args.first().map(|value| parse_ident(value)).transpose()?, )), (".CreationDate", args, 1) => Ok(FileAttribute::CreationDate(parse_date_time(args[0])?)), - (".GenerationSoftware", args, len) if len <= 3 => { + // Vendor and application are mandatory; anything shorter falls + // through to a user-defined attribute instead of indexing past the + // arguments. + (".GenerationSoftware", args, len) if (2..=3).contains(&len) => { Ok(FileAttribute::GenerationSoftware(GenerationSoftware { vendor: args[0].to_string(), application: args[1].to_string(), diff --git a/tests/component_tests.rs b/tests/component_tests.rs index 919ceea..2d6b2b1 100644 --- a/tests/component_tests.rs +++ b/tests/component_tests.rs @@ -4055,3 +4055,24 @@ fn malformed_aperture_definition() { } if type_str.eq("T") && content.eq("%ADD10T*%") )); } + +/// A `.GenerationSoftware` file attribute with fewer than the mandatory vendor and +/// application values must not panic; it is kept as a user-defined attribute. +#[test] +fn generation_software_with_one_value_does_not_panic() { + // given + logging_init(); + + let reader = gerber_to_reader("%FSLAX23Y23*%\n%MOMM*%\n%TF.GenerationSoftware,KiCad*%\nM02*\n"); + + // when + let doc = parse(reader).unwrap(); + + // then + assert!(doc.commands.iter().any(|c| matches!( + c, + Ok(Command::ExtendedCode(ExtendedCode::FileAttribute( + FileAttribute::UserDefined { name, .. } + ))) if name == ".GenerationSoftware" + ))); +}