Jeffrey is trying very hard to avoid working on his
[p5sagit/p5-mst-13.2.git] / lib / unicore / mktables
1 #!/usr/bin/perl -w
2 use strict;
3 use Carp;
4 ##
5 ## mktables -- create the runtime Perl Unicode files (lib/unicore/**/*.pl)
6 ## from the Unicode database files (lib/unicore/*.txt).
7 ##
8
9 mkdir("In", 0755);
10 mkdir("Is", 0755);
11 mkdir("To", 0755);
12
13 ##
14 ## Process any args.
15 ##
16 my $Verbose = 0;
17
18 while (@ARGV)
19 {
20     my $arg = shift @ARGV;
21     if ($arg eq '-v') {
22         $Verbose = 1;
23     } elsif ($arg eq '-q') {
24         $Verbose = 0;
25     } else {
26         die "usage: $0 [-v|-q]";
27     }
28 }
29
30 my $LastUnicodeCodepoint = 0x10FFFF; # As of Unicode 3.1.1.
31
32 my $HEADER=<<"EOF";
33 # !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!! 
34 # This file is built by $0 from e.g. Unicode.txt.
35 # Any changes made here will be lost!
36
37 EOF
38
39 ##
40 ## The main datastructure (a "Table") represents a set of code points that
41 ## are part of a particular quality (that are part of \pL, \p{InGreek},
42 ## etc.). They are kept as ranges of code points (starting and ending of
43 ## each range).
44 ##
45 ## For example, a range ASCII LETTERS would be represented as:
46 ##   [ [ 0x41 => 0x5A, 'UPPER' ],
47 ##     [ 0x61 => 0x7A, 'LOWER, ] ]
48 ##
49 sub RANGE_START() { 0 } ## index into range element
50 sub RANGE_END()   { 1 } ## index into range element
51 sub RANGE_NAME()  { 2 } ## index into range element
52
53 ## Conceptually, these should really be folded into the 'Table' objects
54 my %TableInfo;
55 my %TableDesc;
56 my %FuzzyNames;
57 my %AliasInfo;
58
59 ##
60 ## Turn something like
61 ##    OLD-ITALIC
62 ## to
63 ##    OldItalic
64 ##
65 sub CanonicalName($)
66 {
67     my $name = lc shift;
68     $name =~ s/(?<![a-z])(\w)/\u$1/g;
69     $name =~ s/[_\W]+//g;
70     return $name;
71 }
72
73 ##
74 ## Turn something like
75 ##    OLD-ITALIC
76 ## to
77 ##    Old_Italic
78 ##
79 sub CanonicalNameForPattern($)
80 {
81     my $name = lc shift;
82     $name =~ s/(?<![a-z])(\w)/\u$1/g;
83     $name =~ s/[_\W]+/_/;
84     return $name;
85 }
86
87
88 ##
89 ## Associates a property ("Greek", "Lu", "Assigned",...) with a Table.
90 ##
91 ## Called like:
92 ##       New_Prop(In => 'Greek', $Table, Desc => 'Greek Block', Fuzzy => 1);
93 ##
94 ## Normally, these parameters are set when the Table is created (when the
95 ## Table->New constructor is called), but there are times when it needs to
96 ## be done after-the-fact...)
97 ##
98 sub New_Prop($$$@)
99 {
100     my $Type = shift; ## "Is" or "In";
101     my $Name = shift;
102     my $Table = shift;
103
104     ## remaining args are optional key/val
105     my %Args = @_;
106
107     my $Fuzzy = delete $Args{Fuzzy};
108     my $Desc  = delete $Args{Desc}; # description
109
110     $Name = CanonicalNameForPattern($Name) if $Fuzzy;
111
112     ## sanity check a few args
113     if (%Args or ($Type ne 'Is' and $Type ne 'In') or not ref $Table) {
114         confess "$0: bad args to New_Prop"
115     }
116
117     if (not $TableInfo{$Type}->{$Name})
118     {
119         $TableInfo{$Type}->{$Name} = $Table;
120         $TableDesc{$Type}->{$Name} = $Desc;
121         if ($Fuzzy) {
122             $FuzzyNames{$Type}->{$Name} = $Name;
123         }
124     }
125 }
126
127
128 ##
129 ## Creates a new Table object.
130 ##
131 ## Args are key/value pairs:
132 ##    In => Name         -- Name of "In" property to be associated with
133 ##    Is => Name         -- Name of "Is" property to be associated with
134 ##    Fuzzy => Boolean   -- True if name can be accessed "fuzzily"
135 ##    Desc  => String    -- Description of the property
136 ##
137 ## No args are required.
138 ##
139 sub Table::New
140 {
141     my $class = shift;
142     my %Args = @_;
143
144     my $Table = bless [], $class;
145
146     my $Fuzzy = delete $Args{Fuzzy};
147     my $Desc  = delete $Args{Desc};
148
149     for my $Type ('Is', 'In')
150     {
151         if (my $Name = delete $Args{$Type}) {
152             New_Prop($Type => $Name, $Table, Desc => $Desc, Fuzzy => $Fuzzy);
153         }
154     }
155
156     ## shouldn't have any left over
157     if (%Args) {
158         confess "$0: bad args to Table->New"
159     }
160
161     return $Table;
162 }
163
164 ##
165 ## Returns true if the Table has no code points
166 ##
167 sub Table::IsEmpty
168 {
169     my $Table = shift; #self
170     return not @$Table;
171 }
172
173 ##
174 ## Returns true if the Table has code points
175 ##
176 sub Table::NotEmpty
177 {
178     my $Table = shift; #self
179     return @$Table;
180 }
181
182 ##
183 ## Returns the maximum code point currently in the table.
184 ##
185 sub Table::Max
186 {
187     my $Table = shift; #self
188     confess "oops" if $Table->IsEmpty; ## must have code points to have a max
189     return $Table->[-1]->[RANGE_END];
190 }
191
192 ##
193 ## Replaces the codepoints in the Table with those in the Table given
194 ## as an arg. (NOTE: this is not a "deep copy").
195 ##
196 sub Table::Replace($$)
197 {
198     my $Table = shift; #self
199     my $New   = shift;
200
201     @$Table = @$New;
202 }
203
204 ##
205 ## Given a new code point, make the last range of the Table extend to
206 ## include the new (and all intervening) code points.
207 ##
208 sub Table::Extend
209 {
210     my $Table = shift; #self
211     my $codepoint = shift;
212
213     my $PrevMax = $Table->Max;
214
215     confess "oops ($codepoint <= $PrevMax)" if $codepoint <= $PrevMax;
216
217     $Table->[-1]->[RANGE_END] = $codepoint;
218 }
219
220 ##
221 ## Given a code point range start and end (and optional name), blindly
222 ## append them to the list of ranges for the Table.
223 ##
224 ## NOTE: Code points must be added in strictly ascending numeric order.
225 ##
226 sub Table::RawAppendRange
227 {
228     my $Table = shift; #self
229     my $start = shift;
230     my $end   = shift;
231     my $name  = shift;
232     $name = "" if not defined $name; ## warning: $name can be "0"
233
234     push @$Table, [ $start,    # RANGE_START
235                     $end,      # RANGE_END
236                     $name   ]; # RANGE_NAME
237 }
238
239 ##
240 ## Given a code point (and optional name), add it to the Table.
241 ##
242 ## NOTE: Code points must be added in strictly ascending numeric order.
243 ##
244 sub Table::Append
245 {
246     my $Table     = shift; #self
247     my $codepoint = shift;
248     my $name      = shift;
249     $name = "" if not defined $name; ## warning: $name can be "0"
250
251     ##
252     ## If we've already got a range working, and this code point is the next
253     ## one in line, and if the name is the same, just extend the current range.
254     ##
255     if ($Table->NotEmpty
256         and
257         $Table->Max == $codepoint - 1
258         and
259         $Table->[-1]->[RANGE_NAME] eq $name)
260     {
261         $Table->Extend($codepoint);
262     }
263     else
264     {
265         $Table->RawAppendRange($codepoint, $codepoint, $name);
266     }
267 }
268
269 ##
270 ## Given a code point range starting value and ending value (and name),
271 ## Add the range to teh Table.
272 ##
273 ## NOTE: Code points must be added in strictly ascending numeric order.
274 ##
275 sub Table::AppendRange
276 {
277     my $Table = shift; #self
278     my $start = shift;
279     my $end   = shift;
280     my $name  = shift;
281     $name = "" if not defined $name; ## warning: $name can be "0"
282
283     $Table->Append($start, $name);
284     $Table->Extend($end) if $end > $start;
285 }
286
287 ##
288 ## Return a new Table that represents all code points not in the Table.
289 ##
290 sub Table::Invert
291 {
292     my $Table = shift; #self
293
294     my $New = Table->New();
295     my $max = -1;
296     for my $range (@$Table)
297     {
298         my $start = $range->[RANGE_START];
299         my $end   = $range->[RANGE_END];
300         if ($start-1 >= $max+1) {
301             $New->AppendRange($max+1, $start-1, "");
302         }
303         $max = $end;
304     }
305     if ($max+1 < $LastUnicodeCodepoint) {
306         $New->AppendRange($max+1, $LastUnicodeCodepoint);
307     }
308     return $New;
309 }
310
311 ##
312 ## Merges any number of other tables with $self, returning the new table.
313 ## (existing tables are not modified)
314 ##
315 ##
316 ## Args may be Tables, or individual code points (as integers).
317 ##
318 ## Can be called as either a constructor or a method.
319 ##
320 sub Table::Merge
321 {
322     shift(@_) if not ref $_[0]; ## if called as a constructor, lose the class
323     my @Tables = @_;
324
325     ## Accumulate all records from all tables
326     my @Records;
327     for my $Arg (@Tables)
328     {
329         if (ref $Arg) {
330             ## arg is a table -- get its ranges
331             push @Records, @$Arg;
332         } else {
333             ## arg is a codepoint, make a range
334             push @Records, [ $Arg, $Arg ]
335         }
336     }
337
338     ## sort by range start, with longer ranges coming first.
339     my ($first, @Rest) = sort {
340         ($a->[RANGE_START] <=> $b->[RANGE_START])
341           or
342         ($b->[RANGE_END]   <=> $b->[RANGE_END])
343     } @Records;
344
345     my $New = Table->New();
346
347     ## Ensuring the first range is there makes the subsequent loop easier
348     $New->AppendRange($first->[RANGE_START],
349                       $first->[RANGE_END]);
350
351     ## Fold in records so long as they add new information.
352     for my $set (@Rest)
353     {
354         my $start = $set->[RANGE_START];
355         my $end   = $set->[RANGE_END];
356         if ($start > $New->Max) {
357             $New->AppendRange($start, $end);
358         } elsif ($end > $New->Max) {
359             $New->Extend($end);
360         }
361     }
362
363     return $New;
364 }
365
366 ##
367 ## Given a filename, write a representation of the Table to a file.
368 ## May have an optional comment as a 2nd arg.
369 ##
370 sub Table::Write
371 {
372     my $Table    = shift; #self
373     my $filename = shift;
374     my $comment  = shift;
375
376     print "$filename\n" if $Verbose;
377
378     if (not open(OUT, ">$filename")) {
379         die "$0: can't write $filename: $!\n";
380     }
381
382     print OUT $HEADER;
383     if (defined $comment) {
384         $comment =~ s/\s+\Z//;
385         $comment =~ s/^/# /gm;
386         print OUT "#\n$comment\n#\n";
387     }
388     print OUT "return <<'END';\n";
389
390     for my $set (@$Table)
391     {
392         my $start = $set->[RANGE_START];
393         my $end   = $set->[RANGE_END];
394         my $name  = $set->[RANGE_NAME];
395
396         if ($start == $end) {
397             printf OUT "%04X\t\t%s\n", $start, $name;
398         } else {
399             printf OUT "%04X\t%04X\t%s\n", $start, $end, $name;
400         }
401     }
402
403     print OUT "END\n";
404     close OUT;
405 }
406
407 ###########################################################################
408 ###########################################################################
409 ###########################################################################
410
411
412 ##
413 ## Called like:
414 ##     New_Alias(Is => 'All', SameAs => 'Any', Fuzzy => 1);
415 ##
416 ## The args must be in that order, although the Fuzzy pair may be omitted.
417 ##
418 ## This creates 'IsAll' as an alias for 'IsAny'
419 ##
420 sub New_Alias($$$@)
421 {
422     my $Type   = shift; ## "Is" or "In"
423     my $Alias  = shift;
424     my $SameAs = shift; # expecting "SameAs" -- just ignored
425     my $Name   = shift;
426
427     ## remaining args are optional key/val
428     my %Args = @_;
429
430     my $Fuzzy = delete $Args{Fuzzy};
431
432     ## sanity check a few args
433     if (%Args or ($Type ne 'Is' and $Type ne 'In') or $SameAs ne 'SameAs') {
434         confess "$0: bad args to New_Alias"
435     }
436
437     if (not $TableInfo{$Type}->{$Name}) {
438         confess "$0: don't have orignial $Type => $Name to make alias"
439     }
440     if ($TableInfo{$Alias}) {
441         confess "$0: already have original $Type => $Alias; can't make alias";
442     }
443     $AliasInfo{$Type}->{$Name} = $Alias;
444     if ($Fuzzy) {
445         $FuzzyNames{$Type}->{$Alias} = $Name;
446     }
447
448 }
449
450
451 ## All assigned code points
452 my $Assigned = Table->New(Is    => 'Assigned',
453                           Desc  => "All assigned code points",
454                           Fuzzy => 1);
455
456 my $Name     = Table->New(); ## all characters, individually by name
457 my $General  = Table->New(); ## all characters, grouped by category
458 my %General;
459 my %Cat;
460
461 ##
462 ## Process Unicode.txt (Categories, etc.)
463 ##
464 sub Unicode_Txt()
465 {
466     my $Bidi     = Table->New();
467     my $Deco     = Table->New();
468     my $Comb     = Table->New();
469     my $Number   = Table->New();
470     my $Mirrored = Table->New(Is    => 'Mirrored',
471                               Desc  => "Mirrored in bidirectional text",
472                               Fuzzy => 0);
473
474     my %DC;
475     my %Bidi;
476     my %Deco;
477     $Deco{Canon}   = Table->New(Is    => 'Canon',
478                                 Desc  => 'Decomposes to multiple characters',
479                                 Fuzzy => 0);
480     $Deco{Compat}  = Table->New(Is    => 'Compat',
481                                 Desc  => 'Compatible with a more-basic character',
482                                 Fuzzy => 0);
483
484     ## Initialize Perl-generated categories
485     ## (Categories from Unicode.txt are auto-initialized in gencat)
486     $Cat{Alnum}  = Table->New(Is => 'Alnum',  Desc => "[[:Alnum:]]",  Fuzzy => 0);
487     $Cat{Alpha}  = Table->New(Is => 'Alpha',  Desc => "[[:Alpha:]]",  Fuzzy => 0);
488     $Cat{ASCII}  = Table->New(Is => 'ASCII',  Desc => "[[:ASCII:]]",  Fuzzy => 0);
489     $Cat{Blank}  = Table->New(Is => 'Blank',  Desc => "[[:Blank:]]",  Fuzzy => 0);
490     $Cat{Cntrl}  = Table->New(Is => 'Cntrl',  Desc => "[[:Cntrl:]]",  Fuzzy => 0);
491     $Cat{Digit}  = Table->New(Is => 'Digit',  Desc => "[[:Digit:]]",  Fuzzy => 0);
492     $Cat{Graph}  = Table->New(Is => 'Graph',  Desc => "[[:Graph:]]",  Fuzzy => 0);
493     $Cat{Lower}  = Table->New(Is => 'Lower',  Desc => "[[:Lower:]]",  Fuzzy => 0);
494     $Cat{Print}  = Table->New(Is => 'Print',  Desc => "[[:Print:]]",  Fuzzy => 0);
495     $Cat{Punct}  = Table->New(Is => 'Punct',  Desc => "[[:Punct:]]",  Fuzzy => 0);
496     $Cat{Space}  = Table->New(Is => 'Space',  Desc => "[[:Space:]]",  Fuzzy => 0);
497     $Cat{Title}  = Table->New(Is => 'Title',  Desc => "[[:Title:]]",  Fuzzy => 0);
498     $Cat{Upper}  = Table->New(Is => 'Upper',  Desc => "[[:Upper:]]",  Fuzzy => 0);
499     $Cat{XDigit} = Table->New(Is => 'XDigit', Desc => "[[:XDigit:]]", Fuzzy => 0);
500     $Cat{Word}   = Table->New(Is => 'Word',   Desc => "[[:Word:]]",   Fuzzy => 0);
501     $Cat{SpacePerl} = Table->New(Is => 'SpacePerl', Desc => '\s', Fuzzy => 0);
502
503     my %To;
504     $To{Upper} = Table->New();
505     $To{Lower} = Table->New();
506     $To{Title} = Table->New();
507     $To{Digit} = Table->New();
508
509     sub gencat($$$$)
510     {
511         my ($name, ## Name ("LATIN CAPITAL LETTER A")
512             $cat,  ## Category ("Lu", "Zp", "Nd", etc.)
513             $code, ## Code point (as an integer)
514             $op) = @_;
515
516         my $MajorCat = substr($cat, 0, 1); ## L, M, Z, S, etc
517
518         $Assigned->$op($code);
519         $Name->$op($code, $name);
520         $General->$op($code, $cat);
521
522         ## add to the sub category (e.g. "Lu", "Nd", "Cf", ..)
523         $Cat{$cat}      ||= Table->New(Is   => $cat,
524                                        Desc => "General Category '$cat'",
525                                        Fuzzy => 0);
526         $Cat{$cat}->$op($code);
527
528         ## add to the major category (e.g. "L", "N", "C", ...)
529         $Cat{$MajorCat} ||= Table->New(Is => $MajorCat,
530                                        Desc => "Major Category '$MajorCat'",
531                                        Fuzzy => 0);
532         $Cat{$MajorCat}->$op($code);
533
534         ($General{$name} ||= Table->New)->$op($code, $name);
535
536         # 005F: SPACING UNDERSCORE
537         $Cat{Word}->$op($code)  if $cat =~ /^[LMN]/ || $code == 0x005F;
538         $Cat{Alnum}->$op($code) if $cat =~ /^[LMN]/;
539         $Cat{Alpha}->$op($code) if $cat =~ /^[LM]/;
540
541
542
543         $Cat{Space}->$op($code) if $cat  =~ /^Z/
544                                 || $code == 0x0009  # 0009: HORIZONTAL TAB
545                                 || $code == 0x000A  # 000A: LINE FEED
546                                 || $code == 0x000B  # 000B: VERTICAL TAB
547                                 || $code == 0x000C  # 000C: FORM FEED
548                                 || $code == 0x000D; # 000D: CARRIAGE RETURN
549
550
551         $Cat{SpacePerl}->$op($code) if $cat =~ /^Z/
552                                     || $code == 0x0009 # 0009: HORIZONTAL TAB
553                                     || $code == 0x000A # 000A: LINE FEED
554                                     || $code == 0x000C # 000C: FORM FEED
555                                     || $code == 0x000D # 000D: CARRIAGE RETURN
556                                     || $code == 0x0085 # 0085: <NEXT LINE>
557                                     || $code == 0x2028 # 2028: LINE SEPARATOR
558                                     || $code == 0x2029;# 2029: PARAGRAPH SEP.
559
560         $Cat{Blank}->$op($code) if $cat  =~ /^Z[^lp]$/
561                                 || $code == 0x0009  # 0009: HORIZONTAL TAB
562                                 || $code == 0x0020; # 0020: SPACE
563
564         $Cat{Digit}->$op($code) if $cat eq "Nd";
565         $Cat{Upper}->$op($code) if $cat eq "Lu";
566         $Cat{Lower}->$op($code) if $cat eq "Ll";
567         $Cat{Title}->$op($code) if $cat eq "Lt";
568         $Cat{ASCII}->$op($code) if $code <= 0x007F;
569         $Cat{Cntrl}->$op($code) if $cat =~ /^C/;
570         $Cat{Graph}->$op($code) if $cat =~ /^([LMNPS]|Co)/;
571         $Cat{Print}->$op($code) if $cat =~ /^([LMNPS]|Co|Zs)/;
572         $Cat{Punct}->$op($code) if $cat =~ /^P/;
573
574         $Cat{XDigit}->$op($code) if ($code >= 0x30 && $code <= 0x39)  ## 0..9
575                                  || ($code >= 0x41 && $code <= 0x46)  ## A..F
576                                  || ($code >= 0x61 && $code <= 0x66); ## a..f
577     }
578
579     ## open ane read file.....
580     if (not open IN, "Unicode.txt") {
581         die "$0: Unicode.txt: $!\n";
582     }
583
584     ##
585     ## For building \p{_CombAbove} and \p{_CanonDCIJ}
586     ##
587     my %_Above_HexCodes; ## Hexcodes for chars with $comb == 230 ("ABOVE")
588
589     my %CodeToDeco;      ## Maps code to decomp. list for chars with first
590                          ## decomp. char an "i" or "j" (for \p{_CanonDCIJ})
591
592     ## This is filled in as we go....
593     my $CombAbove = Table->New(Is   => '_CombAbove',
594                                Desc  => '(for internal casefolding use)',
595                                Fuzzy => 0);
596
597     while (<IN>)
598     {
599         next unless /^[0-9A-Fa-f]+;/;
600         s/\s+$//;
601
602         my ($hexcode,   ## code point in hex (e.g. "0041")
603             $name,      ## character name (e.g. "LATIN CAPITAL LETTER A")
604             $cat,       ## category (e.g. "Lu")
605             $comb,      ## Canonical combining class (e.t. "230")
606             $bidi,      ## directional category (e.g. "L")
607             $deco,      ## decomposition mapping
608             $decimal,   ## decimal digit value
609             $digit,     ## digit value
610             $number,    ## numeric value
611             $mirrored,  ## mirrored
612             $unicode10, ## name in Unicode 1.0
613             $comment,   ## comment field
614             $upper,     ## uppercase mapping
615             $lower,     ## lowercase mapping
616             $title,     ## titlecase mapping
617               ) = split(/\s*;\s*/);
618
619         my $code = hex($hexcode);
620
621         if ($comb and $comb == 230) {
622             $CombAbove->Append($code);
623             $_Above_HexCodes{$hexcode} = 1;
624         }
625
626         ## Used in building \p{_CanonDCIJ}
627         if ($deco and $deco =~ m/^006[9A]\b/) {
628             $CodeToDeco{$code} = $deco;
629         }
630
631         ##
632         ## There are a few pairs of lines like:
633         ##   AC00;<Hangul Syllable, First>;Lo;0;L;;;;;N;;;;;
634         ##   D7A3;<Hangul Syllable, Last>;Lo;0;L;;;;;N;;;;;
635         ## that define ranges.
636         ##
637         if ($name =~ /^<(.+), (First|Last)>$/)
638         {
639             $name = $1;
640             gencat($name, $cat, $code, $2 eq 'First' ? 'Append' : 'Extend');
641             #New_Prop(In => $name, $General{$name}, Fuzzy => 1);
642         }
643         else
644         {
645             ## normal (single-character) lines
646             gencat($name, $cat, $code, 'Append');
647
648             # No Append() here since since several codes may map into one.
649             $To{Upper}->RawAppendRange($code, $code, $upper) if $upper;
650             $To{Lower}->RawAppendRange($code, $code, $lower) if $lower;
651             $To{Title}->RawAppendRange($code, $code, $title) if $title;
652             $To{Digit}->Append($code, $decimal) if length $decimal;
653
654             $Bidi->Append($code, $bidi);
655             $Comb->Append($code, $comb) if $comb;
656             $Number->Append($code, $number) if length $number;
657
658             $Mirrored->Append($code) if $mirrored eq "Y";
659
660             $Bidi{$bidi} ||= Table->New(Is    => "Bidi$bidi",
661                                         Desc  => "Bi-directional category '$bidi'",
662                                         Fuzzy => 0);
663             $Bidi{$bidi}->Append($code);
664
665             if ($deco)
666             {
667                 $Deco->Append($code, $deco);
668                 if ($deco =~/^<(\w+)>/)
669                 {
670                     $Deco{Compat}->Append($code);
671
672                     $DC{$1} ||= Table->New(Is => "DC$1",
673                                            Desc  => "Compatible with '$1'",
674                                            Fuzzy => 0);
675                     $DC{$1}->Append($code);
676                 }
677                 else
678                 {
679                     $Deco{Canon}->Append($code);
680                 }
681             }
682         }
683     }
684     close IN;
685
686     ##
687     ## Tidy up a few special cases....
688     ##
689
690     $Cat{Cn} = $Assigned->Invert; ## Cn is everything that doesn't exist
691     New_Prop(Is => 'Cn',
692              $Cat{Cn},
693              Desc => "General Category 'Cn' [not functional in Perl]",
694              Fuzzy => 0);
695
696     ## Unassigned is the same as 'Cn'
697     New_Alias(Is => 'Unassigned', SameAs => 'Cn', Fuzzy => 1);
698
699     $Cat{C}->Replace($Cat{C}->Merge($Cat{Cn}));  ## Now merge in Cn into C
700
701
702     # L& is Ll, Lu, and Lt.
703     New_Prop(Is => 'L&',
704              Table->Merge(@Cat{qw[Ll Lu Lt]}),
705              Desc  => '[\p{Ll}\p{Lu}\p{Lt}]',
706              Fuzzy => 0);
707
708     ## Any and All are all code points.
709     my $Any = Table->New(Is    => 'Any',
710                          Desc  => sprintf("[\\x{0000}-\\x{%X}]",
711                                           $LastUnicodeCodepoint),
712                          Fuzzy => 1);
713     $Any->RawAppendRange(0, $LastUnicodeCodepoint);
714
715     New_Alias(Is => 'All', SameAs => 'Any', Fuzzy => 1);
716
717     ##
718     ## Build special properties for Perl's internal case-folding needs:
719     ##    \p{_CaseIgnorable}
720     ##    \p{_CanonDCIJ}
721     ##    \p{_CombAbove}
722     ## _CombAbove was built above. Others are built here....
723     ##
724
725     ## \p{_CaseIgnorable} is [\p{Mn}\0x00AD\x2010]
726     New_Prop(Is => '_CaseIgnorable',
727              Table->Merge($Cat{Mn},
728                           0x00AD,    #SOFT HYPHEN
729                           0x2010),   #HYPHEN
730              Desc  => '(for internal casefolding use)',
731              Fuzzy => 0);
732
733
734     ## \p{_CanonDCIJ} is fairly complex...
735     my $CanonCDIJ = Table->New(Is    => '_CanonDCIJ',
736                                Desc  => '(for internal casefolding use)',
737                                Fuzzy => 0);
738     ## It contains the ASCII 'i' and 'j'....
739     $CanonCDIJ->Append(0x0069); # ASCII ord("i")
740     $CanonCDIJ->Append(0x006A); # ASCII ord("j")
741     ## ...and any character with a decomposition that starts with either of
742     ## those code points, but only if the decomposition does not have any
743     ## combining character with the "ABOVE" canonical combining class.
744     for my $code (sort { $a <=> $b} keys %CodeToDeco)
745     {
746         ## Need to ensure that all decomposition characters do not have
747         ## a %HexCodeToComb in %AboveCombClasses.
748         my $want = 1;
749         for my $deco_hexcode (split / /, $CodeToDeco{$code})
750         {
751             if (exists $_Above_HexCodes{$deco_hexcode}) {
752                 ## one of the decmposition chars has an ABOVE combination
753                 ## class, so we're not interested in this one
754                 $want = 0;
755                 last;
756             }
757         }
758         if ($want) {
759             $CanonCDIJ->Append($code);
760         }
761     }
762
763
764
765     ##
766     ## Now dump the files.
767     ##
768     $Name->Write("Name.pl");
769     $Bidi->Write("Bidirectional.pl");
770     $Comb->Write("CombiningClass.pl");
771     $Deco->Write("Decomposition.pl");
772     $Number->Write("Number.pl");
773     $General->Write("Category.pl");
774
775     for my $to (sort keys %To) {
776         $To{$to}->Write("To/$to.pl");
777     }
778 }
779
780 ##
781 ## Process LineBrk.txt
782 ##
783 sub LineBrk_Txt()
784 {
785     if (not open IN, "LineBrk.txt") {
786         die "$0: LineBrk.txt: $!\n";
787     }
788
789     my $Lbrk = Table->New();
790     my %Lbrk;
791
792     while (<IN>)
793     {
794         next unless /^([0-9A-Fa-f]+)(?:\.\.([0-9A-Fa-f]+))?\s*;\s*(\w+)/;
795
796         my ($first, $last, $lbrk) = (hex($1), hex($2||""), $3);
797
798         $Lbrk->Append($first, $lbrk);
799
800         $Lbrk{$lbrk} ||= Table->New(Is    => "Lbrk$lbrk",
801                                     Desc  => "Linebreak category '$lbrk'",
802                                     Fuzzy => 0);
803         $Lbrk{$lbrk}->Append($first);
804
805         if ($last) {
806             $Lbrk->Extend($last);
807             $Lbrk{$lbrk}->Extend($last);
808         }
809     }
810     close IN;
811
812     $Lbrk->Write("Lbrk.pl");
813 }
814
815 ##
816 ## Process ArabShap.txt.
817 ##
818 sub ArabShap_txt()
819 {
820     if (not open IN, "ArabShap.txt") {
821         die "$0: ArabShap.txt: $!\n";
822     }
823
824     my $ArabLink      = Table->New();
825     my $ArabLinkGroup = Table->New();
826
827     while (<IN>)
828     {
829         next unless /^[0-9A-Fa-f]+;/;
830         s/\s+$//;
831
832         my ($hexcode, $name, $link, $linkgroup) = split(/\s*;\s*/);
833         my $code = hex($hexcode);
834         $ArabLink->Append($code, $link);
835         $ArabLinkGroup->Append($code, $linkgroup);
836     }
837     close IN;
838
839     $ArabLink->Write("ArabLink.pl");
840     $ArabLinkGroup->Write("ArabLnkGrp.pl");
841 }
842
843 ##
844 ## Process Jamo.txt.
845 ##
846 sub Jamo_txt()
847 {
848     if (not open IN, "Jamo.txt") {
849         die "$0: Jamo.txt: $!\n";
850     }
851     my $Short = Table->New();
852
853     while (<IN>)
854     {
855         next unless /^([0-9A-Fa-f]+)\s*;\s*(\w*)/;
856         my ($code, $short) = (hex($1), $2);
857
858         $Short->Append($code, $short);
859     }
860     close IN;
861     $Short->Write("JamoShort.pl");
862 }
863
864 ##
865 ## Process Scripts.txt.
866 ##
867 sub Scripts_txt()
868 {
869     my @ScriptInfo;
870
871     if (not open(IN, "Scripts.txt")) {
872         die "$0: Scripts.txt: $!\n";
873     }
874     while (<IN>) {
875         next unless /^([0-9A-Fa-f]+)(?:\.\.([0-9A-Fa-f]+))?\s*;\s*(.+?)\s*\#/;
876
877         # Wait until all the scripts have been read since
878         # they are not listed in numeric order.
879         push @ScriptInfo, [ hex($1), hex($2||""), $3 ];
880     }
881     close IN;
882
883     # Now append the scripts properties in their code point order.
884
885     my %Script;
886     my $Scripts = Table->New();
887
888     for my $script (sort { $a->[0] <=> $b->[0] } @ScriptInfo)
889     {
890         my ($first, $last, $name) = @$script;
891         $Scripts->Append($first, $name);
892
893         $Script{$name} ||= Table->New(Is    => $name,
894                                       Desc  => "Script '$name'",
895                                       Fuzzy => 1);
896         $Script{$name}->Append($first, $name);
897
898         if ($last) {
899             $Scripts->Extend($last);
900             $Script{$name}->Extend($last);
901         }
902     }
903
904     $Scripts->Write("Scripts.pl");
905
906     ## Common is everything not explicitly assigned to a Script
907     ##
908     ##    ***shouldn't this be intersected with \p{Assigned}? ******
909     ##
910     New_Prop(Is => 'Common',
911              $Scripts->Invert,
912              Desc  => 'Pseudo-Script of codepoints not in other Unicode scripts',
913              Fuzzy => 1);
914 }
915
916 ##
917 ## Given a name like "Close Punctuation", return a regex (that when applied
918 ## with /i) matches any valid form of that name (e.g. "ClosePunctuation",
919 ## "Close-Punctuation", etc.)
920 ##
921 ## Accept any space, dash, or underbar where in the official name there is
922 ## space or a dash (or underbar, but there never is).
923 ##
924 ##
925 sub NameToRegex($)
926 {
927     my $Name = shift;
928     $Name =~ s/[- _]/(?:[-_]|\\s+)?/g;
929     return $Name;
930 }
931
932 ##
933 ## Process Blocks.txt.
934 ##
935 sub Blocks_txt()
936 {
937     my $Blocks = Table->New();
938     my %Blocks;
939
940     if (not open IN, "Blocks.txt") {
941         die "$0: Blocks.txt: $!\n";
942     }
943
944     while (<IN>)
945     {
946         #next if not /Private Use$/;
947         next if not /^([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+)\s*;\s*(.+?)\s*$/;
948
949         my ($first, $last, $name) = (hex($1), hex($2), $3);
950
951         $Blocks->Append($first, $name);
952
953         $Blocks{$name} ||= Table->New(In    => $name,
954                                       Desc  => "Block '$name'",
955                                       Fuzzy => 1);
956         $Blocks{$name}->Append($first, $name);
957
958         if ($last and $last != $first) {
959             $Blocks->Extend($last);
960             $Blocks{$name}->Extend($last);
961         }
962     }
963     close IN;
964
965     $Blocks->Write("Blocks.pl");
966 }
967
968 ##
969 ## Read in the PropList.txt.  It contains extended properties not
970 ## listed in the Unicode.txt, such as 'Other_Alphabetic':
971 ## alphabetic but not of the general category L; many modifiers
972 ## belong to this extended property category: while they are not
973 ## alphabets, they are alphabetic in nature.
974 ##
975 sub PropList_txt()
976 {
977     my @PropInfo;
978
979     if (not open IN, "PropList.txt") {
980         die "$0: PropList.txt: $!\n";
981     }
982
983     while (<IN>)
984     {
985         next unless /^([0-9A-Fa-f]+)(?:\.\.([0-9A-Fa-f]+))?\s*;\s*(.+?)\s*\#/;
986
987         # Wait until all the extended properties have been read since
988         # they are not listed in numeric order.
989         push @PropInfo, [ hex($1), hex($2||""), $3 ];
990     }
991     close IN;
992
993     # Now append the extended properties in their code point order.
994     my $Props = Table->New();
995     my %Prop;
996
997     for my $prop (sort { $a->[0] <=> $b->[0] } @PropInfo)
998     {
999         my ($first, $last, $name) = @$prop;
1000         $Props->Append($first, $name);
1001
1002         $Prop{$name} ||= Table->New(Is    => $name,
1003                                     Desc  => "Extended property '$name'",
1004                                     Fuzzy => 1);
1005         $Prop{$name}->Append($first, $name);
1006
1007         if ($last) {
1008             $Props->Extend($last);
1009             $Prop{$name}->Extend($last);
1010         }
1011     }
1012
1013     # Alphabetic is L and Other_Alphabetic.
1014     New_Prop(Is    => 'Alphabetic',
1015              Table->Merge($Cat{L}, $Prop{Other_Alphabetic}),
1016              Desc  => '[\p{L}\p{OtherAlphabetic}]', # use canonical names here
1017              Fuzzy => 1);
1018
1019     # Lowercase is Ll and Other_Lowercase.
1020     New_Prop(Is    => 'Lowercase',
1021              Table->Merge($Cat{Ll}, $Prop{Other_Lowercase}),
1022              Desc  => '[\p{Ll}\p{OtherLowercase}]', # use canonical names here
1023              Fuzzy => 1);
1024
1025     # Uppercase is Lu and Other_Uppercase.
1026     New_Prop(Is => 'Uppercase',
1027              Table->Merge($Cat{Lu}, $Prop{Other_Uppercase}),
1028              Desc  => '[\p{Lu}\p{Other_Uppercase}]', # use canonical names here
1029              Fuzzy => 1);
1030
1031     # Math is Sm and Other_Math.
1032     New_Prop(Is => 'Math',
1033              Table->Merge($Cat{Sm}, $Prop{Other_Math}),
1034              Desc  => '[\p{Sm}\p{OtherMath}]', # use canonical names here
1035              Fuzzy => 1);
1036
1037     # ID_Start is Ll, Lu, Lt, Lm, Lo, and Nl.
1038     New_Prop(Is => 'ID_Start',
1039              Table->Merge(@Cat{qw[Ll Lu Lt Lm Lo Nl]}),
1040              Desc  => '[\p{Ll}\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{Nl}]',
1041              Fuzzy => 1);
1042
1043     # ID_Continue is ID_Start, Mn, Mc, Nd, and Pc.
1044     New_Prop(Is => 'ID_Continue',
1045              Table->Merge(@Cat{qw[Ll Lu Lt Lm Lo Nl Mn Mc Nd Pc ]}),
1046              Desc  => '[\p{ID_Start}\p{Mn}\p{Mc}\p{Nd}\p{Pc}]',
1047              Fuzzy => 1);
1048 }
1049
1050 sub Make_GC_Aliases()
1051 {
1052     ##
1053     ## The mapping from General Category long forms to short forms is
1054     ## currently hardwired here since no simple data file in the UCD
1055     ## seems to do that.  Unicode 3.2 will assumedly correct this.
1056     ##
1057     my %Is = (
1058         'Letter'                        =>      'L',
1059         'Uppercase_Letter'              =>      'Lu',
1060         'Lowercase_Letter'              =>      'Ll',
1061         'Titlecase_Letter'              =>      'Lt',
1062         'Modifier_Letter'               =>      'Lm',
1063         'Other_Letter'                  =>      'Lo',
1064
1065         'Mark'                          =>      'M',
1066         'Non_Spacing_Mark'              =>      'Mn',
1067         'Spacing_Mark'                  =>      'Mc',
1068         'Enclosing_Mark'                =>      'Me',
1069
1070         'Separator'                     =>      'Z',
1071         'Space_Separator'               =>      'Zs',
1072         'Line_Separator'                =>      'Zl',
1073         'Paragraph_Separator'           =>      'Zp',
1074
1075         'Number'                        =>      'N',
1076         'Decimal_Number'                =>      'Nd',
1077         'Letter_Number'                 =>      'Nl',
1078         'Other_Number'                  =>      'No',
1079
1080         'Punctuation'                   =>      'P',
1081         'Connector_Punctuation'         =>      'Pc',
1082         'Dash_Punctuation'              =>      'Pd',
1083         'Open_Punctuation'              =>      'Ps',
1084         'Close_Punctuation'             =>      'Pe',
1085         'Initial_Punctuation'           =>      'Pi',
1086         'Final_Punctuation'             =>      'Pf',
1087         'Other_Punctuation'             =>      'Po',
1088
1089         'Symbol'                        =>      'S',
1090         'Math_Symbol'                   =>      'Sm',
1091         'Currency_Symbol'               =>      'Sc',
1092         'Modifier_Symbol'               =>      'Sk',
1093         'Other_Symbol'                  =>      'So',
1094
1095         'Other'                         =>      'C',
1096         'Control'                       =>      'Cc',
1097         'Format'                        =>      'Cf',
1098         'Surrogate'                     =>      'Cs',
1099         'Private Use'                   =>      'Co',
1100         'Unassigned'                    =>      'Cn',
1101     );
1102
1103     ## make the aliases....
1104     while (my ($Alias, $Name) = each %Is) {
1105         New_Alias(Is => $Alias, SameAs => $Name, Fuzzy => 1);
1106     }
1107 }
1108
1109 ##
1110 ## Writes the info accumulated in
1111 ##
1112 ##       %TableInfo;
1113 ##       %FuzzyNames;
1114 ##       %AliasInfo;
1115 ##
1116 ##
1117 sub WriteAllMappings()
1118 {
1119     my @MAP;
1120
1121     for my $Type ('In', 'Is')
1122     {
1123         my %Filenames;
1124         my %NameToFile;
1125
1126         my %Exact; ## will become %utf8::Is    or %utf8::In
1127         my %Pat;   ## will become %utf8::IsPat or %utf8::InPat
1128
1129         ##
1130         ## First write all the files to the $Type/ directory
1131         ##
1132         for my $Name (sort { length $a <=> length $b } keys %{$TableInfo{$Type}})
1133         {
1134             my $Table   = $TableInfo{$Type}->{$Name};
1135
1136             ## Need an 8.3 safe filename (which means "an 8 safe" $filename)
1137             my $filename = $FuzzyNames{$Type}->{$Name} ? CanonicalName($Name): $Name;
1138             $filename =~ s/[^\w_]+/_/g; # "L&" -> "L_"
1139             substr($filename, 8) = '' if length($filename) > 8;
1140
1141             ##
1142             ## Make sure the filename doesn't conflict with something we
1143             ## might have already written. If we have, say,
1144             ##     GreekExtended1
1145             ##     GreekExtended2
1146             ## they become
1147             ##     GreekExt
1148             ##     GreekEx2
1149             ##
1150             while (my $num = $Filenames{lc $filename}++)
1151             {
1152                 $num++; ## so filenames with numbers start with '2', which
1153                         ## just looks more natural.
1154                 ## Want to append $num, but if it'll make the filename longer
1155                 ## than 8 characters, pre-truncate $filename so that the result
1156                 ## is acceptable.
1157                 my $delta = length($filename) + length($num) - 8;
1158                 if ($delta > 0) {
1159                     substr($filename, -$delta) = $num;
1160                 } else {
1161                     $filename .= $num;
1162                 }
1163             }
1164
1165             $Exact{$Name} = $filename;
1166
1167             ##
1168             ## Construct a nice comment to add to the file, and build data
1169             ## for the "./Properties" file along the way.
1170             ##
1171             my $Comment;
1172             {
1173                 my $Desc = $TableDesc{$Type}->{$Name} || "";
1174                 ## get list of names this table is reference by
1175                 my @Supported = $Name;
1176                 while (my ($Orig, $Alias) = each %{ $AliasInfo{$Type} })
1177                 {
1178                     if ($Orig eq $Name) {
1179                         push @Supported, $Alias;
1180                     }
1181                 }
1182
1183                 my $TypeToShow = $Type eq 'Is' ? "" : $Type;
1184                 my $OrigProp;
1185
1186                 $Comment = "This file supports:\n";
1187                 for my $N (@Supported)
1188                 {
1189                     my $IsFuzzy = $FuzzyNames{$Type}->{$N};
1190                     my $CName   = $IsFuzzy ? CanonicalName($N): $N;
1191                     my $Prop    = "\\p{$TypeToShow$CName}";
1192                     $OrigProp = $Prop if not $OrigProp; #cache for aliases
1193                     if ($IsFuzzy) {
1194                         $Comment .= "\t$Prop (and fuzzy permutations)\n";
1195                     } else {
1196                         $Comment .= "\t$Prop\n";
1197                     }
1198                     my $MyDesc = ($N eq $Name) ? $Desc : "Alias for $OrigProp ($Desc)";
1199
1200                     push @MAP, sprintf("%s %-42s %s\n",
1201                                        $IsFuzzy ? '*' : ' ', $Prop, $MyDesc);
1202                 }
1203                 if ($Desc) {
1204                     $Comment .= "\nMeaning: $Desc\n";
1205                 }
1206
1207             }
1208             ##
1209             ## Okay, write the file...
1210             ##
1211             $Table->Write("$Type/$filename.pl", $Comment);
1212         }
1213
1214         ##
1215         ## Write out the map
1216         ##
1217         if (not open MAP, ">Properties") {
1218             die "$0: can't write Properties: $!\n";
1219         }
1220         print MAP "##\n";
1221         print MAP "## This file created by $0\n";
1222         print MAP "## List of built-in \\p{...}/\\P{...} properties.\n";
1223         print MAP "##\n";
1224         print MAP "## '*' means name may be 'fuzzy'\n";
1225         print MAP "##\n";
1226         print MAP "\n";
1227         print MAP sort { substr($a,2) cmp substr($b, 2) } @MAP;
1228         close MAP;
1229
1230         ##
1231         ## Build %Pat
1232         ##
1233         while (my ($Fuzzy, $Real) = each %{$FuzzyNames{$Type}})
1234         {
1235             my $File = $Exact{$Real};
1236
1237             if (not $File) {
1238                 die "$0: oops [$Real]";
1239             }
1240
1241             ## The prefix length of 2 is enough spread,
1242             ## and besides, we have 'Yi' as an In category.
1243             my $Prefix = lc(substr($Fuzzy, 0, 2));
1244             my $Regex = NameToRegex($Fuzzy);
1245
1246             if ($Pat{$Prefix}->{$Regex}) {
1247                 warn "WHOA, conflict with /$Regex/: $Pat{$Prefix}->{$Regex} vs $File\n";
1248             }
1249
1250             $Pat{$Prefix}->{$Regex} = $File;
1251         }
1252
1253         ##
1254         ## Since the fuzzy method will provide for a way to match $Fuzzy,
1255         ## there's no need for $Fuzzy to be in %Exact as well.
1256         ## This can't be done in the loop above because there could be
1257         ## multiple $Fuzzys pointing at the same $Real, and we don't want
1258         ## the first to delete the exact mapping out from under the second.
1259         ##
1260         for my $Fuzzy (keys %{$FuzzyNames{$Type}})
1261         {
1262             delete $Exact{$Fuzzy};
1263         }
1264
1265
1266
1267         ##
1268         ## Now write In.pl / Is.pl
1269         ##
1270         if (not open OUT, ">$Type.pl") {
1271             die "$0: $Type.pl: $!\n";
1272         }
1273         print OUT $HEADER;
1274         print OUT "##\n";
1275         print OUT "## Data in this file used by ../utf8_heavy.pl\n";
1276         print OUT "##\n";
1277         print OUT "\n";
1278         print OUT "## Mapping from name to filename in ./$Type\n";
1279         print OUT "%utf8::$Type = (\n";
1280         for my $Name (sort keys %Exact)
1281         {
1282             my $File = $Exact{$Name};
1283             printf OUT "  %-41s => %s,\n", "'$Name'", "'$File'";
1284         }
1285         print OUT ");\n\n";
1286
1287         print OUT "## Mappings from regex to filename in ./$Type/\n";
1288         print OUT "%utf8::${Type}Pat = (\n";
1289         for my $Prefix (sort keys %Pat)
1290         {
1291             print OUT " '$Prefix' => {\n";
1292             while (my ($Regex, $File) = each %{ $Pat{$Prefix} }) {
1293                 print OUT "\t'$Regex' => '$File',\n";
1294             }
1295             print OUT " },\n";
1296         }
1297         print OUT ");\n";
1298
1299         close(OUT);
1300     }
1301 }
1302
1303 sub SpecCase_txt()
1304 {
1305     #
1306     # Read in the special cases.
1307     #
1308
1309     my %CaseInfo;
1310
1311     if (not open IN, "SpecCase.txt") {
1312         die "$0: SpecCase.txt: $!\n";
1313     }
1314     while (<IN>) {
1315         next unless /^[0-9A-Fa-f]+;/;
1316         s/\#.*//;
1317         s/\s+$//;
1318
1319         my ($code, $lower, $title, $upper, $condition) = split(/\s*;\s*/);
1320
1321         if ($condition) { # not implemented yet
1322             print "# SKIPPING $_\n" if $Verbose;
1323             next;
1324         }
1325
1326         # Wait until all the special cases have been read since
1327         # they are not listed in numeric order.
1328         my $ix = hex($code);
1329         push @{$CaseInfo{Lower}}, [ $ix, $code, $lower ];
1330         push @{$CaseInfo{Title}}, [ $ix, $code, $title ];
1331         push @{$CaseInfo{Upper}}, [ $ix, $code, $upper ];
1332     }
1333     close IN;
1334
1335     # Now write out the special cases properties in their code point order.
1336     # Prepend them to the To/{Upper,Lower,Title}.pl.
1337
1338     for my $case (qw(Lower Title Upper))
1339     {
1340         my $NormalCase = do "To/$case.pl" || die "$0: $@\n";
1341         if (not open OUT, ">To/$case.pl") {
1342             die "$0: To/$case.txt: $!";
1343         }
1344
1345         print OUT $HEADER, "\n";
1346         print OUT "%utf8::ToSpec$case =\n(\n";
1347
1348         for my $prop (sort { $a->[0] <=> $b->[0] } @{$CaseInfo{$case}}) {
1349             my ($ix, $code, $to) = @$prop;
1350             my $tostr =
1351               join "", map { sprintf "\\x{%s}", $_ } split ' ', $to;
1352             printf OUT qq['%04X' => "$tostr",\n], $ix;
1353         }
1354         print OUT ");\n\n";
1355         print OUT "return <<'END';\n";
1356         print OUT $NormalCase;
1357         print OUT "END\n";
1358         close OUT;
1359     }
1360 }
1361
1362 #
1363 # Read in the case foldings.
1364 #
1365 # We will do full case folding, C + F + I (see CaseFold.txt).
1366 #
1367 sub CaseFold_txt()
1368 {
1369     if (not open IN, "CaseFold.txt") {
1370         die "$0: To/Fold.pl: $!\n";
1371     }
1372
1373     my $Fold = Table->New();
1374     my %Fold;
1375
1376     while (<IN>) {
1377         # Skip status 'S', simple case folding
1378         next unless /^([0-9A-Fa-f]+)\s*;\s*([CFI])\s*;\s*([0-9A-Fa-f]+(?: [0-9A-Fa-f]+)*)\s*;/;
1379
1380         my ($code, $status, $fold) = (hex($1), $2, $3);
1381
1382         if ($status eq 'C') { # Common: one-to-one folding
1383             # No append() since several codes may fold into one.
1384             $Fold->RawAppendRange($code, $code, $fold);
1385         } else { # F: full, or I: dotted uppercase I -> dotless lowercase I
1386             $Fold{$code} = $fold;
1387         }
1388     }
1389     close IN;
1390
1391     $Fold->Write("To/Fold.pl");
1392
1393     #
1394     # Prepend the special foldings to the common foldings.
1395     #
1396
1397     my $CommonFold = do "To/Fold.pl" || die "$0: To/Fold.pl: $!\n";
1398     if (not open OUT, ">To/Fold.pl") {
1399         die "$0: To/Fold.pl: $!\n";
1400     }
1401     print OUT $HEADER, "\n";
1402     print OUT "%utf8::ToSpecFold =\n(\n";
1403     for my $code (sort { $a <=> $b } keys %Fold) {
1404         my $foldstr =
1405           join "", map { sprintf "\\x{%s}", $_ } split ' ', $Fold{$code};
1406         printf OUT qq['%04X' => "$foldstr",\n], $code;
1407     }
1408     print OUT ");\n\n";
1409     print OUT "return <<'END';\n";
1410     print OUT $CommonFold;
1411     print OUT "END\n";
1412     close OUT;
1413 }
1414
1415 ## Do it....
1416
1417 Unicode_Txt();
1418 Make_GC_Aliases();
1419 PropList_txt();
1420
1421 Scripts_txt();
1422 Blocks_txt();
1423
1424 LineBrk_Txt();
1425 ArabShap_txt();
1426 Jamo_txt();
1427 SpecCase_txt();
1428
1429 WriteAllMappings();
1430
1431 CaseFold_txt();
1432
1433 # That's all, folks!
1434
1435 __END__