191f110e0Safresh1package regcharclass_multi_char_folds;
291f110e0Safresh1use 5.015;
391f110e0Safresh1use strict;
491f110e0Safresh1use warnings;
591f110e0Safresh1use Unicode::UCD "prop_invmap";
691f110e0Safresh1
791f110e0Safresh1# This returns an array of strings of the form
891f110e0Safresh1#   "\x{foo}\x{bar}\x{baz}"
991f110e0Safresh1# of the sequences of code points that are multi-character folds in the
1091f110e0Safresh1# current Unicode version.  If the parameter is 1, all such folds are
1191f110e0Safresh1# returned.  If the parameters is 0, only the ones containing exclusively
1291f110e0Safresh1# Latin1 characters are returned.  In the latter case all combinations of
1391f110e0Safresh1# Latin1 characters that can fold to the base one are returned.  Thus for
1491f110e0Safresh1# 'ss', it would return in addition, 'Ss', 'sS', and 'SS'.  This is because
1591f110e0Safresh1# this code is designed to help regcomp.c, and EXACTFish regnodes.  For
1656d68f1eSafresh1# non-UTF-8 patterns, the strings are not necessarily folded, so we need to
1756d68f1eSafresh1# check for the upper and lower case versions.  For UTF-8 patterns, the
1856d68f1eSafresh1# strings are folded, except in EXACTFL nodes) so we only need to worry about
1956d68f1eSafresh1# the fold version.  All folded-to characters in non-UTF-8 (Latin1) are
2056d68f1eSafresh1# members of fold-pairs, at least within Latin1, 'k', and 'K', for example.
2156d68f1eSafresh1# So there aren't complications with dealing with unfolded input.  That's not
2256d68f1eSafresh1# true of UTF-8 patterns, where things can get tricky.  Thus for EXACTFL nodes
2356d68f1eSafresh1# where things aren't all folded, code has to be written specially to handle
2456d68f1eSafresh1# this, instead of the macros here being extended to try to handle it.
25b8851fccSafresh1#
26b8851fccSafresh1# There are no non-ASCII Latin1 multi-char folds currently, and none likely to
27b8851fccSafresh1# be ever added.  Thus the output is the same as if it were just asking for
28b8851fccSafresh1# ASCII characters, not full Latin1.  Hence, it is suitable for generating
2956d68f1eSafresh1# things that match EXACTFAA.  It does check for and croak if there ever were
30b8851fccSafresh1# to be an upper Latin1 range multi-character fold.
3191f110e0Safresh1#
3291f110e0Safresh1# This is designed for input to regen/regcharlass.pl.
3391f110e0Safresh1
3491f110e0Safresh1sub gen_combinations ($;) {
3591f110e0Safresh1    # Generate all combinations for the first parameter which is an array of
3691f110e0Safresh1    # arrays.
3791f110e0Safresh1
3891f110e0Safresh1    my ($fold_ref, $string, $i) = @_;
3991f110e0Safresh1    $string = "" unless $string;
4091f110e0Safresh1    $i = 0 unless $i;
4191f110e0Safresh1
4291f110e0Safresh1    my @ret;
4391f110e0Safresh1
4491f110e0Safresh1    # Look at each element in this level's array.
45*eac174f2Safresh1    if (ref $fold_ref->[$i]) {
4691f110e0Safresh1    foreach my $j (0 .. @{$fold_ref->[$i]} - 1) {
4791f110e0Safresh1
4891f110e0Safresh1        # Append its representation to what we have currently
4956d68f1eSafresh1        my $new_string = $fold_ref->[$i][$j] =~ /[[:print:]]/
5056d68f1eSafresh1                         ? ($string . chr $fold_ref->[$i][$j])
5156d68f1eSafresh1                         : sprintf "$string\\x{%X}", $fold_ref->[$i][$j];
5291f110e0Safresh1
5391f110e0Safresh1        if ($i >=  @$fold_ref - 1) {    # Final level: just return it
5491f110e0Safresh1            push @ret, "\"$new_string\"";
5591f110e0Safresh1        }
5691f110e0Safresh1        else {  # Generate the combinations for the next level with this one's
5791f110e0Safresh1            push @ret, &gen_combinations($fold_ref, $new_string, $i + 1);
5891f110e0Safresh1        }
5991f110e0Safresh1    }
60*eac174f2Safresh1    }
6191f110e0Safresh1
6291f110e0Safresh1    return @ret;
6391f110e0Safresh1}
6491f110e0Safresh1
6556d68f1eSafresh1sub multi_char_folds ($$) {
6656d68f1eSafresh1    my $type = shift;  # 'u' for UTF-8; 'l' for latin1
6756d68f1eSafresh1    my $range = shift;  # 'a' for all; 'h' for starting 2 bytes; 'm' for ending 2
6856d68f1eSafresh1    die "[lu] only valid values for first parameter" if $type !~ /[lu]/;
6956d68f1eSafresh1    die "[aht3] only valid values for 2nd parameter" if $range !~ /[aht3]/;
7091f110e0Safresh1
71b8851fccSafresh1    return () if pack("C*", split /\./, Unicode::UCD::UnicodeVersion()) lt v3.0.1;
72b8851fccSafresh1
7391f110e0Safresh1    my ($cp_ref, $folds_ref, $format) = prop_invmap("Case_Folding");
7491f110e0Safresh1    die "Could not find inversion map for Case_Folding" unless defined $format;
7591f110e0Safresh1    die "Incorrect format '$format' for Case_Folding inversion map"
7691f110e0Safresh1                                                        unless $format eq 'al';
7756d68f1eSafresh1
78*eac174f2Safresh1    my %inverse_latin1_folds;
79*eac174f2Safresh1    for my $i (0 .. @$cp_ref - 1) {
80*eac174f2Safresh1        next if ref $folds_ref->[$i];   # multi-char fold
81*eac174f2Safresh1        next if $folds_ref->[$i] == 0;  # Not folded
82*eac174f2Safresh1        my $cp_base = $cp_ref->[$i];
83*eac174f2Safresh1
84*eac174f2Safresh1        for my $j ($cp_base .. $cp_ref->[$i+1] - 1) {
85*eac174f2Safresh1            my $folded_base = $folds_ref->[$i];
86*eac174f2Safresh1            next if $folded_base > 255;         # only interested in Latin1
87*eac174f2Safresh1            push @{$inverse_latin1_folds{$folded_base + $j - $cp_base}}, $j;
88*eac174f2Safresh1        }
89*eac174f2Safresh1    }
90*eac174f2Safresh1
9191f110e0Safresh1    my @folds;
92*eac174f2Safresh1    my %output_folds;
9391f110e0Safresh1
9491f110e0Safresh1    for my $i (0 .. @$folds_ref - 1) {
9591f110e0Safresh1        next unless ref $folds_ref->[$i];   # Skip single-char folds
9691f110e0Safresh1
9791f110e0Safresh1        # The code in regcomp.c currently assumes that no multi-char fold
9891f110e0Safresh1        # folds to the upper Latin1 range.  It's not a big deal to add; we
9991f110e0Safresh1        # just have to forbid such a fold in EXACTFL nodes, like we do already
10091f110e0Safresh1        # for ascii chars in EXACTFA (and EXACTFL) nodes.  But I (khw) doubt
10191f110e0Safresh1        # that there will ever be such a fold created by Unicode, so the code
10291f110e0Safresh1        # isn't there to occupy space and time; instead there is this check.
1036fb12b70Safresh1        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]};
10491f110e0Safresh1
10556d68f1eSafresh1        @folds = @{$folds_ref->[$i]};
10656d68f1eSafresh1        if ($range eq '3') {
10756d68f1eSafresh1            next if @folds < 3;
10856d68f1eSafresh1        }
10956d68f1eSafresh1        elsif ($range eq 'h') {
11056d68f1eSafresh1            pop @folds;
11156d68f1eSafresh1        }
11256d68f1eSafresh1        elsif ($range eq 't') {
11356d68f1eSafresh1            next if @folds < 3;
11456d68f1eSafresh1            shift @folds;
11556d68f1eSafresh1        }
11656d68f1eSafresh1
11791f110e0Safresh1        # Create a line that looks like "\x{foo}\x{bar}\x{baz}" of the code
11856d68f1eSafresh1        # points that make up the fold (use the actual character if
11956d68f1eSafresh1        # printable).
12056d68f1eSafresh1        my $fold = join "", map { chr $_ =~ /[[:print:]]/a
12156d68f1eSafresh1                                            ? chr $_
12256d68f1eSafresh1                                            : sprintf "\\x{%X}", $_
12356d68f1eSafresh1                                } @folds;
12491f110e0Safresh1        $fold = "\"$fold\"";
12591f110e0Safresh1
12691f110e0Safresh1        # Skip if something else already has this fold
127*eac174f2Safresh1        next if grep { $_ eq $fold } keys %output_folds;
12891f110e0Safresh1
12956d68f1eSafresh1        my $this_fold_ref = \@folds;
13091f110e0Safresh1        for my $j (0 .. @$this_fold_ref - 1) {
13191f110e0Safresh1            my $this_ord = $this_fold_ref->[$j];
13291f110e0Safresh1            undef $this_fold_ref->[$j];
133*eac174f2Safresh1
134*eac174f2Safresh1            # If the fold is to a Latin1-range cased letter, replace the entry
135*eac174f2Safresh1            # with an array which also includes everything that folds to it.
136*eac174f2Safresh1            if (exists $inverse_latin1_folds{$this_ord}) {
137*eac174f2Safresh1                push @{$this_fold_ref->[$j]},
138*eac174f2Safresh1                      ( $this_ord, @{$inverse_latin1_folds{$this_ord}} );
139*eac174f2Safresh1            }
140*eac174f2Safresh1            else {  # Otherwise, just itself. (gen_combinations() needs a ref)
141*eac174f2Safresh1                @{$this_fold_ref->[$j]} = ( $this_ord );
14291f110e0Safresh1            }
14391f110e0Safresh1        }
14491f110e0Safresh1
14591f110e0Safresh1        # Then generate all combinations of upper/lower case of the fold.
146*eac174f2Safresh1        $output_folds{$_} = $cp_ref->[$i] for gen_combinations($this_fold_ref);
14791f110e0Safresh1    }
14891f110e0Safresh1
1496fb12b70Safresh1    # \x17F is the small LONG S, which folds to 's'.  Both Capital and small
1506fb12b70Safresh1    # LATIN SHARP S fold to 'ss'.  Therefore, they should also match two 17F's
1516fb12b70Safresh1    # in a row under regex /i matching.  But under /iaa regex matching, all
1526fb12b70Safresh1    # three folds to 's' are prohibited, but the sharp S's should still match
1536fb12b70Safresh1    # two 17F's.  This prohibition causes our regular regex algorithm that
1546fb12b70Safresh1    # would ordinarily allow this match to fail.  This is the only instance in
1556fb12b70Safresh1    # all Unicode of this kind of issue.  By adding a special case here, we
1566fb12b70Safresh1    # can use the regular algorithm (with some other changes elsewhere as
1576fb12b70Safresh1    # well).
1586fb12b70Safresh1    #
1596fb12b70Safresh1    # It would be possible to re-write the above code to automatically detect
1606fb12b70Safresh1    # and handle this case, and any others that might eventually get added to
1616fb12b70Safresh1    # the Unicode standard, but I (khw) don't think it's worth it.  I believe
1626fb12b70Safresh1    # that it's extremely unlikely that more folds to ASCII characters are
1636fb12b70Safresh1    # going to be added, and if I'm wrong, fold_grind.t has the intelligence
1646fb12b70Safresh1    # to detect them, and test that they work, at which point another special
1656fb12b70Safresh1    # case could be added here if necessary.
1666fb12b70Safresh1    #
1676fb12b70Safresh1    # No combinations of this with 's' need be added, as any of these
168b46d8ef2Safresh1    # containing 's' are prohibited under /iaa.
169*eac174f2Safresh1    $output_folds{"\"\x{17F}\x{17F}\""} = 0xDF if $type eq 'u' && $range eq 'a';
1706fb12b70Safresh1
171*eac174f2Safresh1    return %output_folds;
17291f110e0Safresh1}
17391f110e0Safresh1
17491f110e0Safresh11
175