1package regcharclass_multi_char_folds;
2use 5.015;
3use strict;
4use warnings;
5use Unicode::UCD "prop_invmap";
6
7# This returns an array of strings of the form
8#   "\x{foo}\x{bar}\x{baz}"
9# of the sequences of code points that are multi-character folds in the
10# current Unicode version.  If the parameter is 1, all such folds are
11# returned.  If the parameters is 0, only the ones containing exclusively
12# Latin1 characters are returned.  In the latter case all combinations of
13# Latin1 characters that can fold to the base one are returned.  Thus for
14# 'ss', it would return in addition, 'Ss', 'sS', and 'SS'.  This is because
15# this code is designed to help regcomp.c, and EXACTFish regnodes.  For
16# non-UTF-8 patterns, the strings are not necessarily folded, so we need to
17# check for the upper and lower case versions.  For UTF-8 patterns, the
18# strings are folded, except in EXACTFL nodes) so we only need to worry about
19# the fold version.  All folded-to characters in non-UTF-8 (Latin1) are
20# members of fold-pairs, at least within Latin1, 'k', and 'K', for example.
21# So there aren't complications with dealing with unfolded input.  That's not
22# true of UTF-8 patterns, where things can get tricky.  Thus for EXACTFL nodes
23# where things aren't all folded, code has to be written specially to handle
24# this, instead of the macros here being extended to try to handle it.
25#
26# There are no non-ASCII Latin1 multi-char folds currently, and none likely to
27# be ever added.  Thus the output is the same as if it were just asking for
28# ASCII characters, not full Latin1.  Hence, it is suitable for generating
29# things that match EXACTFAA.  It does check for and croak if there ever were
30# to be an upper Latin1 range multi-character fold.
31#
32# This is designed for input to regen/regcharlass.pl.
33
34sub gen_combinations ($;) {
35    # Generate all combinations for the first parameter which is an array of
36    # arrays.
37
38    my ($fold_ref, $string, $i) = @_;
39    $string = "" unless $string;
40    $i = 0 unless $i;
41
42    my @ret;
43
44    # Look at each element in this level's array.
45    foreach my $j (0 .. @{$fold_ref->[$i]} - 1) {
46
47        # Append its representation to what we have currently
48        my $new_string = $fold_ref->[$i][$j] =~ /[[:print:]]/
49                         ? ($string . chr $fold_ref->[$i][$j])
50                         : sprintf "$string\\x{%X}", $fold_ref->[$i][$j];
51
52        if ($i >=  @$fold_ref - 1) {    # Final level: just return it
53            push @ret, "\"$new_string\"";
54        }
55        else {  # Generate the combinations for the next level with this one's
56            push @ret, &gen_combinations($fold_ref, $new_string, $i + 1);
57        }
58    }
59
60    return @ret;
61}
62
63sub multi_char_folds ($$) {
64    my $type = shift;  # 'u' for UTF-8; 'l' for latin1
65    my $range = shift;  # 'a' for all; 'h' for starting 2 bytes; 'm' for ending 2
66    die "[lu] only valid values for first parameter" if $type !~ /[lu]/;
67    die "[aht3] only valid values for 2nd parameter" if $range !~ /[aht3]/;
68
69    return () if pack("C*", split /\./, Unicode::UCD::UnicodeVersion()) lt v3.0.1;
70
71    my ($cp_ref, $folds_ref, $format) = prop_invmap("Case_Folding");
72    die "Could not find inversion map for Case_Folding" unless defined $format;
73    die "Incorrect format '$format' for Case_Folding inversion map"
74                                                        unless $format eq 'al';
75
76    my @folds;
77    my @output_folds;
78
79    for my $i (0 .. @$folds_ref - 1) {
80        next unless ref $folds_ref->[$i];   # Skip single-char folds
81
82        # The code in regcomp.c currently assumes that no multi-char fold
83        # folds to the upper Latin1 range.  It's not a big deal to add; we
84        # just have to forbid such a fold in EXACTFL nodes, like we do already
85        # for ascii chars in EXACTFA (and EXACTFL) nodes.  But I (khw) doubt
86        # that there will ever be such a fold created by Unicode, so the code
87        # isn't there to occupy space and time; instead there is this check.
88        die sprintf("regcomp.c can't cope with a latin1 multi-char fold (found in the fold of 0x%X", $cp_ref->[$i]) if grep { $_ < 256 && chr($_) !~ /[[:ascii:]]/ } @{$folds_ref->[$i]};
89
90        @folds = @{$folds_ref->[$i]};
91        if ($range eq '3') {
92            next if @folds < 3;
93        }
94        elsif ($range eq 'h') {
95            pop @folds;
96        }
97        elsif ($range eq 't') {
98            next if @folds < 3;
99            shift @folds;
100        }
101
102        # Create a line that looks like "\x{foo}\x{bar}\x{baz}" of the code
103        # points that make up the fold (use the actual character if
104        # printable).
105        my $fold = join "", map { chr $_ =~ /[[:print:]]/a
106                                            ? chr $_
107                                            : sprintf "\\x{%X}", $_
108                                } @folds;
109        $fold = "\"$fold\"";
110
111        # Skip if something else already has this fold
112        next if grep { $_ eq $fold } @output_folds;
113
114        if ($type eq 'u') {
115            push @output_folds, $fold;
116        }   # Skip if wants only all-ascii folds, and there is a non-ascii
117        elsif (! grep { chr($_) =~ /[^[:ascii:]]/ } @folds) {
118
119            # If the fold is to a cased letter, replace the entry with an
120            # array which also includes its upper case.
121            my $this_fold_ref = \@folds;
122            for my $j (0 .. @$this_fold_ref - 1) {
123                my $this_ord = $this_fold_ref->[$j];
124                if (chr($this_ord) =~ /\p{Cased}/) {
125                    my $uc = ord(uc(chr($this_ord)));
126                    undef $this_fold_ref->[$j];
127                    @{$this_fold_ref->[$j]} = ( $this_ord, $uc);
128                }
129            }
130
131            # Then generate all combinations of upper/lower case of the fold.
132            push @output_folds, gen_combinations($this_fold_ref);
133
134        }
135    }
136
137    # \x17F is the small LONG S, which folds to 's'.  Both Capital and small
138    # LATIN SHARP S fold to 'ss'.  Therefore, they should also match two 17F's
139    # in a row under regex /i matching.  But under /iaa regex matching, all
140    # three folds to 's' are prohibited, but the sharp S's should still match
141    # two 17F's.  This prohibition causes our regular regex algorithm that
142    # would ordinarily allow this match to fail.  This is the only instance in
143    # all Unicode of this kind of issue.  By adding a special case here, we
144    # can use the regular algorithm (with some other changes elsewhere as
145    # well).
146    #
147    # It would be possible to re-write the above code to automatically detect
148    # and handle this case, and any others that might eventually get added to
149    # the Unicode standard, but I (khw) don't think it's worth it.  I believe
150    # that it's extremely unlikely that more folds to ASCII characters are
151    # going to be added, and if I'm wrong, fold_grind.t has the intelligence
152    # to detect them, and test that they work, at which point another special
153    # case could be added here if necessary.
154    #
155    # No combinations of this with 's' need be added, as any of these
156    # containing 's' are prohibited under /iaa.
157    push @output_folds, '"\x{17F}\x{17F}"' if $type eq 'u' && $range eq 'a';
158
159    return @output_folds;
160}
161
1621
163