File Coverage

blib/lib/Chemistry/OpenSMILES/Parser.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             ####################################################################
2             #
3             # This file was generated using Parse::Yapp version 1.21.
4             #
5             # Don't edit this file, use source file instead.
6             #
7             # ANY CHANGE MADE HERE WILL BE LOST !
8             #
9             ####################################################################
10             package Chemistry::OpenSMILES::Parser;
11 23     23   12377 use vars qw ( @ISA );
  23         122  
  23         1664  
12 23     23   165 use strict;
  23         56  
  23         808  
13              
14             @ISA= qw ( Parse::Yapp::Driver );
15 23     23   11970 use Parse::Yapp::Driver;
  23         53992  
  23         8278  
16              
17             #line 3 "lib/Chemistry/OpenSMILES/Parser.yp"
18              
19              
20             use warnings;
21             use 5.0100;
22              
23             use Chemistry::OpenSMILES qw(
24             %bond_symbol_to_order
25             is_aromatic
26             is_chiral
27             %normal_valence
28             toggle_cistrans
29             );
30             use Graph::Undirected;
31             use List::Util qw( any sum );
32              
33              
34              
35             sub new {
36             my($class)=shift;
37             ref($class)
38             and $class=ref($class);
39              
40             my($self)=$class->SUPER::new( yyversion => '1.21',
41             yystates =>
42             [
43             {#State 0
44             ACTIONS => {
45             'atom' => 3
46             },
47             GOTOS => {
48             'chain' => 2,
49             'smiles' => 1
50             }
51             },
52             {#State 1
53             ACTIONS => {
54             '' => 4
55             }
56             },
57             {#State 2
58             ACTIONS => {
59             "-" => 13,
60             "." => 12,
61             "=" => 14,
62             'atom' => 15,
63             "#" => 16,
64             "(" => 6,
65             "/" => 7,
66             ":" => 9,
67             'ringbond' => 8,
68             "\\" => 11,
69             "\$" => 10
70             },
71             DEFAULT => -1,
72             GOTOS => {
73             'bond' => 5
74             }
75             },
76             {#State 3
77             DEFAULT => -2
78             },
79             {#State 4
80             DEFAULT => 0
81             },
82             {#State 5
83             ACTIONS => {
84             'atom' => 18,
85             'ringbond' => 17
86             }
87             },
88             {#State 6
89             ACTIONS => {
90             "/" => 7,
91             ":" => 9,
92             "\\" => 11,
93             "\$" => 10,
94             "#" => 16,
95             'atom' => 3,
96             "-" => 13,
97             "." => 21,
98             "=" => 14
99             },
100             GOTOS => {
101             'bond' => 19,
102             'chain' => 20
103             }
104             },
105             {#State 7
106             DEFAULT => -16
107             },
108             {#State 8
109             DEFAULT => -9
110             },
111             {#State 9
112             DEFAULT => -15
113             },
114             {#State 10
115             DEFAULT => -14
116             },
117             {#State 11
118             DEFAULT => -17
119             },
120             {#State 12
121             ACTIONS => {
122             'atom' => 22
123             }
124             },
125             {#State 13
126             DEFAULT => -11
127             },
128             {#State 14
129             DEFAULT => -12
130             },
131             {#State 15
132             DEFAULT => -3
133             },
134             {#State 16
135             DEFAULT => -13
136             },
137             {#State 17
138             DEFAULT => -10
139             },
140             {#State 18
141             DEFAULT => -4
142             },
143             {#State 19
144             ACTIONS => {
145             'atom' => 3
146             },
147             GOTOS => {
148             'chain' => 23
149             }
150             },
151             {#State 20
152             ACTIONS => {
153             ":" => 9,
154             'ringbond' => 8,
155             "/" => 7,
156             "\$" => 10,
157             "\\" => 11,
158             "(" => 6,
159             'atom' => 15,
160             "#" => 16,
161             "-" => 13,
162             ")" => 24,
163             "." => 12,
164             "=" => 14
165             },
166             GOTOS => {
167             'bond' => 5
168             }
169             },
170             {#State 21
171             ACTIONS => {
172             'atom' => 3
173             },
174             GOTOS => {
175             'chain' => 25
176             }
177             },
178             {#State 22
179             DEFAULT => -5
180             },
181             {#State 23
182             ACTIONS => {
183             "\$" => 10,
184             "\\" => 11,
185             'ringbond' => 8,
186             ":" => 9,
187             "/" => 7,
188             "(" => 6,
189             "#" => 16,
190             'atom' => 15,
191             "=" => 14,
192             "." => 12,
193             ")" => 26,
194             "-" => 13
195             },
196             GOTOS => {
197             'bond' => 5
198             }
199             },
200             {#State 24
201             DEFAULT => -6
202             },
203             {#State 25
204             ACTIONS => {
205             "#" => 16,
206             'atom' => 15,
207             ")" => 27,
208             "-" => 13,
209             "." => 12,
210             "=" => 14,
211             "/" => 7,
212             ":" => 9,
213             'ringbond' => 8,
214             "\$" => 10,
215             "\\" => 11,
216             "(" => 6
217             },
218             GOTOS => {
219             'bond' => 5
220             }
221             },
222             {#State 26
223             DEFAULT => -7
224             },
225             {#State 27
226             DEFAULT => -8
227             }
228             ],
229             yyrules =>
230             [
231             [#Rule 0
232             '$start', 2, undef
233             ],
234             [#Rule 1
235             'smiles', 1, undef
236             ],
237             [#Rule 2
238             'chain', 1,
239             sub
240             #line 29 "lib/Chemistry/OpenSMILES/Parser.yp"
241             {
242             my $g = Graph::Undirected->new( refvertexed => 1 );
243             $g->add_vertex( $_[1] );
244             push @{$_[0]->{USER}{GRAPHS}}, $g;
245              
246             $_[1]->{graph} = $g;
247             $_[1]->{index} = @{$_[0]->{USER}{GRAPHS}}-1;
248             $_[1]->{first_of_chain} = 1;
249              
250             return { first => $_[1],
251             last => $_[1] };
252             }
253             ],
254             [#Rule 3
255             'chain', 2,
256             sub
257             #line 42 "lib/Chemistry/OpenSMILES/Parser.yp"
258             {
259             $_[2]->{graph} = $_[1]->{last}{graph};
260             $_[2]->{index} = $_[1]->{last}{index};
261              
262             $_[2]->{graph}->add_edge( $_[1]->{last}, $_[2] );
263              
264             if( is_aromatic $_[1]->{last} && is_aromatic $_[2] ) {
265             $_[2]->{graph}->set_edge_attribute( $_[1]->{last},
266             $_[2],
267             'bond',
268             ':' );
269             }
270              
271             delete $_[2]->{first_of_chain};
272              
273             _push_chirality_neighbour( $_[1]->{last}, $_[2] );
274             _push_chirality_neighbour( $_[2], $_[1]->{last} );
275              
276             $_[1]->{last} = $_[2];
277              
278             return $_[1];
279             }
280             ],
281             [#Rule 4
282             'chain', 3,
283             sub
284             #line 65 "lib/Chemistry/OpenSMILES/Parser.yp"
285             {
286             $_[3]->{graph} = $_[1]->{last}{graph};
287             $_[3]->{index} = $_[1]->{last}{index};
288              
289             if( $_[2] ne '-' ) {
290             $_[3]->{graph}->set_edge_attribute( $_[1]->{last},
291             $_[3],
292             'bond',
293             $_[2] );
294             } else {
295             $_[3]->{graph}->add_edge( $_[1]->{last}, $_[3] );
296             }
297              
298             delete $_[3]->{first_of_chain};
299              
300             _push_chirality_neighbour( $_[1]->{last}, $_[3] );
301             _push_chirality_neighbour( $_[3], $_[1]->{last} );
302              
303             $_[1]->{last} = $_[3];
304              
305             return $_[1];
306             }
307             ],
308             [#Rule 5
309             'chain', 3,
310             sub
311             #line 88 "lib/Chemistry/OpenSMILES/Parser.yp"
312             {
313             my $g = Graph::Undirected->new( refvertexed => 1 );
314             $g->add_vertex( $_[3] );
315             push @{$_[0]->{USER}{GRAPHS}}, $g;
316              
317             $_[3]->{graph} = $g;
318             $_[3]->{index} = @{$_[0]->{USER}{GRAPHS}}-1;
319             $_[3]->{first_of_chain} = 1;
320              
321             return { first => $_[3],
322             last => $_[3] };
323             }
324             ],
325             [#Rule 6
326             'chain', 4,
327             sub
328             #line 101 "lib/Chemistry/OpenSMILES/Parser.yp"
329             {
330             if( $_[1]->{last}{index} != $_[3]->{first}{index} ) {
331             $_[0]->_merge_graphs( $_[1]->{last}{index},
332             $_[3]->{first}{index} );
333             }
334              
335             $_[1]->{last}{graph}->add_edge( $_[1]->{last}, $_[3]->{first} );
336              
337             if( is_aromatic $_[1]->{last} && is_aromatic $_[3]->{first} ) {
338             $_[1]->{last}{graph}->set_edge_attribute( $_[1]->{last},
339             $_[3]->{first},
340             'bond',
341             ':' );
342             }
343              
344             delete $_[3]->{first}{first_of_chain};
345              
346             _push_chirality_neighbour( $_[1]->{last}, $_[3]->{first} );
347             _unshift_chirality_neighbour( $_[3]->{first}, $_[1]->{last} );
348              
349             return $_[1];
350             }
351             ],
352             [#Rule 7
353             'chain', 5,
354             sub
355             #line 124 "lib/Chemistry/OpenSMILES/Parser.yp"
356             {
357             if( $_[1]->{last}{index} != $_[4]->{first}{index} ) {
358             $_[0]->_merge_graphs( $_[1]->{last}{index},
359             $_[4]->{first}{index} );
360             }
361              
362             if( $_[3] ne '-' ) {
363             $_[1]->{last}{graph}->set_edge_attribute( $_[1]->{last},
364             $_[4]->{first},
365             'bond',
366             $_[3] );
367             } else {
368             $_[1]->{last}{graph}->add_edge( $_[1]->{last},
369             $_[4]->{first} );
370             }
371              
372             delete $_[4]->{first}{first_of_chain};
373              
374             _push_chirality_neighbour( $_[1]->{last}, $_[4]->{first} );
375             _unshift_chirality_neighbour( $_[4]->{first}, $_[1]->{last} );
376              
377             return $_[1];
378             }
379             ],
380             [#Rule 8
381             'chain', 5, undef
382             ],
383             [#Rule 9
384             'chain', 2,
385             sub
386             #line 154 "lib/Chemistry/OpenSMILES/Parser.yp"
387             {
388             $_[0]->_add_ring_bond( $_[1]->{last}, $_[2] );
389             return $_[1];
390             }
391             ],
392             [#Rule 10
393             'chain', 3,
394             sub
395             #line 159 "lib/Chemistry/OpenSMILES/Parser.yp"
396             {
397             $_[0]->_add_ring_bond( $_[1]->{last}, $_[3], $_[2] );
398             return $_[1];
399             }
400             ],
401             [#Rule 11
402             'bond', 1, undef
403             ],
404             [#Rule 12
405             'bond', 1, undef
406             ],
407             [#Rule 13
408             'bond', 1, undef
409             ],
410             [#Rule 14
411             'bond', 1, undef
412             ],
413             [#Rule 15
414             'bond', 1, undef
415             ],
416             [#Rule 16
417             'bond', 1, undef
418             ],
419             [#Rule 17
420             'bond', 1, undef
421             ]
422             ],
423             @_);
424             bless($self,$class);
425             }
426              
427             #line 167 "lib/Chemistry/OpenSMILES/Parser.yp"
428              
429              
430             # Footer section
431              
432             sub _Error
433             {
434             my( $self ) = @_;
435             close $self->{USER}{FILEIN} if $self->{USER}{FILEIN};
436              
437             if( ${$self->{TOKEN}} eq '' &&
438             grep { defined $_ && !ref $_ && $_ eq '(' }
439             map { $_->[1] } @{$self->{STACK}} ) {
440             die "$0: syntax error: missing closing parenthesis.\n";
441             }
442              
443             if( ${$self->{TOKEN}} eq ')' ) {
444             die "$0: syntax error: unbalanced parentheses.\n";
445             }
446              
447             my $msg = "$0: syntax error at position $self->{USER}{CHARNO}";
448             if( $self->YYData->{INPUT} ) {
449             $self->YYData->{INPUT} =~ s/\n$//;
450             die "$msg: '" . $self->YYData->{INPUT} . "'.\n";
451             } else {
452             die "$msg.\n";
453             }
454             }
455              
456             sub _Lexer
457             {
458             my( $self ) = @_;
459              
460             # If the line is empty and the input is originating from the file,
461             # another line is read.
462             if( !$self->YYData->{INPUT} && $self->{USER}{FILEIN} ) {
463             my $filein = $self->{USER}{FILEIN};
464             $self->YYData->{INPUT} = <$filein>;
465             $self->{USER}{CHARNO} = 0;
466             }
467              
468             if( $self->YYData->{INPUT} =~ s/^(\s+)// ) {
469             $self->{USER}{CHARNO} += length $1;
470             }
471              
472             my $hcount_re = 'H[0-9]?';
473             if( defined $self->{USER}{OPTIONS}{max_hydrogen_count_digits} ) {
474             $hcount_re = sprintf 'H[0-9]{0,%d}',
475             $self->{USER}{OPTIONS}{max_hydrogen_count_digits};
476             }
477              
478             # Bracket atoms
479             if( $self->YYData->{INPUT} =~ s/^\[ (?[0-9]+)?
480             (?[A-Za-z][a-z]?|\*)
481             (?@(
482             (TH|AL)[12] |
483             SP [123] |
484             (TB|OH)[0-9]{1,2} |
485             @?
486             ))?
487             (? $hcount_re)?
488             (?--|\+\+|[-+][0-9]{0,2})?
489             (:(?[0-9]+))? \]//x ) {
490             my $atom = { %+, number => $self->{USER}{ATOMNO} };
491             $self->{USER}{ATOMNO} ++;
492             $self->{USER}{CHARNO} += length $&;
493              
494             if( $atom->{charge} ) {
495             $atom->{charge} =~ s/^([-+])$/${1}1/;
496             $atom->{charge} =~ s/^([-+])\1$/${1}2/;
497             $atom->{charge} = int $atom->{charge};
498             }
499              
500             if( $atom->{hcount} ) {
501             $atom->{hcount} =~ s/^H//;
502             $atom->{hcount} = $atom->{hcount} ? int $atom->{hcount} : 1;
503             } else {
504             $atom->{hcount} = 0;
505             }
506              
507             if( $atom->{isotope} ) {
508             $atom->{isotope} = int $atom->{isotope};
509             }
510              
511             # Atom class is an arbitrary number, 0 by default
512             $atom->{class} = exists $atom->{class} ? int $atom->{class} : 0;
513              
514             return ( 'atom', $atom );
515             }
516              
517             # Bracketless atoms
518             if( $self->YYData->{INPUT} =~ s/^(Br|Cl|[BCINOPSFbcnops*])// ) {
519             my $atom = { symbol => $1,
520             class => 0,
521             number => $self->{USER}{ATOMNO} };
522             $self->{USER}{ATOMNO} ++;
523             $self->{USER}{CHARNO} += length $&;
524             return ( 'atom', $atom );
525             }
526              
527             # Ring bonds
528             if( $self->YYData->{INPUT} =~ s/^%([0-9]{2})// ||
529             $self->YYData->{INPUT} =~ s/^([0-9])// ) {
530             $self->{USER}{CHARNO} += length $&;
531             return ( 'ringbond', int $1 );
532             }
533              
534             my $char = substr( $self->YYData->{INPUT}, 0, 1 );
535             if( $char ne '' ) {
536             $self->YYData->{INPUT} = substr( $self->YYData->{INPUT}, 1 );
537             }
538             $self->{USER}{CHARNO} ++;
539             return( $char, $char );
540             }
541              
542             sub parse
543             {
544             my( $self, $string, $options ) = @_;
545             $options = {} unless $options;
546              
547             $self->YYData->{INPUT} = $string;
548             $self->{USER}{GRAPHS} = [];
549             $self->{USER}{RINGBONDS} = {};
550             $self->{USER}{ATOMNO} = 0;
551             $self->{USER}{CHARNO} = 0;
552             $self->{USER}{OPTIONS} = $options;
553             $self->YYParse( yylex => \&_Lexer,
554             yyerror => \&_Error,
555             yydebug => $options->{debug} );
556              
557             if( scalar keys %{$self->{USER}{RINGBONDS}} ) {
558             die "$0: unclosed ring bond(s) detected: " .
559             join( ', ', sort { $a <=> $b } keys %{$self->{USER}{RINGBONDS}} ) .
560             ".\n";
561             }
562              
563             my @graphs = grep { defined } @{$self->{USER}{GRAPHS}};
564             for my $graph (@graphs) {
565             for my $atom (sort { $a->{number} <=> $b->{number} } $graph->vertices) {
566             delete $atom->{graph};
567             delete $atom->{index};
568             if( !$options->{raw} ) {
569             # Promote implicit hydrogen atoms into explicit ones
570             if( !exists $atom->{hcount} ) {
571             next if !exists $normal_valence{$atom->{symbol}};
572             my $degree = sum map { $_ ne ':' && exists $bond_symbol_to_order{$_} ? $bond_symbol_to_order{$_} : 1 }
573             map { $graph->has_edge_attribute( $atom, $_, 'bond' )
574             ? $graph->get_edge_attribute( $atom, $_, 'bond' )
575             : '-' }
576             $graph->neighbours( $atom );
577             $degree = 0 unless $degree;
578             my( $valence ) = grep { $degree <= $_ }
579             @{$normal_valence{$atom->{symbol}}};
580             next if !defined $valence;
581             $atom->{hcount} = $valence - $degree;
582             }
583             for (1..$atom->{hcount}) {
584             my $hydrogen = { symbol => 'H',
585             class => 0,
586             number => $self->{USER}{ATOMNO} };
587             $graph->add_edge( $atom, $hydrogen );
588             $self->{USER}{ATOMNO} ++;
589             if( $atom->{first_of_chain} ) {
590             _unshift_chirality_neighbour( $atom, $hydrogen );
591             } else {
592             _push_chirality_neighbour( $atom, $hydrogen );
593             }
594             }
595             delete $atom->{hcount};
596              
597             # Unify the representation of chirality
598             if( is_chiral $atom ) {
599             if( $atom->{chirality} =~ /^@@?$/ &&
600             $graph->degree( $atom ) == 2 ) {
601             $atom->{chirality} =~ s/@+/'@AL' . length $&/e;
602             }
603              
604             $atom->{chirality} =~ s/^\@TH1$/@/;
605             $atom->{chirality} =~ s/^\@TH2$/@@/;
606             }
607              
608             # Adjust chirality for centers having lone pairs
609             if( is_chiral $atom &&
610             $atom->{first_of_chain} &&
611             $atom->{chirality} =~ /^@@?$/ &&
612             $atom->{chirality_neighbours} &&
613             scalar @{$atom->{chirality_neighbours}} == 3 ) {
614             $atom->{chirality} = $atom->{chirality} eq '@' ? '@@' : '@';
615             }
616             }
617             delete $atom->{first_of_chain};
618             }
619             }
620              
621             return @graphs;
622             }
623              
624             sub _add_ring_bond
625             {
626             my( $self, $atom, $ring_bond, $bond ) = @_;
627             if( $self->{USER}{RINGBONDS}{$ring_bond} ) {
628             $self->_merge_graphs( $self->{USER}{RINGBONDS}{$ring_bond}{atom}{index},
629             $atom->{index} );
630              
631             if( $bond && $self->{USER}{RINGBONDS}{$ring_bond}{bond} &&
632             (($bond !~ /^[\\\/]$/ &&
633             $bond ne $self->{USER}{RINGBONDS}{$ring_bond}{bond}) ||
634             ($bond eq '\\' &&
635             $self->{USER}{RINGBONDS}{$ring_bond}{bond} ne '/') ||
636             ($bond eq '/' &&
637             $self->{USER}{RINGBONDS}{$ring_bond}{bond} ne '\\')) ) {
638             die "$0: ring bond types for ring bond $ring_bond do not match.\n";
639             }
640             ( $bond ) = grep { defined }
641             ( $self->{USER}{RINGBONDS}{$ring_bond}{bond}, $bond );
642              
643             if( $bond && $bond =~ /^[\\\/]$/ &&
644             !defined $self->{USER}{RINGBONDS}{$ring_bond}{bond} ) {
645             # If cis/trans marker is not specified when cis/trans bond is
646             # seen first, it has to be inverted:
647             $bond = toggle_cistrans $bond;
648             }
649              
650             my $ring_atom = $self->{USER}{RINGBONDS}{$ring_bond}{atom};
651             if( !$bond && is_aromatic $ring_atom && is_aromatic $atom ) {
652             $bond = ':';
653             }
654             if( $bond && $bond ne '-' ) {
655             $atom->{graph}->set_edge_attribute( $ring_atom,
656             $atom,
657             'bond',
658             $bond );
659             } else {
660             $atom->{graph}->add_edge( $ring_atom, $atom );
661             }
662             delete $self->{USER}{RINGBONDS}{$ring_bond};
663              
664             if( is_chiral $ring_atom && $ring_atom->{chirality_neighbours} ) {
665             my( $pos ) = grep { !ref $ring_atom->{chirality_neighbours}[$_] &&
666             $ring_atom->{chirality_neighbours}[$_] == $ring_bond }
667             0..$#{$ring_atom->{chirality_neighbours}};
668             $ring_atom->{chirality_neighbours}[$pos] = $atom if defined $pos;
669             }
670             _push_chirality_neighbour( $atom, $ring_atom );
671             } else {
672             $self->{USER}{RINGBONDS}{$ring_bond} =
673             { atom => $atom, $bond ? ( bond => $bond ) : () };
674              
675             # Record a placeholder for later addition of real chirality
676             # neighbour, which will be identified by the ring bond number
677             _push_chirality_neighbour( $atom, $ring_bond );
678             }
679             }
680              
681             sub _merge_graphs
682             {
683             my( $self, $index1, $index2 ) = @_;
684             return if $index1 == $index2;
685              
686             my $g1 = $self->{USER}{GRAPHS}[$index1];
687             my $g2 = $self->{USER}{GRAPHS}[$index2];
688              
689             for ($g2->vertices) {
690             $_->{graph} = $g1;
691             $_->{index} = $index1;
692             }
693             $g1->add_vertices( $g2->vertices );
694              
695             for ($g2->edges) {
696             my $attributes = $g2->get_edge_attributes( @$_ );
697             if( $attributes ) {
698             $g1->set_edge_attributes( @$_, $attributes );
699             } else {
700             $g1->add_edge( @$_ );
701             }
702             }
703              
704             $self->{USER}{GRAPHS}[$index2] = undef;
705             }
706              
707             sub _push_chirality_neighbour
708             {
709             my( $atom1, $atom2 ) = @_;
710             return unless is_chiral $atom1;
711             push @{$atom1->{chirality_neighbours}}, $atom2;
712             }
713              
714             sub _unshift_chirality_neighbour
715             {
716             my( $atom1, $atom2 ) = @_;
717             return unless is_chiral $atom1;
718             unshift @{$atom1->{chirality_neighbours}}, $atom2;
719             }
720              
721             1;
722              
723             1;