This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Deliver t/re/charset.t
[perl5.git] / t / re / charset.t
1 # Test the /a, /d, etc regex modifiers
2
3 BEGIN {
4     chdir 't' if -d 't';
5     @INC = '../lib';
6     require './test.pl';
7 }
8
9 use strict;
10 use warnings;
11
12 # Each case is a valid element of its hash key.  Choose, where available, an
13 # ASCII-range, Latin-1 non-ASCII range, and above Latin1 range code point.
14 my %testcases = (
15     '\w' => [ ord("A"), 0xE2, 0x16B ],   # Below expects these to all be alpha
16     '\d' => [ ord("0"), 0x0662 ],
17     '\s' => [ ord("\t"), 0xA0, 0x1680 ],  # Below expects these to be [:blank:]
18     '[:cntrl:]' => [ 0x00, 0x88 ],
19     '[:graph:]' => [ ord("&"), 0xF7, 0x02C7 ], # Below expects these to be
20                                                # [:print:]
21     '[:lower:]' => [ ord("g"), 0xE3, 0x0127 ],
22     '[:punct:]' => [ ord("!"), 0xBF, 0x055C ],
23     '[:upper:]' => [ ord("G"), 0xC3, 0x0126 ],
24     '[:xdigit:]' => [ ord("4"), 0xFF15 ],
25 );
26
27 $testcases{'[:digit:]'} = $testcases{'\d'};
28 $testcases{'[:alnum:]'} = $testcases{'\w'};
29 $testcases{'[:alpha:]'} = $testcases{'\w'};
30 $testcases{'[:blank:]'} = $testcases{'\s'};
31 $testcases{'[:print:]'} = $testcases{'[:graph:]'};
32 $testcases{'[:space:]'} = $testcases{'\s'};
33 $testcases{'[:word:]'} = $testcases{'\w'};
34
35 # For each possible character set...
36 foreach my $charset ("d", "u") {
37
38     # And in utf8 or not
39     foreach my $upgrade ("", 'utf8::upgrade($a); ') {
40
41         # reverse gets the, \w, \s, \d first.
42         for my $class (reverse sort keys %testcases) {
43
44             # The complement of \w is \W; of [:posix:] is [:^posix:]
45             my $complement = $class;
46             if ($complement !~ s/ ( \[: ) /$1^/x) {
47                 $complement = uc($class);
48             }
49
50             # For each test case
51             foreach my $ord (@{$testcases{$class}}) {
52                 my $char = display(chr($ord));
53
54                 # > 255 already implies upgraded.  Skip the ones that don't
55                 # have an explicit upgradei.  This shows more clearly in the
56                 # output which tests are in utf8, or not.
57                 next if $ord > 255 && ! $upgrade;
58
59                 my $reason = "";    # Explanation output with each test
60                 my $match = 1;      # Calculated whether test regex should
61                                     # match or not
62
63                 # Everything always matches in ASCII, or under /u
64                 if ($ord < 128 || $charset eq 'u') {
65                     $reason = "\"$char\" is a $class under /$charset";
66                 }
67                 elsif ($charset eq "a") {
68                     $match = 0;
69                     $reason = "\"$char\" is non-ASCII, which can't be a $class under /a";
70                 }
71                 elsif ($ord > 255) {
72                     $reason = "\"$char\" is a $class under /$charset";
73                 }
74                 elsif ($charset eq 'l') {
75
76                     # We are using the C locale, which is essentially ASCII,
77                     # but under utf8, the above-latin1 chars are treated as
78                     # Unicode)
79                     $reason = "\"$char\" is not a $class in this locale under /l";
80                     $match = 0;
81                 }
82                 elsif ($upgrade) {
83                     $reason = "\"$char\" is a $class in utf8 under /d";
84                 }
85                 else {
86                     $reason = "\"$char\" is latin1, which requires utf8 to be a $class under /d";
87                     $match = 0;
88                 }
89                 $reason = "; $reason" if $reason;
90
91                 my $op;
92                 my $neg_op;
93                 if ($match) {
94                     $op = '=~';
95                     $neg_op = '!~';
96                 }
97                 else {
98                     $op = '!~';
99                     $neg_op = '=~';
100                 }
101
102                 # In [...] or not
103                 foreach my $bracketed (0, 1) {
104                     my $lb = "";
105                     my $rb = "";
106                     if ($bracketed) {
107
108                         # Adds an extra char to the character class to make sure
109                         # that the class doesn't get optimized away.
110                         $lb = ($bracketed) ? '[_' : "";
111                         $rb = ($bracketed) ? ']' : "";
112                     }
113                     else {  # [:posix:] must be inside outer [ ]
114                         next if $class =~ /\[/;
115                     }
116
117                     my $length = 10;    # For regexec.c regrepeat() cases by
118                                         # matching more than one item
119                     # Test both class and its complement, and with one or more
120                     # than one item to match.
121                     foreach my $eval (
122                         qq[my \$a = "$char"; $upgrade\$a $op qr/ (?$charset: $lb$class$rb ) /x],
123                         qq[my \$a = "$char"; $upgrade\$a $neg_op qr/ (?$charset: $lb$complement$rb ) /x],
124                         qq[my \$a = "$char" x $length; $upgrade\$a $op qr/ (?$charset: $lb$class$rb\{$length} ) /x],
125                         qq[my \$a = "$char" x $length; $upgrade\$a $neg_op qr/ (?$charset: $lb$complement$rb\{$length} ) /x],
126                     ) {
127                         ok (eval $eval, $eval . $reason);
128                     }
129                 }
130
131                 next if $class ne '\w';
132
133                 # Test \b, \B at beginning and end of string
134                 foreach my $eval (
135                     qq[my \$a = "$char"; $upgrade\$a $op qr/ (?$charset: ^ \\b . ) /x],
136                     qq[my \$a = "$char"; $upgrade\$a $op qr/ (?$charset: . \\b \$) /x],
137                     qq[my \$a = "$char"; $upgrade\$a $neg_op qr/(?$charset: ^ \\B . ) /x],
138                     qq[my \$a = "$char"; $upgrade\$a $neg_op qr/(?$charset: . \\B \$ ) /x],
139                 ) {
140                     ok (eval $eval, $eval . $reason);
141                 }
142
143                 # Test \b, \B adjacent to a non-word char, both before it and
144                 # after.  We test with ASCII, Latin1 and Unicode non-word chars
145                 foreach my $space_ord (@{$testcases{'\s'}}) {
146
147                     # Useless to try to test non-utf8 when the ord itself
148                     # forces utf8
149                     next if $space_ord > 255 && ! $upgrade;
150
151                     my $space = display(chr $space_ord);
152
153                     foreach my $eval (
154                         qq[my \$a = "$space$char"; $upgrade\$a $op qr/ (?$charset: . \\b . ) /x],
155                         qq[my \$a = "$char$space"; $upgrade\$a $op qr/ (?$charset: . \\b . ) /x],
156                         qq[my \$a = "$space$char"; $upgrade\$a $neg_op qr/ (?$charset: . \\B . ) /x],
157                         qq[my \$a = "$char$space"; $upgrade\$a $neg_op qr/ (?$charset: . \\B . ) /x],
158                     ) {
159                         ok (eval $eval, $eval . $reason . "; \"$space\" is not a \\w");
160                     }
161                 }
162
163                 # Test \b, \B in the middle of two nominally word chars, but
164                 # one or both may be considered non-word depending on range
165                 # and charset.
166                 foreach my $other_ord (@{$testcases{'\w'}}) {
167                     next if $other_ord > 255 && ! $upgrade;
168                     my $other = display(chr $other_ord);
169
170                     # Determine if the other char is a word char in current
171                     # circumstances
172                     my $other_is_word = 1;
173                     my $other_reason = "\"$other\" is a $class under /$charset";
174                     if ($other_ord > 127
175                         && $charset ne 'u'
176                         && ($charset eq "a"
177                             || ($other_ord < 256 && ($charset eq 'l' || ! $upgrade))))
178                     {
179                         $other_is_word = 0;
180                         $other_reason = "\"$other\" is not a $class under /$charset";
181                     }
182                     my $both_reason = $reason;
183                     $both_reason .= "; $other_reason" if $other_ord != $ord;
184
185                     # If both are the same wordness, then \b will fail; \B
186                     # succeed
187                     if ($match == $other_is_word) {
188                         $op = '!~';
189                         $neg_op = '=~';
190                     }
191                     else {
192                         $op = '=~';
193                         $neg_op = '!~';
194                     }
195
196                     foreach my $eval (
197                         qq[my \$a = "$other$char"; $upgrade\$a $op qr/ (?$charset: $other \\b $char ) /x],
198                         qq[my \$a = "$char$other"; $upgrade\$a $op qr/ (?$charset: $char \\b $other ) /x],
199                         qq[my \$a = "$other$char"; $upgrade\$a $neg_op qr/ (?$charset: $other \\B $char ) /x],
200                         qq[my \$a = "$char$other"; $upgrade\$a $neg_op qr/ (?$charset: $char \\B $other ) /x],
201                     ) {
202                         ok (eval $eval, $eval . $both_reason);
203                     }
204
205                     next if $other_ord == $ord;
206
207                     # These start with the \b or \B.  They are included, based
208                     # on source code analysis, to force the testing of the FBC
209                     # (find_by_class) portions of regexec.c.
210                     foreach my $eval (
211                         qq[my \$a = "$other$char"; $upgrade\$a $op qr/ (?$charset: \\b $char ) /x],
212                         qq[my \$a = "$char$other"; $upgrade\$a $op qr/ (?$charset: \\b $other ) /x],
213                         qq[my \$a = "$other$char"; $upgrade\$a $neg_op qr/ (?$charset: \\B $char ) /x],
214                         qq[my \$a = "$char$other"; $upgrade\$a $neg_op qr/ (?$charset: \\B $other ) /x],
215                     ) {
216                         ok (eval $eval, $eval . $both_reason);
217                     }
218                 }
219             } # End of each test case in a class
220         } # End of \w, \s, ...
221     } # End of utf8 upgraded or not
222 }
223
224 plan(curr_test() - 1);