File Coverage

blib/lib/Regexp/Lexer.pm
Criterion Covered Total %
statement 76 77 98.7
branch 25 28 89.2
condition 8 10 80.0
subroutine 8 8 100.0
pod 1 1 100.0
total 118 124 95.1


line stmt bran cond sub pod time code
1             package Regexp::Lexer;
2 7     7   6516 use 5.010001;
  7         21  
  7         330  
3 7     7   29 use strict;
  7         7  
  7         164  
4 7     7   32 use warnings;
  7         8  
  7         136  
5 7     7   26 use B;
  7         7  
  7         279  
6 7     7   24 use Carp qw/croak/;
  7         9  
  7         351  
7 7     7   2604 use Regexp::Lexer::TokenType;
  7         8  
  7         199  
8 7     7   2859 use parent qw(Exporter);
  7         1881  
  7         29  
9              
10             our @EXPORT_OK = qw(tokenize);
11              
12             our $VERSION = "0.04";
13              
14             my %escapedSpecialChar = (
15             t => Regexp::Lexer::TokenType::EscapedTab,
16             n => Regexp::Lexer::TokenType::EscapedNewline,
17             r => Regexp::Lexer::TokenType::EscapedReturn,
18             f => Regexp::Lexer::TokenType::EscapedFormFeed,
19             F => Regexp::Lexer::TokenType::EscapedFoldcase,
20             a => Regexp::Lexer::TokenType::EscapedAlarm,
21             e => Regexp::Lexer::TokenType::EscapedEscape,
22             c => Regexp::Lexer::TokenType::EscapedControlChar,
23             x => Regexp::Lexer::TokenType::EscapedCharHex,
24             o => Regexp::Lexer::TokenType::EscapedCharOct,
25             0 => Regexp::Lexer::TokenType::EscapedCharOct,
26             l => Regexp::Lexer::TokenType::EscapedLowerNext,
27             u => Regexp::Lexer::TokenType::EscapedUpperNext,
28             L => Regexp::Lexer::TokenType::EscapedLowerUntil,
29             U => Regexp::Lexer::TokenType::EscapedUpperUntil,
30             Q => Regexp::Lexer::TokenType::EscapedQuoteMetaUntil,
31             E => Regexp::Lexer::TokenType::EscapedEnd,
32             w => Regexp::Lexer::TokenType::EscapedWordChar,
33             W => Regexp::Lexer::TokenType::EscapedNotWordChar,
34             s => Regexp::Lexer::TokenType::EscapedWhiteSpaceChar,
35             S => Regexp::Lexer::TokenType::EscapedNotWhiteSpaceChar,
36             d => Regexp::Lexer::TokenType::EscapedDigitChar,
37             D => Regexp::Lexer::TokenType::EscapedNotDigitChar,
38             p => Regexp::Lexer::TokenType::EscapedProp,
39             P => Regexp::Lexer::TokenType::EscapedNotProp,
40             X => Regexp::Lexer::TokenType::EscapedUnicodeExtendedChar,
41             C => Regexp::Lexer::TokenType::EscapedCChar,
42             1 => Regexp::Lexer::TokenType::EscapedBackRef,
43             2 => Regexp::Lexer::TokenType::EscapedBackRef,
44             3 => Regexp::Lexer::TokenType::EscapedBackRef,
45             4 => Regexp::Lexer::TokenType::EscapedBackRef,
46             5 => Regexp::Lexer::TokenType::EscapedBackRef,
47             6 => Regexp::Lexer::TokenType::EscapedBackRef,
48             7 => Regexp::Lexer::TokenType::EscapedBackRef,
49             8 => Regexp::Lexer::TokenType::EscapedBackRef,
50             9 => Regexp::Lexer::TokenType::EscapedBackRef,
51             g => Regexp::Lexer::TokenType::EscapedBackRef,
52             k => Regexp::Lexer::TokenType::EscapedBackRef,
53             K => Regexp::Lexer::TokenType::EscapedKeepStuff,
54             v => Regexp::Lexer::TokenType::EscapedVerticalWhitespace,
55             V => Regexp::Lexer::TokenType::EscapedNotVerticalWhitespace,
56             h => Regexp::Lexer::TokenType::EscapedHorizontalWhitespace,
57             H => Regexp::Lexer::TokenType::EscapedNotHorizontalWhitespace,
58             R => Regexp::Lexer::TokenType::EscapedLinebreak,
59             b => Regexp::Lexer::TokenType::EscapedWordBoundary,
60             B => Regexp::Lexer::TokenType::EscapedNotWordBoundary,
61             A => Regexp::Lexer::TokenType::EscapedBeginningOfString,
62             Z => Regexp::Lexer::TokenType::EscapedEndOfStringBeforeNewline,
63             z => Regexp::Lexer::TokenType::EscapedEndOfString,
64             G => Regexp::Lexer::TokenType::EscapedPos,
65             );
66              
67             my %specialChar = (
68             '.' => Regexp::Lexer::TokenType::MatchAny,
69             '|' => Regexp::Lexer::TokenType::Alternation,
70             '(' => Regexp::Lexer::TokenType::LeftParenthesis,
71             ')' => Regexp::Lexer::TokenType::RightParenthesis,
72             '[' => Regexp::Lexer::TokenType::LeftBracket,
73             ']' => Regexp::Lexer::TokenType::RightBracket,
74             '{' => Regexp::Lexer::TokenType::LeftBrace,
75             '}' => Regexp::Lexer::TokenType::RightBrace,
76             '<' => Regexp::Lexer::TokenType::LeftAngle,
77             '>' => Regexp::Lexer::TokenType::RightAngle,
78             '*' => Regexp::Lexer::TokenType::Asterisk,
79             '+' => Regexp::Lexer::TokenType::Plus,
80             '?' => Regexp::Lexer::TokenType::Question,
81             ',' => Regexp::Lexer::TokenType::Comma,
82             '-' => Regexp::Lexer::TokenType::Minus,
83             '$' => Regexp::Lexer::TokenType::ScalarSigil,
84             '@' => Regexp::Lexer::TokenType::ArraySigil,
85             ':' => Regexp::Lexer::TokenType::Colon,
86             '#' => Regexp::Lexer::TokenType::Sharp,
87             '^' => Regexp::Lexer::TokenType::Cap,
88             '=' => Regexp::Lexer::TokenType::Equal,
89             '!' => Regexp::Lexer::TokenType::Exclamation,
90             q<'> => Regexp::Lexer::TokenType::SingleQuote,
91             q<"> => Regexp::Lexer::TokenType::DoubleQuote,
92             );
93              
94             sub tokenize {
95 7     7 1 23198 my ($re) = @_;
96              
97 7 100       43 if (ref $re ne 'Regexp') {
98 1         179 croak "Not regexp quoted argument is given";
99             }
100              
101             # B::cstring() is used to escape backslashes
102 6         94 my $re_cluster_string = B::cstring($re);
103              
104             # to remove double-quotes and parenthesis on leading and trailing
105 6         26 my $re_str = substr(substr($re_cluster_string, 2), 0, -2);
106              
107 6         20 $re_str =~ s/\\"/"/g; # for double quote which is converted by B::cstring
108              
109             # extract modifiers
110 6         46 $re_str =~ s/\A[?]([^:]*)://;
111 6         17 my @modifiers;
112 6         36 for my $modifier (split //, $1) {
113 10         28 push @modifiers, $modifier;
114             }
115              
116 6         48 my @chars = split //, $re_str;
117              
118 6         12 my @tokens;
119 6         13 my $index = 0;
120              
121 6         12 my $end_of_line_exists = 0;
122 6 100       24 if ($chars[-1] eq '$') {
123 1         2 pop @chars;
124 1         3 $end_of_line_exists = 1;
125             }
126              
127 6 100       28 if ($chars[0] eq '^') {
128 1         7 push @tokens, {
129             char => shift @chars,
130             index => ++$index,
131             type => Regexp::Lexer::TokenType::BeginningOfLine,
132             };
133             }
134              
135 6         13 my $backslashes = 0;
136 6         30 my $next_c;
137 6         29 for (my $i = 0; defined(my $c = $chars[$i]); $i++) {
138 111 100       184 if ($c eq '\\') {
139 59 100       106 if ($backslashes <= 1) {
140 47         39 $backslashes++;
141 47         90 next;
142             }
143              
144             # now status -> '\\\\\\'
145 12 50       26 if ($backslashes == 2) {
146 12         15 $next_c = $chars[++$i];
147 12 50 33     50 if (!defined $next_c || $next_c ne '\\') {
148 0         0 croak "Invalid syntax regexp is given"; # fail safe
149             }
150              
151 12         36 push @tokens, {
152             char => '\\\\',
153             index => ++$index,
154             type => Regexp::Lexer::TokenType::EscapedCharacter,
155             };
156              
157 12         13 $backslashes = 0;
158 12         27 next;
159             }
160             }
161              
162             # To support *NOT META* newline character which is in regexp
163 52 100       167 if ($backslashes == 1) {
164 3         4 my $type = Regexp::Lexer::TokenType::Unknown;
165 3 100       9 if ($c eq 'n') {
    50          
166 2         4 $type = Regexp::Lexer::TokenType::Newline;
167             }
168             elsif ($c eq 'r') { # XXX maybe unreachable
169 1         2 $type = Regexp::Lexer::TokenType::Return;
170             }
171              
172 3         10 push @tokens, {
173             char => '\\' . $c,
174             index => ++$index,
175             type => $type,
176             };
177              
178 3         3 $backslashes = 0;
179 3         8 next;
180             }
181              
182 49 100       85 if ($backslashes == 2) {
183 10         21 my $type = $escapedSpecialChar{$c};
184              
185             # Determine meaning of \N
186 10 100       29 if ($c eq 'N') {
187 4         7 $type = Regexp::Lexer::TokenType::EscapedCharUnicode;
188              
189 4         7 $next_c = $chars[$i+1];
190 4 100 100     24 if (!defined $next_c || $next_c ne '{') {
191 2         3 $type = Regexp::Lexer::TokenType::EscapedNotNewline;
192             }
193             }
194              
195 10   100     61 push @tokens, {
196             char => '\\' . $c,
197             index => ++$index,
198             type => $type || Regexp::Lexer::TokenType::EscapedCharacter,
199             };
200              
201 10         17 $backslashes = 0;
202 10         24 next;
203             }
204              
205 39   100     202 push @tokens, {
206             char => $c,
207             index => ++$index,
208             type => $specialChar{$c} || Regexp::Lexer::TokenType::Character,
209             };
210              
211 39         97 $backslashes = 0; # for fail safe
212             }
213              
214 6 100       23 if ($end_of_line_exists) {
215 1         4 push @tokens, {
216             char => '$',
217             index => ++$index,
218             type => Regexp::Lexer::TokenType::EndOfLine,
219             };
220             }
221              
222             return {
223 6         49 tokens => \@tokens,
224             modifiers => \@modifiers,
225             };
226             }
227              
228             1;
229             __END__