File Coverage

blib/lib/Bio/Regexp/AST.pm
Criterion Covered Total %
statement 86 95 90.5
branch 36 50 72.0
condition 4 15 26.6
subroutine 8 8 100.0
pod 0 5 0.0
total 134 173 77.4


line stmt bran cond sub pod time code
1             package Bio::Regexp::AST;
2              
3 1     1   5 use common::sense;
  1         1  
  1         8  
4              
5 1     1   890 use List::MoreUtils;
  1         1333  
  1         63  
6              
7             my $parser;
8              
9             {
10 1     1   2031 use Regexp::Grammars;
  1         26103  
  1         10  
11              
12             $parser = qr{
13             ## MAIN
14              
15            
16              
17             ## GRAMMAR
18              
19            
20             ^
21             <[element]>*
22             $
23              
24            
25             ? |
26             ?
27              
28            
29             [a-zA-Z]
30              
31            
32             \[ ? <[literal]>+ \]
33              
34            
35             \^
36              
37            
38             \{
39             (?:
40             |
41             ,
42             )
43             \} |
44             \?
45             }xs;
46             }
47              
48              
49             sub new {
50 17     17 0 34 my ($class, $regexp, $type, $arg) = @_;
51              
52 17         81 my $self = {
53             regexp => $regexp,
54             type => $type,
55             strict_thymine_uracil => $arg->{strict_thymine_uracil},
56             };
57              
58 17         42 bless $self, $class;
59              
60 17         615 $regexp =~ $parser;
61              
62 17         44 my $parsed = \%/;
63              
64 17         20 my @components;
65              
66 17         20 foreach my $element (@{ $parsed->{regexp}->{element} }) {
  17         53  
67 54         79 my $component = {};
68              
69 54 100       114 if ($element->{literal}) {
    50          
70 49         145 $component->{chars} = [ $element->{literal} ];
71             } elsif ($element->{charclass}) {
72 5         12 $component->{chars} = $element->{charclass}->{literal};
73 5 100       18 $component->{negate} = 1 if $element->{charclass}->{negate_charclass};
74             } else {
75 0         0 die "unknown element type";
76             }
77              
78 54 100       112 if ($element->{repeat}) {
79 2         6 $component->{min} = $element->{repeat}->{min};
80 2         6 $component->{max} = $element->{repeat}->{max};
81             } else {
82 52         80 $component->{min} = 1;
83 52         75 $component->{max} = 1;
84             }
85              
86 54         110 push @components, $component;
87             }
88              
89 17         49 $self->{components} = \@components;
90              
91 17 50 33     58 if ($type eq 'dna' || $type eq 'rna') {
92 17         70 $self->normalize_dna_rna;
93             } else {
94 0         0 die "protein not impl";
95             }
96              
97 17         75 return $self;
98             }
99              
100              
101             sub compute_min_max {
102 17     17 0 25 my ($self) = @_;
103              
104 17         29 my $min = my $max = 0;
105              
106 17         21 for my $component (@{ $self->{components} }) {
  17         41  
107 54         66 $min += $component->{min};
108 54         86 $max += $component->{max};
109             }
110              
111 17         56 return ($min, $max);
112             }
113              
114              
115             my $iupac_lookup = {
116             R => [ qw/A G/ ],
117             Y => [ qw/C T/ ],
118             W => [ qw/A T/ ],
119             S => [ qw/C G/ ],
120             M => [ qw/A C/ ],
121             K => [ qw/G T/ ],
122             H => [ qw/A C T/ ],
123             B => [ qw/C G T/ ],
124             V => [ qw/A C G/ ],
125             D => [ qw/A G T/ ],
126             N => [ qw/A C G T/ ],
127             };
128              
129              
130              
131             sub normalize_dna_rna {
132 17     17 0 21 my ($self) = @_;
133              
134 17         22 foreach my $component (@{ $self->{components} }) {
  17         37  
135 54         67 my @chars = @{ $component->{chars} };
  54         162  
136              
137 54 50       140 if ($self->{strict_thymine_uracil}) {
138 0         0 die "U in DNA pattern and strict_thymine_uracil specified"
139 0 0 0     0 if $self->{type} eq 'dna' && grep { $_ eq 'U' } @chars;
140              
141 0         0 die "T in RNA pattern and strict_thymine_uracil specified"
142 0 0 0     0 if $self->{type} eq 'rna' && grep { $_ eq 'T' } @chars;
143             }
144              
145             ## Temporarily normalize U to T
146 54 100       71 @chars = map { $_ eq 'U' ? 'T' : $_ } @chars;
  56         219  
147              
148             ## Expand IUPAC codes
149 54 100       97 @chars = map { @{ $iupac_lookup->{$_} || [$_] } } @chars;
  56         57  
  56         365  
150              
151             ## Remove uniques
152 54         263 @chars = List::MoreUtils::uniq(@chars);
153              
154             ## Negate
155 54 100       185 if ($component->{negate}) {
156 4         6 my @negated;
157              
158 4         7 foreach my $base (qw/ A T C G /) {
159 16 100       18 push @negated, $base unless grep { $_ eq $base } @chars;
  48         103  
160             }
161              
162 4 50       12 die "can't negate all encompassing character class" if !@negated;
163              
164 4         10 @chars = @negated;
165             }
166              
167 54         167 $component->{chars} = \@chars;
168             }
169             }
170              
171              
172              
173             sub reverse_complement {
174 11     11 0 19 my ($self) = @_;
175              
176 11         15 $self->{components} = [ reverse @{ $self->{components} } ];
  11         36  
177              
178 11         19 foreach my $component (@{ $self->{components} }) {
  11         23  
179 39         80 my @chars = @{ $component->{chars} };
  39         98  
180              
181 39 50       66 @chars = map { $_ eq 'A' ? 'T' :
  51 100       191  
    100          
    100          
182             $_ eq 'T' ? 'A' :
183             $_ eq 'C' ? 'G' :
184             $_ eq 'G' ? 'C' :
185             die "unrecognised base: $_"
186             } @chars;
187              
188 39         121 $component->{chars} = \@chars;
189             }
190             }
191              
192              
193              
194             sub render {
195 28     28 0 43 my ($self) = @_;
196              
197 28         38 my $output = '';
198              
199 28         34 foreach my $component (@{ $self->{components} }) {
  28         58  
200 93         97 my @chars = @{ $component->{chars} };
  93         214  
201              
202             ## Re-normalize T to U
203 93 50       222 if ($self->{type} eq 'rna') {
204 0 0       0 @chars = map { $_ eq 'T' ? 'U' : $_ } @chars;
  0         0  
205             }
206              
207             ## Support T and U unless strict
208 93 50       188 if (!$self->{strict_thymine_uracil}) {
209 93 100 66     114 @chars = map { $_ eq 'T' || $_ eq 'U' ? ('T', 'U') : $_ } @chars;
  118         610  
210             }
211              
212 93 100       201 if (@chars == 1) {
213 55         79 $output .= $chars[0];
214             } else {
215 38         89 $output .= '[' . join('', @chars) . ']';
216             }
217              
218 93 100 33     192 if ($component->{min} == $component->{max}) {
    50          
219 92 100       272 $output .= "{$component->{min}}" unless $component->{min} == 1;
220             } elsif ($component->{min} == 0 && $component->{max} == 1) {
221 0         0 $output .= "?";
222             } else {
223 1         5 $output .= "{$component->{min},$component->{max}}";
224             }
225             }
226              
227 28         94 return $output;
228             }
229              
230              
231              
232             1;