File Coverage

blib/lib/Regexp/English.pm
Criterion Covered Total %
statement 113 113 100.0
branch 32 32 100.0
condition 2 2 100.0
subroutine 24 24 100.0
pod 8 8 100.0
total 179 179 100.0


line stmt bran cond sub pod time code
1             package Regexp::English;
2              
3 7     7   129354 use strict;
  7         18  
  7         417  
4 7     7   37 use warnings;
  7         17  
  7         300  
5              
6 7     7   48 use Exporter 'import';
  7         25  
  7         269  
7 7     7   41 use vars qw( @export @EXPORT_OK %EXPORT_TAGS $VERSION );
  7         11  
  7         807  
8              
9             $VERSION = '1.01';
10              
11 7     7   11302 use overload '""' => \&compile;
  7         7795  
  7         78  
12 7     7   559 use Scalar::Util 'blessed';
  7         15  
  7         1250  
13              
14             # REGEX: storage for the raw regex
15             # STORE: storage for bound references (see remember())
16             # STACK: used to nest groupings
17 7     7   46 use constant REGEX => 0;
  7         15  
  7         646  
18 7     7   45 use constant STORE => 1;
  7         22  
  7         327  
19 7     7   35 use constant STACK => 2;
  7         14  
  7         6039  
20              
21             # the key is the name of the method to create
22             # symbol is the regex token this represents
23             # plural is the name of the shortcut method for $symbol+, i.e. \w+
24             # non is the name of the negated token, its shortcut, and a plural, if needed
25             my %chars = (
26             word_char =>
27             {
28             symbol => '\w',
29             plural => 'word_chars',
30             non => [ 'non_word_char', '\W', 'non_word_chars' ],
31             },
32             whitespace_char =>
33             {
34             symbol => '\s',
35             plural => 'whitespace_chars',
36             non => [ 'non_whitespace_char', '\S', 'non_whitespace_chars' ],
37             },
38             digit =>
39             {
40             symbol => '\d',
41             plural => 'digits',
42             non => [ 'non_digit', '\D', 'non_digits' ],
43             },
44             word_boundary =>
45             {
46             symbol => '\b',
47             non => [ 'non_word_boundary', '\B' ],
48             },
49             end_of_string =>
50             {
51             symbol => '\Z',
52             non => [ 'very_end_of_string', '\z' ],
53             },
54             beginning_of_string => { symbol => '\A', },
55             end_of_previous_match => { symbol => '\G', },
56              
57             # XXX: non for these ?
58             tab =>
59             {
60             symbol => '\t',
61             plural => 'tabs',
62             non => [ 'non_tab', '[^\t]' ],
63             },
64              
65             # implies /s modifier
66             newline =>
67             {
68             symbol => '\n',
69             plural => 'newlines',
70             non => [ 'non_newline', '(?s)[^\n]' ],
71             },
72              
73             carriage_return =>
74             {
75             symbol => '\r',
76             plural => 'carriage_returns',
77             non => [ 'non_carriage_return', '[^\r]' ],
78             },
79              
80             form_feed =>
81             {
82             symbol => '\f',
83             plural => 'form_feeds',
84             non => [ 'non_form_feed', '[^\f]' ],
85             },
86              
87             'alarm' =>
88             {
89             symbol => '\a',
90             plural => 'alarms',
91             non => [ 'non_alarm', '[^\a]' ],
92             },
93             escape =>
94             {
95             symbol => '\e',
96             plural => 'escapes',
97             non => [ 'non_escape', '[^\e]' ],
98             },
99             start_of_line => { symbol => '^', },
100             end_of_line => { symbol => '$', },
101             );
102              
103             sub _chars
104             {
105 266     266   354 my $symbol = shift;
106              
107             return sub
108             {
109             # cannot use $_[0] here, as it trips the overload
110             # that can mess with remember/end groups
111 95 100   95   4011 return $symbol unless @_;
112              
113 77         102 my $self = shift;
114 77 100       505 $self = $self->new() unless blessed( $self );
115              
116 77         360 $self->[REGEX] .= $symbol;
117 77         278 return $self;
118 266         2493 };
119             }
120              
121             my @char_tags;
122              
123             for my $char ( keys %chars )
124             {
125             push @char_tags, $char;
126             _install( $char, _chars( $chars{$char}{symbol} ) );
127              
128             if ( $chars{$char}{plural} )
129             {
130             _install( $chars{$char}{plural}, _chars( $chars{$char}{symbol} . '+' ));
131             push @char_tags, $chars{$char}{plural};
132             }
133              
134             if ( $chars{$char}{non} )
135             {
136             my ( $nonname, $symbol, $pluralname ) = @{ $chars{$char}{non} };
137             _install( $nonname, _chars($symbol) );
138             push @char_tags, $nonname;
139             if ($pluralname)
140             {
141             _install( $pluralname, _chars( $symbol . '+' ) );
142             push @char_tags, $pluralname;
143             }
144             }
145             }
146              
147             # tested in t/quantifiers
148             # XXX:
149             # the syntax for minimal/optional is slightly awkward
150             my %quantifiers =
151             (
152             zero_or_more => '*',
153             multiple => '+',
154             minimal => '?',
155             optional => '?',
156             );
157              
158             for my $quantifier ( keys %quantifiers )
159             {
160             _install( $quantifier,
161             _standard( '(?:', '', $quantifiers{$quantifier} . ')' ), 1 );
162             }
163              
164             # tested in t/groupings
165             my %groupings =
166             (
167             after => '(?<=',
168             group => '(?:',
169             comment => '(?#',
170             not_after => '(?
171             followed_by => '(?=',
172             not_followed_by => '(?!',
173             );
174              
175             for my $group ( keys %groupings )
176             {
177             _install( $group, _standard( $groupings{$group}, '', '' ), 1 );
178             }
179              
180             sub _standard
181             {
182 77     77   128 my ( $group, $sep, $symbol ) = @_;
183              
184 77   100     256 $symbol ||= ')';
185              
186             return sub
187             {
188 37 100   37   61 if ( eval { $_[0]->isa( 'Regexp::English' ) } )
  37         276  
189             {
190 17         29 my $self = shift;
191 17         73 $self->[REGEX] .= $group;
192              
193 17 100       47 if (@_)
194             {
195 10         31 $self->[REGEX] .= join( "$sep", @_ ) . $symbol;
196             }
197             else
198             {
199 7         11 push @{ $self->[STACK] }, $symbol;
  7         18  
200             }
201 17         66 return $self;
202             }
203 20         104 return $group . join( $sep, @_ ) . $symbol;
204 77         422 };
205             }
206              
207             # can't be used with standard because of quotemeta()
208             sub literal
209             {
210 16     16 1 25 my $self = shift;
211 16         73 $self->[REGEX] .= quotemeta( +shift );
212 16         61 return $self;
213             }
214              
215             sub _install
216             {
217 350     350   490 my ( $name, $sub, $export ) = @_;
218 7     7   89 no strict 'refs';
  7         13  
  7         8878  
219 350         586 *{$name} = $sub;
  350         1594  
220 350 100       770 push @export, "&$name" if $export;
221 350         1087 push @EXPORT_OK, "&$name";
222             }
223              
224             _install(
225             'or',
226             sub {
227 6 100   6   10 if ( eval { $_[0]->isa( 'Regexp::English' ) } )
  6         53  
228             {
229 4         6 my $self = shift;
230 4 100       10 if (@_)
231             {
232 2         9 $self->[REGEX] .= '(?:' . join( '|', @_ ) . ')';
233             }
234             else
235             {
236 2         8 $self->[REGEX] .= '|';
237             }
238 4         16 return $self;
239             }
240 2         16 return '(?:' . join( '|', @_ ) . ')';
241             },
242             1
243             );
244              
245             _install( 'class', _standard( '[', '', ']' ), 1 );
246              
247             # XXX - not()
248              
249             sub remember
250             {
251 29     29 1 46 my $self = shift;
252 29 100       131 $self = $self->new() unless blessed( $self );
253              
254             # the first element may be a reference, so stick it in STORE
255 29 100       90 if ( ref( $_[0] ) eq 'SCALAR' )
256             {
257 11         15 push @{ $self->[STORE] }, shift;
  11         26  
258             }
259              
260             # if there are other arguments, add them to REGEX
261 29 100       85 if (@_)
262             {
263 17         45 $self->[REGEX] .= '(' . join( '', @_ ) . ')';
264              
265             # otherwise, this is the opening op of a multi-call remember block
266             # XXX: might store calling line for verbose debugging
267             }
268             else
269             {
270 12         24 $self->[REGEX] .= '(';
271 12         19 push @{ $self->[STACK] }, ')';
  12         31  
272             }
273              
274 29         215 return $self;
275             }
276              
277             sub end
278             {
279 16     16 1 29 my ( $self, $levels ) = @_;
280 16 100       44 $levels = 1 unless defined $levels;
281              
282 16 100       19 unless ( @{ $self->[STACK] } )
  16         47  
283             {
284 1         10 require Carp;
285 1         229 Carp::confess( 'end() called without remember()' );
286             }
287              
288 15         44 $self->[REGEX] .= pop @{ $self->[STACK] } for 1 .. $levels;
  18         56  
289              
290 15         65 return $self;
291             }
292              
293             sub new
294             {
295 53     53 1 5271 bless( [ '', [], [] ], $_[0] );
296             }
297              
298             sub match
299             {
300 118     118 1 13174 my $self = shift;
301 118         275 $self->[REGEX] = $self->compile();
302              
303 118 100       227 if ( @{ $self->[STORE] } )
  118         298  
304             {
305 8         65 return $self->capture( $_[0] =~ $self->[REGEX] );
306             }
307             else
308             {
309 110 100       398 if ( wantarray() )
310             {
311 10         107 return $_[0] =~ $self->[REGEX];
312             }
313             else
314             {
315 100         818 return ( $_[0] =~ $self->[REGEX] )[0];
316             }
317             }
318             }
319              
320             sub capture
321             {
322 8     8 1 15 my $self = shift;
323              
324 8         13 for my $ref ( @{ $self->[STORE] } )
  8         19  
325             {
326 18         40 $$ref = shift @_;
327             }
328 8 100       24 if ( wantarray() )
329             {
330 2         4 return map { $$_ } @{ $self->[STORE] };
  6         22  
  2         6  
331             }
332             else
333             {
334 6         12 return ${ ${ $self->[STORE] }[0] };
  6         7  
  6         34  
335             }
336             }
337              
338             sub compile
339             {
340 121     121 1 136 my $self = shift;
341              
342 121 100       158 if ( my $num = @{ $self->[STACK] } )
  121         568  
343             {
344 8         39 $self->end($num);
345             }
346 121         1717 return qr/$self->[REGEX]/;
347             }
348              
349             sub debug
350             {
351 1     1 1 5 my $self = shift;
352 1         7 return $self->[REGEX];
353             }
354              
355             %EXPORT_TAGS =
356             (
357             all => [ @char_tags, @export ],
358             chars => \@char_tags,
359             standard => \@export,
360             );
361              
362             1;
363              
364             __END__