File Coverage

blib/lib/Bio/Regexp.pm
Criterion Covered Total %
statement 95 103 92.2
branch 24 36 66.6
condition 7 12 58.3
subroutine 16 22 72.7
pod 0 14 0.0
total 142 187 75.9


line stmt bran cond sub pod time code
1             package Bio::Regexp;
2              
3             our $VERSION = '0.101';
4              
5 1     1   52108 use v5.10;
  1         5  
  1         48  
6 1     1   1012 use common::sense;
  1         9  
  1         6  
7              
8 1     1   1022 use Data::Alias;
  1         10769  
  1         109  
9 1     1   927 use Regexp::Exhaustive;
  1         1372  
  1         45  
10              
11 1     1   560 use Bio::Regexp::AST;
  1         4  
  1         1167  
12              
13              
14              
15              
16 0     0 0 0 sub dna { _arg($_[0], 'type', 'dna') }
17 1     1 0 6 sub rna { _arg($_[0], 'type', 'rna') }
18 0     0 0 0 sub protein { _arg($_[0], 'type', 'protein') }
19              
20 3     3 0 12 sub circular { _arg($_[0], 'circular', 1) }
21 0     0 0 0 sub linear { _arg($_[0], 'circular', 0) }
22              
23 5     5 0 20 sub single_stranded { _arg($_[0], 'strands', 1) }
24 0     0 0 0 sub double_stranded { _arg($_[0], 'strands', 2) }
25              
26 0     0 0 0 sub strict_thymine_uracil { _arg($_[0], 'strict_thymine_uracil', 1) }
27 0     0 0 0 sub strict_case { _arg($_[0], 'strict_case', 1) }
28              
29 1     1 0 5 sub no_substr { _arg($_[0], 'no_substr', 1) }
30              
31              
32             sub _arg_defaults {
33 16     16   32 my ($self) = @_;
34              
35 16   50     83 $self->{type} //= 'dna';
36              
37 16 50       49 if ($self->{type} eq 'dna') {
    0          
    0          
38 16   100     85 $self->{arg}->{strands} //= 2;
39             } elsif ($self->{type} eq 'rna') {
40 0   0     0 $self->{arg}->{strands} //= 1;
41             } elsif ($self->{type} eq 'protein') {
42 0         0 die "protein search not implemented";
43             }
44             }
45              
46              
47              
48              
49              
50             sub new {
51 16     16 0 58 my ($class, @args) = @_;
52              
53 16         37 my $self = {};
54 16         40 bless $self, $class;
55              
56 16         72 return $self;
57             }
58              
59              
60              
61             sub add {
62 17     17 0 32 my ($self, $regexp) = @_;
63              
64 17 50       59 die "Can't add new regexp because regexp has already been compiled" if $self->{compiled_regexp};
65              
66 17         22 push @{ $self->{regexps} }, $regexp;
  17         66  
67              
68 17         137 return $self;
69             }
70              
71              
72              
73             sub compile {
74 16     16 0 27 my ($self) = @_;
75              
76 16 50       51 return if $self->{compiled_regexp};
77              
78 16         47 $self->_arg_defaults;
79              
80 16         20 my $regexp_index = 0;
81 16         19 my @regexp_fragments;
82              
83 16         21 foreach my $regexp (@{ $self->{regexps} }) {
  16         39  
84             ## Parse
85              
86 17         125 my $ast = Bio::Regexp::AST->new($regexp, $self->{type}, $self->{arg});
87              
88             ## Compute meta data
89              
90 17         67 my ($min, $max) = $ast->compute_min_max;
91              
92 17 100 66     82 $self->{min} = $min if !defined $self->{min} || $min < $self->{min};
93 17 100 66     58 $self->{max} = $max if !defined $self->{max} || $max > $self->{max};
94              
95             ## Main "sense" strand
96              
97 17         64 my $rendered = $ast->render;
98              
99 17         50 push @regexp_fragments, "$rendered(?{ $regexp_index })";
100 17         21 $regexp_index++;
101              
102 17         35 my $component = { regexp => $regexp, };
103              
104 17 100       59 $component->{strand} = 1 if $self->{arg}->{strands} == 2;
105              
106 17         18 push @{ $self->{components} }, $component;
  17         38  
107              
108             ## Reverse complement strand
109              
110 17 100       85 if ($self->{arg}->{strands} == 2) {
111 11         41 $ast->reverse_complement;
112 11         36 $rendered = $ast->render;
113              
114 11         30 push @regexp_fragments, "$rendered(?{ $regexp_index })";
115 11         14 $regexp_index++;
116              
117 11         30 my $component = { regexp => $regexp, strand => 2, };
118              
119 11         15 push @{ $self->{components} }, $component;
  11         83  
120             }
121             }
122              
123 16 50       145 my $compiled_regexp = ($self->{arg}->{strict_case} ? '' : '(?i)') .
    100          
124             '(' .
125             ($self->{arg}->{no_substr} ? '?:' : '') .
126             join('|', @regexp_fragments) .
127             ')';
128              
129             {
130 1     1   12 use re 'eval';
  1         2  
  1         902  
  16         19  
131 16         2586 $self->{compiled_regexp} = qr{$compiled_regexp};
132             }
133              
134 16         58 return $self;
135             }
136              
137              
138              
139             sub match {
140 16     16 0 147 alias my ($self, $input, $callback) = @_;
141              
142 16         46 $self->compile;
143              
144 16         28 my @output;
145              
146 16         79 my @matches = Regexp::Exhaustive::exhaustive($input => $self->{compiled_regexp},
147             qw[ $1 @- @+ $^R ]);
148              
149 16         991 foreach my $match (@matches) {
150 22         139 my $element = {
151             match => $match->[0],
152             start => $match->[1]->[0],
153             end => $match->[2]->[0],
154 22         57 %{ $self->{components}->[$match->[3]] },
155             };
156              
157 22         62 push @output, $element;
158             }
159              
160              
161             ## Check circular overlap
162              
163 16 100       56 if ($self->{arg}->{circular}) {
164 3         9 my $start = length($input) - $self->{max} + 1;
165 3 50       9 $start = 1 if $start < 1;
166              
167 3         5 my $end = $self->{max} - 1;
168 3 50       13 $end = 0 if $end < 0;
169              
170 3         16 my $input_overlap = substr($input, $start) . substr($input, 0, $end);
171              
172 3         12 my @matches_overlap = Regexp::Exhaustive::exhaustive($input_overlap => $self->{compiled_regexp},
173             qw[ $1 @- @+ $^R ]);
174              
175 3         61 foreach my $match (@matches_overlap) {
176 3         23 my $element = {
177             match => $match->[0],
178             start => $match->[1]->[0],
179             end => $match->[2]->[0],
180 3         9 %{ $self->{components}->[$match->[3]] },
181             };
182              
183 3         8 $element->{start} += $start;
184 3         5 $element->{end} += $start;
185              
186 3         11 push @output, $element;
187             }
188             }
189              
190              
191             ## Re-order reverse complement start/end
192              
193 16 100       57 if ($self->{arg}->{strands} == 2) {
194 11         20 foreach my $match (@output) {
195 15 100       63 ($match->{start}, $match->{end}) = ($match->{end}, $match->{start})
196             if $match->{strand} == 2;
197             }
198             }
199              
200 16         81 return @output;
201             }
202              
203              
204              
205              
206              
207              
208             sub _arg {
209 10     10   21 my ($self, $arg, $val) = @_;
210              
211 10 50       34 die "Can't set $arg to $val because it was already set to $val" if exists $self->{$arg};
212 10 50       31 die "Can't set $arg to $val because regexp has already been compiled" if $self->{compiled_regexp};
213              
214 10         33 $self->{arg}->{$arg} = $val;
215              
216 10         67 return $self;
217             }
218              
219              
220             1;
221              
222              
223              
224             __END__