@@ -10,14 +10,7 @@ use crate::gerber_types::{
1010} ;
1111use crate :: util:: { partial_coordinates_from_gerber, partial_coordinates_offset_from_gerber} ;
1212use crate :: ParseError ;
13- use gerber_types:: {
14- ApertureBlock , AxisSelect , CommentContent , ComponentCharacteristics , ComponentDrill ,
15- ComponentMounting , ComponentOutline , CopperType , DrillFunction , DrillRouteType ,
16- ExtendedPosition , GenerationSoftware , GerberDate , IPC4761ViaProtection , Ident , ImageMirroring ,
17- ImageName , ImageOffset , ImagePolarity , ImageRotation , ImageScaling , Mirroring , Net ,
18- NonPlatedDrill , ObjectAttribute , Pin , PlatedDrill , Position , Profile , Rotation , Scaling ,
19- StandardComment , ThermalPrimitive , Uuid , VariableDefinition ,
20- } ;
13+ use gerber_types:: { ApertureBlock , AttributeDeletionCriterion , AxisSelect , CommentContent , ComponentCharacteristics , ComponentDrill , ComponentMounting , ComponentOutline , CoordinateMode , CopperType , DrillFunction , DrillRouteType , ExtendedPosition , GenerationSoftware , GerberDate , IPC4761ViaProtection , Ident , ImageMirroring , ImageName , ImageOffset , ImagePolarity , ImageRotation , ImageScaling , Mirroring , Net , NonPlatedDrill , ObjectAttribute , Pin , PlatedDrill , Position , Profile , Rotation , Scaling , StandardComment , ThermalPrimitive , Uuid , VariableDefinition , ZeroOmission } ;
2114use lazy_regex:: * ;
2215use regex:: Regex ;
2316use std:: collections:: HashMap ;
@@ -45,7 +38,7 @@ static RE_LOAD_MIRRORING: Lazy<Regex> = lazy_regex!(r"%LM(?P<mirroring>N|X|Y|XY)
4538// Note: scaling cannot be negative, or 0.
4639static RE_LOAD_SCALING : Lazy < Regex > = lazy_regex ! ( r"%LS(?P<value>[0-9]+(?:\.[0-9]*)?)\*%" ) ;
4740static RE_LOAD_ROTATION : Lazy < Regex > = lazy_regex ! ( r"%LR(?P<value>[+-]?[0-9]+(?:\.[0-9]*)?)\*%" ) ;
48- static RE_FORMAT_SPEC : Lazy < Regex > = lazy_regex ! ( r"%FSLAX (.*)Y(.*)\*%" ) ;
41+ static RE_FORMAT_SPEC : Lazy < Regex > = lazy_regex ! ( r"%FS[LT][AI]X (.*)Y(.*)\*%" ) ;
4942
5043/// https://regex101.com/r/YNnrmK/1
5144static RE_APERTURE : Lazy < Regex > =
@@ -70,13 +63,17 @@ static RE_MACRO_VARIABLE: Lazy<Regex> =
7063struct ParserContext < T : Read > {
7164 line_number : usize ,
7265 lines : Lines < BufReader < T > > ,
66+ aperture_attributes : HashMap < String , ApertureAttribute > ,
67+ object_attributes : HashMap < String , ObjectAttribute > ,
7368}
7469
7570impl < T : Read > ParserContext < T > {
7671 pub fn new ( lines : Lines < BufReader < T > > ) -> ParserContext < T > {
7772 ParserContext {
7873 line_number : 0 ,
7974 lines,
75+ aperture_attributes : HashMap :: new ( ) ,
76+ object_attributes : HashMap :: new ( ) ,
8077 }
8178 }
8279
@@ -233,7 +230,7 @@ fn parse_line<T: Read>(
233230 }
234231 '4' => {
235232 // G04
236- commands. push ( parse_comment ( line) )
233+ commands. push ( parse_comment ( line, parser_context ) )
237234 }
238235 _ => commands. push ( Err ( ContentError :: UnknownCommand { } ) ) ,
239236 }
@@ -351,15 +348,22 @@ fn parse_line<T: Read>(
351348 linechars = trim_attr_line( linechars) ?;
352349
353350 parse_aperture_attribute( linechars. clone( ) )
354- . map( |attr| ExtendedCode :: ApertureAttribute ( attr) . into( ) )
351+ . map( |( name, attr) | {
352+ parser_context. aperture_attributes. insert( name, attr. clone( ) ) ;
353+ ExtendedCode :: ApertureAttribute ( attr) . into( )
354+ } )
355355 }
356356 'O' => {
357357 linechars = trim_attr_line( linechars) ?;
358-
359358 parse_object_attribute( linechars. clone( ) )
360- . map( |attr| ExtendedCode :: ObjectAttribute ( attr) . into( ) )
359+ . map( |( name, attr) | {
360+ parser_context. object_attributes. insert( name, attr. clone( ) ) ;
361+ ExtendedCode :: ObjectAttribute ( attr) . into( )
362+ } )
361363 }
362- 'D' => parse_delete_attribute( linechars. clone( ) ) ,
364+ 'D' => parse_delete_attribute( linechars. clone( ) , parser_context)
365+ . map( ExtendedCode :: DeleteAttribute )
366+ . map( Command :: ExtendedCode ) ,
363367 _ => Err ( ContentError :: UnknownCommand { } ) ,
364368 } ,
365369 'S' => match linechars. next( ) . ok_or( ContentError :: UnknownCommand { } ) ? {
@@ -702,7 +706,7 @@ fn parse_load_rotation(line: &str) -> Result<Command, ContentError> {
702706///
703707/// String: `G04 (String)*`
704708/// Standard: `G04 #@! (Attribute)*`
705- fn parse_comment ( line : & str ) -> Result < Command , ContentError > {
709+ fn parse_comment < T : Read > ( line : & str , parser_context : & mut ParserContext < T > ) -> Result < Command , ContentError > {
706710 match RE_COMMENT . captures ( line) {
707711 Some ( regmatch) => {
708712 let string_content = regmatch
@@ -724,9 +728,17 @@ fn parse_comment(line: &str) -> Result<Command, ContentError> {
724728 'F' => parse_file_attribute ( comment_chars. clone ( ) )
725729 . map ( StandardComment :: FileAttribute ) ,
726730 'A' => parse_aperture_attribute ( comment_chars. clone ( ) )
727- . map ( StandardComment :: ApertureAttribute ) ,
731+ . map ( |( name, attr) |{
732+ parser_context. aperture_attributes . insert ( name, attr. clone ( ) ) ;
733+ StandardComment :: ApertureAttribute ( attr)
734+ } ) ,
728735 'O' => parse_object_attribute ( comment_chars. clone ( ) )
729- . map ( StandardComment :: ObjectAttribute ) ,
736+ . map ( |( name, attr) |{
737+ parser_context. object_attributes . insert ( name, attr. clone ( ) ) ;
738+ StandardComment :: ObjectAttribute ( attr)
739+ } ) ,
740+ 'D' => parse_comment_delete_attribute ( comment_chars. clone ( ) , parser_context)
741+ . map ( StandardComment :: DeleteAttribute ) ,
730742 _ => Err ( ContentError :: UnknownCommand { } ) ,
731743 } ,
732744 _ => Err ( ContentError :: UnknownCommand { } ) ,
@@ -1202,13 +1214,34 @@ fn parse_units(line: &str, gerber_doc: &GerberDoc) -> Result<Unit, ContentError>
12021214}
12031215
12041216/// parse a Gerber format spec statement (e.g. '%FSLAX23Y23*%')
1217+ /// FS<L,T><A|I>X<int><dec>Y<int><dec>*%
1218+ /// <L,T> = Leading or Trailing (omitted = Leading)
1219+ /// <A|I> = Absolute or Incremental (omitted = Absolute)
1220+ /// <int> = number of integer digits (1-6)
12051221fn parse_format_spec ( line : & str , gerber_doc : & GerberDoc ) -> Result < CoordinateFormat , ContentError > {
12061222 // Ensure that FS was not set before, which would imply two FS statements in the same doc
12071223 if gerber_doc. format_specification . is_some ( ) {
12081224 Err ( ContentError :: TriedToFormatTwice { } )
12091225 } else {
12101226 match RE_FORMAT_SPEC . captures ( line) {
12111227 Some ( regmatch) => {
1228+ let fs_str = regmatch. get ( 0 )
1229+ . ok_or ( ContentError :: MissingRegexCapture {
1230+ regex : RE_FORMAT_SPEC . clone ( ) ,
1231+ capture_index : 0 ,
1232+ } ) ?
1233+ . as_str ( ) ;
1234+
1235+ let zero_omission = match fs_str. chars ( ) . nth ( 3 ) {
1236+ Some ( 'T' ) => ZeroOmission :: Trailing ,
1237+ _ => ZeroOmission :: Leading ,
1238+ } ;
1239+
1240+ let mode = match fs_str. chars ( ) . nth ( 4 ) {
1241+ Some ( 'I' ) => CoordinateMode :: Incremental ,
1242+ _ => CoordinateMode :: Absolute ,
1243+ } ;
1244+
12121245 let mut fs_chars = regmatch
12131246 . get ( 1 )
12141247 . ok_or ( ContentError :: MissingRegexCapture {
@@ -1235,8 +1268,9 @@ fn parse_format_spec(line: &str, gerber_doc: &GerberDoc) -> Result<CoordinateFor
12351268 } ) ;
12361269 }
12371270
1238- Ok ( CoordinateFormat :: new ( integer, decimal) )
1271+ Ok ( CoordinateFormat :: new ( zero_omission , mode , integer, decimal) )
12391272 }
1273+
12401274 None => Err ( ContentError :: NoRegexMatch {
12411275 regex : RE_FORMAT_SPEC . clone ( ) ,
12421276 } ) ,
@@ -1492,31 +1526,47 @@ fn parse_command(command_str: &str) -> Result<Command, ContentError> {
14921526 }
14931527}
14941528
1529+
1530+ fn format_coordinate_number ( coord : & str , fs : & CoordinateFormat ) -> Result < i64 , ContentError > {
1531+ let len = fs. integer + fs. decimal ;
1532+ Ok ( match fs. zero_omission {
1533+ ZeroOmission :: Leading => {
1534+ parse_coord :: < i64 > ( coord) ?
1535+ }
1536+ ZeroOmission :: Trailing => {
1537+ let len = if coord. starts_with ( "-" ) { len + 1 } else { len } ;
1538+ let formated = format ! ( "{:0<width$}" , coord, width = len as usize ) ;
1539+ parse_coord :: < i64 > ( formated. as_str ( ) ) ?
1540+ }
1541+ } )
1542+ }
1543+
14951544// parse a Gerber interpolation command (e.g. 'X2000Y40000I300J50000D01*')
14961545fn parse_interpolation ( line : & str , gerber_doc : & GerberDoc ) -> Result < Command , ContentError > {
1546+
14971547 match RE_INTERPOLATION . captures ( line) {
14981548 Some ( regmatch) => {
1549+ let fs = gerber_doc
1550+ . format_specification
1551+ . ok_or ( ContentError :: OperationBeforeFormat { } ) ?;
1552+
14991553 let x_coord = regmatch
15001554 . get ( 1 )
1501- . map ( |x| parse_coord :: < i64 > ( x. as_str ( ) ) )
1555+ . map ( |x| format_coordinate_number ( x. as_str ( ) , & fs ) )
15021556 . transpose ( ) ?;
15031557 let y_coord = regmatch
15041558 . get ( 2 )
1505- . map ( |y| parse_coord :: < i64 > ( y . as_str ( ) ) )
1559+ . map ( |x| format_coordinate_number ( x . as_str ( ) , & fs ) )
15061560 . transpose ( ) ?;
15071561 let i_coord = regmatch
15081562 . get ( 3 )
1509- . map ( |i| parse_coord :: < i64 > ( i . as_str ( ) ) )
1563+ . map ( |x| format_coordinate_number ( x . as_str ( ) , & fs ) )
15101564 . transpose ( ) ?;
15111565 let j_coord = regmatch
15121566 . get ( 4 )
1513- . map ( |i| parse_coord :: < i64 > ( i . as_str ( ) ) )
1567+ . map ( |x| format_coordinate_number ( x . as_str ( ) , & fs ) )
15141568 . transpose ( ) ?;
15151569
1516- let fs = gerber_doc
1517- . format_specification
1518- . ok_or ( ContentError :: OperationBeforeFormat { } ) ?;
1519-
15201570 let coordinates =
15211571 partial_coordinates_from_gerber ( x_coord, y_coord, fs) . map_err ( |error| {
15221572 ContentError :: CoordinateFormatMismatch {
@@ -1553,19 +1603,19 @@ fn parse_move_or_flash(
15531603) -> Result < Command , ContentError > {
15541604 match RE_MOVE_OR_FLASH . captures ( line) {
15551605 Some ( regmatch) => {
1606+ let fs = gerber_doc
1607+ . format_specification
1608+ . ok_or ( ContentError :: OperationBeforeFormat { } ) ?;
1609+
15561610 let x_coord = regmatch
15571611 . get ( 1 )
1558- . map ( |x| parse_coord :: < i64 > ( x. as_str ( ) ) )
1612+ . map ( |x| format_coordinate_number ( x. as_str ( ) , & fs ) )
15591613 . transpose ( ) ?;
15601614 let y_coord = regmatch
15611615 . get ( 2 )
1562- . map ( |y| parse_coord :: < i64 > ( y . as_str ( ) ) )
1616+ . map ( |x| format_coordinate_number ( x . as_str ( ) , & fs ) )
15631617 . transpose ( ) ?;
15641618
1565- let fs = gerber_doc
1566- . format_specification
1567- . ok_or ( ContentError :: OperationBeforeFormat { } ) ?;
1568-
15691619 let coords =
15701620 partial_coordinates_from_gerber ( x_coord, y_coord, fs) . map_err ( |error| {
15711621 ContentError :: CoordinateFormatMismatch {
@@ -1946,7 +1996,7 @@ fn split_first_str<'a>(slice: &'a [&'a str]) -> (&'a str, &'a [&'a str], usize)
19461996/// Parse an Aperture Attribute (%TA.<AttributeName>[,<AttributeValue>]*%) into Command
19471997///
19481998/// ⚠️ This parsing statement needs a lot of tests and validation!
1949- fn parse_aperture_attribute ( line : Chars ) -> Result < ApertureAttribute , ContentError > {
1999+ fn parse_aperture_attribute ( partial_line : Chars ) -> Result < ( String , ApertureAttribute ) , ContentError > {
19502000 use ContentError :: UnsupportedApertureAttribute ;
19512001
19522002 build_enum_map ! ( IPC_MAP , IPC4761ViaProtection ) ;
@@ -1978,13 +2028,13 @@ fn parse_aperture_attribute(line: Chars) -> Result<ApertureAttribute, ContentErr
19782028 } )
19792029 }
19802030
1981- let raw_line = line . as_str ( ) . to_string ( ) ;
1982- let attr_args = attr_args ( line ) ;
2031+ let raw_line = partial_line . as_str ( ) . to_string ( ) ;
2032+ let attr_args = attr_args ( partial_line ) ;
19832033
19842034 log:: trace!( "TA ARGS: {:?}" , attr_args) ;
19852035
19862036 let ( first, remaining_args, remaining_len) = split_first_str ( & attr_args) ;
1987- match ( first, remaining_args, remaining_len) {
2037+ let attr = match ( first, remaining_args, remaining_len) {
19882038 ( ".AperFunction" , remaining_args, len) if len >= 1 => {
19892039 let ( first, remaining_args, remaining_len) = split_first_str ( remaining_args) ;
19902040 Ok ( ApertureAttribute :: ApertureFunction (
@@ -2088,13 +2138,15 @@ fn parse_aperture_attribute(line: Chars) -> Result<ApertureAttribute, ContentErr
20882138 name : arg. to_string ( ) ,
20892139 values : args. iter ( ) . map ( |v| v. to_string ( ) ) . collect ( ) ,
20902140 } ) ,
2091- }
2141+ } ;
2142+
2143+ attr. map ( |it|( first. to_string ( ) , it) )
20922144}
20932145
20942146/// Parse an Object Attribute (%TO.<AttributeName>[,<AttributeValue>]*%) into Command
20952147///
20962148/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage!
2097- fn parse_object_attribute ( line : Chars ) -> Result < ObjectAttribute , ContentError > {
2149+ fn parse_object_attribute ( partial_line : Chars ) -> Result < ( String , ObjectAttribute ) , ContentError > {
20982150 macro_rules! parse_cc_decimal {
20992151 ( $cc: ident, $value: expr) => { {
21002152 let decimal = $value
@@ -2113,13 +2165,13 @@ fn parse_object_attribute(line: Chars) -> Result<ObjectAttribute, ContentError>
21132165 } } ;
21142166 }
21152167
2116- let attr_args = attr_args ( line ) ;
2168+ let attr_args = attr_args ( partial_line ) ;
21172169
21182170 log:: trace!( "TO ARGS: {:?}" , attr_args) ;
21192171
21202172 let ( first, remaining_args, remaining_len) = split_first_str ( & attr_args) ;
21212173
2122- match ( first, remaining_args, remaining_len) {
2174+ let attr = match ( first, remaining_args, remaining_len) {
21232175 ( ".N" , args, len) if len >= 1 => {
21242176 // See 2024.05 - 5.6.13 ".N (Net)" "
21252177 let first = args. first ( ) . unwrap ( ) ;
@@ -2180,21 +2232,37 @@ fn parse_object_attribute(line: Chars) -> Result<ObjectAttribute, ContentError>
21802232 name : arg. to_string ( ) ,
21812233 values : args. iter ( ) . map ( |v| v. to_string ( ) ) . collect ( ) ,
21822234 } ) ,
2183- }
2235+ } ;
2236+
2237+ attr. map ( |it|( first. to_string ( ) , it) )
21842238}
21852239
2186- fn parse_delete_attribute ( line : Chars ) -> Result < Command , ContentError > {
2187- let raw_line = line. as_str ( ) . to_string ( ) ;
2240+ fn parse_delete_attribute < T : Read > ( line : Chars , parser_context : & mut ParserContext < T > ) -> Result < AttributeDeletionCriterion , ContentError > {
21882241 let line = trim_attr_line ( line) ?;
2189- let attr_args = attr_args ( line) ;
2242+ let value = line. as_str ( ) . to_string ( ) ;
21902243
2191- if attr_args. len ( ) == 1 {
2192- Ok ( ExtendedCode :: DeleteAttribute ( attr_args[ 0 ] . to_string ( ) ) . into ( ) )
2193- } else {
2194- Err ( ContentError :: InvalidDeleteAttribute {
2195- delete_attribute : raw_line,
2196- } )
2244+ parse_delete_attribute_inner ( parser_context, value)
2245+ }
2246+
2247+ fn parse_comment_delete_attribute < T : Read > ( comment : Chars , parser_context : & mut ParserContext < T > ) -> Result < AttributeDeletionCriterion , ContentError > {
2248+ let value = comment. as_str ( ) . trim ( ) . to_string ( ) ;
2249+ parse_delete_attribute_inner ( parser_context, value)
2250+ }
2251+
2252+ fn parse_delete_attribute_inner < T : Read > ( parser_context : & mut ParserContext < T > , value : String ) -> Result < AttributeDeletionCriterion , ContentError > {
2253+ if value. is_empty ( ) {
2254+ return Ok ( AttributeDeletionCriterion :: AllApertureAndObjectAttributes )
2255+ }
2256+ if parser_context. aperture_attributes . contains_key ( & value) {
2257+ parser_context. aperture_attributes . remove ( & value) ;
2258+ return Ok ( AttributeDeletionCriterion :: SingleApertureAttribute ( value) )
21972259 }
2260+ if parser_context. object_attributes . contains_key ( & value) {
2261+ parser_context. object_attributes . remove ( & value) ;
2262+ return Ok ( AttributeDeletionCriterion :: SingleObjectAttribute ( value) )
2263+ }
2264+
2265+ Err ( ContentError :: InvalidDeleteAttribute { delete_attribute : value } )
21982266}
21992267
22002268/// Split the line by commas and convert to a vector of strings
@@ -2303,7 +2371,7 @@ fn parse_macro_integer(value: &str) -> Result<MacroInteger, ContentError> {
23032371 }
23042372}
23052373
2306- fn attr_args ( partial_line : Chars ) -> Vec < & str > {
2374+ fn attr_args ( partial_line : Chars < ' _ > ) -> Vec < & str > {
23072375 partial_line
23082376 . as_str ( )
23092377 . split ( ',' )
@@ -2322,7 +2390,6 @@ fn trim_attr_line(mut partial_line: Chars) -> Result<Chars, ContentError> {
23222390 } ) ,
23232391 }
23242392}
2325-
23262393#[ cfg( test) ]
23272394mod attr_args_tests {
23282395 use super :: * ;
0 commit comments