@@ -52,8 +52,10 @@ static RE_FORMAT_SPEC: Lazy<Regex> = lazy_regex!(r"%FS[LT][AI]X(.*)Y(.*)\*%");
5252static RE_APERTURE : Lazy < Regex > =
5353 lazy_regex ! ( r"%ADD([0-9]+)([._$a-zA-Z][._$a-zA-Z0-9]{0,126})(?:,\s?(.*))?\*%" ) ;
5454static RE_APERTURE_BLOCK : Lazy < Regex > = lazy_regex ! ( r"%AB(D(?P<code>[0-9]+))?\*%" ) ;
55+ // The `D(0)?1` suffix is optional to support the deprecated modal-D01 form
56+ // (gerber spec 8.3); the caller has already classified the line as interpolate.
5557static RE_INTERPOLATION : Lazy < Regex > =
56- lazy_regex ! ( r"X?(-?[0-9]+)?Y?(-?[0-9]+)?I?(-?[0-9]+)?J?(-?[0-9]+)?D(0)?1\*" ) ;
58+ lazy_regex ! ( r"X?(-?[0-9]+)?Y?(-?[0-9]+)?I?(-?[0-9]+)?J?(-?[0-9]+)?(?: D(0)?1)? \*" ) ;
5759static RE_MOVE_OR_FLASH : Lazy < Regex > = lazy_regex ! ( r"X?(-?[0-9]+)?Y?(-?[0-9]+)?D(0)?[2-3]*" ) ;
5860static RE_IMAGE_NAME : Lazy < Regex > = lazy_regex ! ( r"%IN(.*)\*%" ) ;
5961static RE_STEP_REPEAT : Lazy < Regex > =
@@ -68,11 +70,20 @@ static RE_MACRO_DECIMAL: Lazy<Regex> = lazy_regex!(
6870static RE_MACRO_VARIABLE : Lazy < Regex > =
6971 lazy_regex ! ( r"\$(?P<number>\d+)\s*=\s*(?P<expression>[^*]+)\s*" ) ;
7072
73+ // Implicit operation code for the deprecated "Coordinate Data without Operation Code" form
74+ // (gerber spec 8.3). Set to `Interpolate` by D01; reset to `Undefined` by D02/D03/aperture select.
75+ #[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
76+ enum ModalOperationMode {
77+ Undefined ,
78+ Interpolate ,
79+ }
80+
7181struct ParserContext < T : Read > {
7282 line_number : usize ,
7383 lines : Lines < BufReader < T > > ,
7484 aperture_attributes : HashMap < String , ApertureAttribute > ,
7585 object_attributes : HashMap < String , ObjectAttribute > ,
86+ modal_operation : ModalOperationMode ,
7687}
7788
7889impl < T : Read > ParserContext < T > {
@@ -82,6 +93,7 @@ impl<T: Read> ParserContext<T> {
8293 lines,
8394 aperture_attributes : HashMap :: new ( ) ,
8495 object_attributes : HashMap :: new ( ) ,
96+ modal_operation : ModalOperationMode :: Undefined ,
8597 }
8698 }
8799
@@ -98,6 +110,22 @@ impl<T: Read> ParserContext<T> {
98110 } )
99111 } )
100112 }
113+
114+ // Update the modal operation mode after a command was parsed (gerber spec 8.3).
115+ fn update_modal_from_command ( & mut self , cmd : & Command ) {
116+ self . modal_operation = match cmd {
117+ Command :: FunctionCode ( FunctionCode :: DCode ( DCode :: Operation (
118+ Operation :: Interpolate ( ..) ,
119+ ) ) ) => ModalOperationMode :: Interpolate ,
120+ Command :: FunctionCode ( FunctionCode :: DCode ( DCode :: Operation (
121+ Operation :: Move ( ..) | Operation :: Flash ( ..) ,
122+ ) ) )
123+ | Command :: FunctionCode ( FunctionCode :: DCode ( DCode :: SelectAperture ( _) ) ) => {
124+ ModalOperationMode :: Undefined
125+ }
126+ _ => return ,
127+ } ;
128+ }
101129}
102130
103131/// Parse a gerber string (in BufReader) to a GerberDoc
@@ -143,6 +171,7 @@ pub fn parse<T: Read>(reader: BufReader<T>) -> Result<GerberDoc, (GerberDoc, Par
143171 let final_result = match result {
144172 Ok ( command) => {
145173 log:: trace!( "Parsed command: {:?}" , command) ;
174+ parser_context. update_modal_from_command ( & command) ;
146175 Ok ( command)
147176 }
148177 Err ( ContentError :: IoError ( error) ) => {
@@ -204,7 +233,7 @@ fn parse_line<T: Read>(
204233 commands. push ( parse_interpolate_move_or_flash (
205234 remaining_line,
206235 gerber_doc,
207- & mut linechars ,
236+ parser_context . modal_operation ,
208237 ) ) ;
209238 }
210239 }
@@ -218,7 +247,7 @@ fn parse_line<T: Read>(
218247 commands. push ( parse_interpolate_move_or_flash (
219248 remaining_line,
220249 gerber_doc,
221- & mut linechars ,
250+ parser_context . modal_operation ,
222251 ) ) ;
223252 }
224253 }
@@ -232,7 +261,7 @@ fn parse_line<T: Read>(
232261 commands. push ( parse_interpolate_move_or_flash (
233262 remaining_line,
234263 gerber_doc,
235- & mut linechars ,
264+ parser_context . modal_operation ,
236265 ) ) ;
237266 }
238267 }
@@ -438,7 +467,7 @@ fn parse_line<T: Read>(
438467 'X' | 'Y' => Ok ( vec ! [ parse_interpolate_move_or_flash(
439468 line,
440469 gerber_doc,
441- & mut linechars ,
470+ parser_context . modal_operation ,
442471 ) ] ) ,
443472 'D' => {
444473 // select aperture D<num>* (where num >= 10) or command where num < 10
@@ -644,17 +673,31 @@ fn parse_axis_select(line: &str) -> Result<Command, ContentError> {
644673fn parse_interpolate_move_or_flash (
645674 line : & str ,
646675 gerber_doc : & mut GerberDoc ,
647- linechars : & mut Chars ,
676+ modal : ModalOperationMode ,
648677) -> Result < Command , ContentError > {
649- linechars. next_back ( ) ;
650- match linechars
651- . next_back ( )
652- . ok_or ( ContentError :: UnknownCommand { } ) ?
653- {
654- '1' => parse_interpolation ( line, gerber_doc) , // D01
655- '2' => parse_move_or_flash ( line, gerber_doc, false ) , // D02
656- '3' => parse_move_or_flash ( line, gerber_doc, true ) , // D03
657- _ => Err ( ContentError :: UnknownCommand { } ) ,
678+ // A trailing `D01`/`D02`/`D03` (or single-digit `D1`/`D2`/`D3`) is an explicit op code.
679+ // Coordinates are digits and signs only, so a 'D' preceding the final 1/2/3 is unambiguous.
680+ // Absence means the deprecated modal-D01 form (gerber spec 8.3).
681+ let bytes = line. strip_suffix ( '*' ) . unwrap_or ( line) . as_bytes ( ) ;
682+ let dcode = match bytes. last ( ) . copied ( ) {
683+ Some ( d @ ( b'1' | b'2' | b'3' ) ) if bytes. len ( ) >= 2 => {
684+ let len = bytes. len ( ) ;
685+ let has_dcode = bytes[ len - 2 ] == b'D'
686+ || ( len >= 3 && bytes[ len - 2 ] == b'0' && bytes[ len - 3 ] == b'D' ) ;
687+ has_dcode. then_some ( d)
688+ }
689+ _ => None ,
690+ } ;
691+
692+ match dcode {
693+ Some ( b'1' ) => parse_interpolation ( line, gerber_doc) , // D01
694+ Some ( b'2' ) => parse_move_or_flash ( line, gerber_doc, false ) , // D02
695+ Some ( b'3' ) => parse_move_or_flash ( line, gerber_doc, true ) , // D03
696+ Some ( _) => unreachable ! ( ) ,
697+ // Deprecated modal D01 (gerber spec 8.3): `RE_INTERPOLATION` accepts the
698+ // suffix-less form, so dispatch straight to it without re-allocating.
699+ None if modal == ModalOperationMode :: Interpolate => parse_interpolation ( line, gerber_doc) ,
700+ None => Err ( ContentError :: CoordinateDataWithoutOperationCode ) ,
658701 }
659702}
660703
0 commit comments