S_utf16_textfilter() was not returning EOF correctly in some situations.
[p5sagit/p5-mst-13.2.git] / lib / unicore / mktables
1 ## !!!!!!!!!!!!!!       IF YOU MODIFY THIS FILE       !!!!!!!!!!!!!!!!!!!!!!!!!
2 ## Any files created or read by this program should be listed in 'mktables.lst'
3
4 #!/usr/bin/perl -w
5 require 5.008;  # Needs pack "U". Probably safest to run on 5.8.x
6 use strict;
7 use Carp;
8 use File::Spec;
9 use Text::Tabs ();  ## using this makes the files about half the size
10
11 ##
12 ## mktables -- create the runtime Perl Unicode files (lib/unicore/**/*.pl)
13 ## from the Unicode database files (lib/unicore/*.txt).
14 ##
15
16 ## "Fuzzy" means this section in Unicode TR18:
17 ##
18 ##    The recommended names for UCD properties and property values are in
19 ##    PropertyAliases.txt [Prop] and PropertyValueAliases.txt
20 ##    [PropValue]. There are both abbreviated names and longer, more
21 ##    descriptive names. It is strongly recommended that both names be
22 ##    recognized, and that loose matching of property names be used,
23 ##    whereby the case distinctions, whitespace, hyphens, and underbar
24 ##    are ignored.
25
26 ## Base names already used in lib/gc_sc (for avoiding 8.3 conflicts)
27 my %BaseNames;
28
29 ##
30 ## Process any args.
31 ##
32 my $Verbose        = 0;
33 my $MakeTestScript = 0;
34 my $AlwaysWrite    = 0;
35 my $UseDir         = "";
36 my $FileList       = "$0.lst";
37 my $MakeList       = 0;
38
39 while (@ARGV)
40 {
41     my $arg = shift @ARGV;
42     if ($arg eq '-v') {
43         $Verbose = 1;
44     } elsif ($arg eq '-q') {
45         $Verbose = 0;
46     } elsif ($arg eq '-w') {
47         $AlwaysWrite = 1;       # update the files even if they havent changed
48         $FileList = "";
49     } elsif ($arg eq '-check') {
50         my $this = shift @ARGV;
51         my $ok = shift @ARGV;
52         if ($this ne $ok) {
53             print "Skipping as check params are not the same.\n";
54             exit(0);
55         }
56     } elsif ($arg eq '-maketest') {
57         $MakeTestScript = 1;
58     } elsif ($arg eq '-makelist') {
59         $MakeList = 1;        
60     } elsif ($arg eq '-C' && defined ($UseDir = shift)) {
61         -d $UseDir or die "Unknown directory '$UseDir'";
62     } elsif ($arg eq '-L' && defined ($FileList = shift)) {
63         -e $FileList or die "Filelist '$FileList' doesn't appear to exist!";
64     } else {
65         die "usage: $0 [-v|-q|-w|-C dir|-L filelist] [-maketest] [-makelist]\n",
66             "  -v          : Verbose Mode\n",
67             "  -q          : Quiet Mode\n",
68             "  -w          : Write files regardless\n",
69             "  -maketest   : Make test script\n",
70             "  -makelist   : Rewrite the file list based on current setup\n",
71             "  -L filelist : Use this file list, (defaults to $0.lst)\n",
72             "  -C dir      : Change to this directory before proceeding\n",
73             "  -check A B  : Executes only if A and B are the same\n";   
74     }
75 }
76
77 if ($FileList) {
78     print "Reading file list '$FileList'\n"
79         if $Verbose;
80     open my $fh,"<",$FileList or die "Failed to read '$FileList':$!";
81     my @input;
82     my @output;
83     for my $list ( \@input, \@output ) {
84         while (<$fh>) {
85             s/^ \s+ | \s+ $//xg;
86             next if /^ \s* (?: \# .* )? $/x;
87             last if /^ =+ $/x;
88             my ( $file ) = split /\t/, $_;
89             push @$list, $file;
90         }
91         my %dupe;
92         @$list = grep !$dupe{ $_ }++, @$list;
93     }
94     close $fh;
95     die "No input or output files in '$FileList'!"
96         if !@input or !@output;
97     if ( $MakeList ) {
98         foreach my $file (@output) {
99             unlink $file;
100         }
101     }            
102     if ( $Verbose ) {
103         print "Expecting ".scalar( @input )." input files. ",
104               "Checking ".scalar( @output )." output files.\n";
105     }
106     # we set maxtime to be the youngest input file, including $0 itself.
107     my $maxtime = -M $0; # do this before the chdir!
108     if ($UseDir) {
109         chdir $UseDir or die "Failed to chdir to '$UseDir':$!";
110     }
111     foreach my $in (@input) {
112         my $time = -M $in;
113         die "Missing input file '$in'" unless defined $time;
114         $maxtime = $time if $maxtime < $time;
115     }
116
117     # now we check to see if any output files are older than maxtime, if
118     # they are we need to continue on, otherwise we can presumably bail.
119     my $ok = 1;
120     foreach my $out (@output) {
121         if ( ! -e $out ) {
122             print "'$out' is missing.\n"
123                 if $Verbose;
124             $ok = 0;
125             last;
126         }
127         if ( -M $out > $maxtime ) {
128             print "'$out' is too old.\n"
129                 if $Verbose;
130             $ok = 0;
131             last;
132         }
133     }
134     if ($ok) {
135         print "Files seem to be ok, not bothering to rebuild.\n";
136         exit(0);
137     }
138     print "Must rebuild tables.\n"
139         if $Verbose;
140 } else {
141     if ($Verbose) {
142         print "Not checking filelist.\n";
143     }
144     if ($UseDir) {
145         chdir $UseDir or die "Failed to chdir to '$UseDir':$!";
146     }
147 }
148
149 foreach my $lib ('To', 'lib',
150                  map {File::Spec->catdir("lib",$_)}
151                  qw(gc_sc dt bc hst ea jt lb nt ccc)) {
152   next if -d $lib;
153   mkdir $lib, 0755 or die "mkdir '$lib': $!";
154 }
155
156 my $LastUnicodeCodepoint = 0x10FFFF; # As of Unicode 5.1.
157
158 my $HEADER=<<"EOF";
159 # !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!! 
160 # This file is built by $0 from e.g. UnicodeData.txt.
161 # Any changes made here will be lost!
162
163 EOF
164
165 my $INTERNAL_ONLY=<<"EOF";
166 # This file is for internal use by the Perl program only.  The format and even
167 # name or existence of this file are subject to change without notice.  Don't
168 # use it directly.
169
170 EOF
171
172 sub force_unlink {
173     my $filename = shift;
174     return unless -e $filename;
175     return if CORE::unlink($filename);
176     # We might need write permission
177     chmod 0777, $filename;
178     CORE::unlink($filename) or die "Couldn't unlink $filename: $!\n";
179 }
180
181 ##
182 ## Given a filename and a reference to an array of lines,
183 ## write the lines to the file only if the contents have not changed.
184 ## Filename can be given as an arrayref of directory names
185 ##
186 sub WriteIfChanged($\@)
187 {
188     my $file  = shift;
189     my $lines = shift;
190
191     $file = File::Spec->catfile(@$file) if ref $file;
192
193     my $TextToWrite = join '', @$lines;
194     if (open IN, $file) {
195         local($/) = undef;
196         my $PreviousText = <IN>;
197         close IN;
198         if ($PreviousText eq $TextToWrite) {
199             print "$file unchanged.\n" if $Verbose;
200             return unless $AlwaysWrite;
201         }
202     }
203     force_unlink ($file);
204     if (not open OUT, ">$file") {
205         die "$0: can't open $file for output: $!\n";
206     }
207     print "$file written.\n" if $Verbose;
208
209     print OUT $TextToWrite;
210     close OUT;
211 }
212
213 ##
214 ## The main datastructure (a "Table") represents a set of code points that
215 ## are part of a particular quality (that are part of \pL, \p{InGreek},
216 ## etc.). They are kept as ranges of code points (starting and ending of
217 ## each range).
218 ##
219 ## For example, a range ASCII LETTERS would be represented as:
220 ##   [ [ 0x41 => 0x5A, 'UPPER' ],
221 ##     [ 0x61 => 0x7A, 'LOWER, ] ]
222 ##
223 sub RANGE_START() { 0 } ## index into range element
224 sub RANGE_END()   { 1 } ## index into range element
225 sub RANGE_NAME()  { 2 } ## index into range element
226
227 ## Conceptually, these should really be folded into the 'Table' objects
228 my %TableInfo;
229 my %TableDesc;
230 my %FuzzyNames;
231 my %AliasInfo;
232 my %CanonicalToOrig;
233
234 ##
235 ## Turn something like
236 ##    OLD-ITALIC
237 ## into
238 ##    OldItalic
239 ##
240 sub CanonicalName($)
241 {
242     my $orig = shift;
243     my $name = lc $orig;
244     $name =~ s/(?<![a-z])(\w)/\u$1/g;
245     $name =~ s/[-_\s]+//g;
246
247     $CanonicalToOrig{$name} = $orig if not $CanonicalToOrig{$name};
248     return $name;
249 }
250
251
252 ##
253 ## Store the alias definitions for later use.
254 ##
255 my %PropertyAlias;
256 my %PropValueAlias;
257
258 my %PA_reverse;
259 my %PVA_reverse;
260
261 sub Build_Aliases()
262 {
263     ##
264     ## Most of the work with aliases doesn't occur here,
265     ## but rather in utf8_heavy.pl, which uses PVA.pl,
266
267     # Placate the warnings about used only once. (They are used again, but
268     # via a typeglob lookup)
269     %utf8::PropertyAlias = ();
270     %utf8::PA_reverse = ();
271     %utf8::PropValueAlias = ();
272     %utf8::PVA_reverse = ();
273     %utf8::PVA_abbr_map = ();
274
275     open PA, "< PropertyAliases.txt"
276         or confess "Can't open PropertyAliases.txt: $!";
277     while (<PA>) {
278         s/#.*//;
279         s/\s+$//;
280         next if /^$/;
281
282         my ($abbrev, $name) = split /\s*;\s*/;
283         next if $abbrev eq "n/a";
284         $PropertyAlias{$abbrev} = $name;
285         $PA_reverse{$name} = $abbrev;
286
287         # The %utf8::... versions use japhy's code originally from utf8_pva.pl
288         # However, it's moved here so that we build the tables at runtime.
289         tr/ _-//d for $abbrev, $name;
290         $utf8::PropertyAlias{lc $abbrev} = $name;
291         $utf8::PA_reverse{lc $name} = $abbrev;
292     }
293     close PA;
294
295     open PVA, "< PropValueAliases.txt"
296         or confess "Can't open PropValueAliases.txt: $!";
297     while (<PVA>) {
298         s/#.*//;
299         s/\s+$//;
300         next if /^$/;
301
302         my ($prop, @data) = split /\s*;\s*/;
303
304         if ($prop eq 'ccc') {
305             $PropValueAlias{$prop}{$data[1]} = [ @data[0,2] ];
306             $PVA_reverse{$prop}{$data[2]} = [ @data[0,1] ];
307         }
308         else {
309             next if $data[0] eq "n/a";
310             $PropValueAlias{$prop}{$data[0]} = $data[1];
311             $PVA_reverse{$prop}{$data[1]} = $data[0];
312         }
313
314         shift @data if $prop eq 'ccc';
315         next if $data[0] eq "n/a";
316
317         $data[1] =~ tr/ _-//d;
318         $utf8::PropValueAlias{$prop}{lc $data[0]} = $data[1];
319         $utf8::PVA_reverse{$prop}{lc $data[1]} = $data[0];
320
321         my $abbr_class = ($prop eq 'gc' or $prop eq 'sc') ? 'gc_sc' : $prop;
322         $utf8::PVA_abbr_map{$abbr_class}{lc $data[0]} = $data[0];
323     }
324     close PVA;
325
326     # backwards compatibility for L& -> LC
327     $utf8::PropValueAlias{gc}{'l&'} = $utf8::PropValueAlias{gc}{lc};
328     $utf8::PVA_abbr_map{gc_sc}{'l&'} = $utf8::PVA_abbr_map{gc_sc}{lc};
329
330 }
331
332
333 ##
334 ## Associates a property ("Greek", "Lu", "Assigned",...) with a Table.
335 ##
336 ## Called like:
337 ##       New_Prop(In => 'Greek', $Table, Desc => 'Greek Block', Fuzzy => 1);
338 ##
339 ## Normally, these parameters are set when the Table is created (when the
340 ## Table->New constructor is called), but there are times when it needs to
341 ## be done after-the-fact...)
342 ##
343 sub New_Prop($$$@)
344 {
345     my $Type = shift; ## "Is" or "In";
346     my $Name = shift;
347     my $Table = shift;
348
349     ## remaining args are optional key/val
350     my %Args = @_;
351
352     my $Fuzzy = delete $Args{Fuzzy};
353     my $Desc  = delete $Args{Desc}; # description
354
355     $Name = CanonicalName($Name) if $Fuzzy;
356
357     ## sanity check a few args
358     if (%Args or ($Type ne 'Is' and $Type ne 'In') or not ref $Table) {
359         confess "$0: bad args to New_Prop"
360     }
361
362     if (not $TableInfo{$Type}->{$Name})
363     {
364         $TableInfo{$Type}->{$Name} = $Table;
365         $TableDesc{$Type}->{$Name} = $Desc;
366         if ($Fuzzy) {
367             $FuzzyNames{$Type}->{$Name} = $Name;
368         }
369     }
370 }
371
372
373 ##
374 ## Creates a new Table object.
375 ##
376 ## Args are key/value pairs:
377 ##    In => Name         -- Name of "In" property to be associated with
378 ##    Is => Name         -- Name of "Is" property to be associated with
379 ##    Fuzzy => Boolean   -- True if name can be accessed "fuzzily"
380 ##    Desc  => String    -- Description of the property
381 ##
382 ## No args are required.
383 ##
384 sub Table::New
385 {
386     my $class = shift;
387     my %Args = @_;
388
389     my $Table = bless [], $class;
390
391     my $Fuzzy = delete $Args{Fuzzy};
392     my $Desc  = delete $Args{Desc};
393
394     for my $Type ('Is', 'In')
395     {
396         if (my $Name = delete $Args{$Type}) {
397             New_Prop($Type => $Name, $Table, Desc => $Desc, Fuzzy => $Fuzzy);
398         }
399     }
400
401     ## shouldn't have any left over
402     if (%Args) {
403         confess "$0: bad args to Table->New"
404     }
405
406     return $Table;
407 }
408
409
410 ##
411 ## Returns the maximum code point currently in the table.
412 ##
413 sub Table::Max
414 {
415     my $last = $_[0]->[-1];      ## last code point
416     confess "oops" unless $last; ## must have code points to have a max
417     return $last->[RANGE_END];
418 }
419
420 ##
421 ## Replaces the codepoints in the Table with those in the Table given
422 ## as an arg. (NOTE: this is not a "deep copy").
423 ##
424 sub Table::Replace($$)
425 {
426     my $Table = shift; #self
427     my $New   = shift;
428
429     @$Table = @$New;
430 }
431
432 ##
433 ## Given a new code point, make the last range of the Table extend to
434 ## include the new (and all intervening) code points.
435 ##
436 ## Takes the time to make sure that the extension is valid.
437 ##
438 sub Table::Extend
439 {
440     my $Table = shift; #self
441     my $codepoint = shift;
442
443     my $PrevMax = $Table->Max;
444
445     confess "oops ($codepoint <= $PrevMax)" if $codepoint <= $PrevMax;
446
447     $Table->ExtendNoCheck($codepoint);
448 }
449
450
451 ##
452 ## Given a new code point, make the last range of the Table extend to
453 ## include the new (and all intervening) code points.
454 ##
455 ## Does NOT check that the extension is valid.  Assumes that the caller
456 ## has already made this check.
457 ##
458 sub Table::ExtendNoCheck
459 {
460     ## Optmized adding: Assumes $Table and $codepoint as parms
461     $_[0]->[-1]->[RANGE_END] = $_[1];
462 }
463
464 ##
465 ## Given a code point range start and end (and optional name), blindly
466 ## append them to the list of ranges for the Table.
467 ##
468 ## NOTE: Code points must be added in strictly ascending numeric order.
469 ##
470 sub Table::RawAppendRange
471 {
472     my $Table = shift; #self
473     my $start = shift;
474     my $end   = shift;
475     my $name  = shift;
476     $name = "" if not defined $name; ## warning: $name can be "0"
477
478     push @$Table, [ $start,    # RANGE_START
479                     $end,      # RANGE_END
480                     $name   ]; # RANGE_NAME
481 }
482
483 ##
484 ## Given a code point (and optional name), add it to the Table.
485 ##
486 ## NOTE: Code points must be added in strictly ascending numeric order.
487 ##
488 sub Table::Append
489 {
490     my $Table     = shift; #self
491     my $codepoint = shift;
492     my $name      = shift;
493     $name = "" if not defined $name; ## warning: $name can be "0"
494
495     ##
496     ## If we've already got a range working, and this code point is the next
497     ## one in line, and if the name is the same, just extend the current range.
498     ##
499     my $last = $Table->[-1];
500     if ($last
501         and
502         $last->[RANGE_END] == $codepoint - 1
503         and
504         $last->[RANGE_NAME] eq $name)
505     {
506         $Table->ExtendNoCheck($codepoint);
507     }
508     else
509     {
510         $Table->RawAppendRange($codepoint, $codepoint, $name);
511     }
512 }
513
514 ##
515 ## Given a code point range starting value and ending value (and name),
516 ## Add the range to the Table.
517 ##
518 ## NOTE: Code points must be added in strictly ascending numeric order.
519 ##
520 sub Table::AppendRange
521 {
522     my $Table = shift; #self
523     my $start = shift;
524     my $end   = shift;
525     my $name  = shift;
526     $name = "" if not defined $name; ## warning: $name can be "0"
527
528     $Table->Append($start, $name);
529     $Table->Extend($end) if $end > $start;
530 }
531
532 ##
533 ## Return a new Table that represents all code points not in the Table.
534 ##
535 sub Table::Invert
536 {
537     my $Table = shift; #self
538
539     my $New = Table->New();
540     my $max = -1;
541     for my $range (@$Table)
542     {
543         my $start = $range->[RANGE_START];
544         my $end   = $range->[RANGE_END];
545         if ($start-1 >= $max+1) {
546             $New->AppendRange($max+1, $start-1, "");
547         }
548         $max = $end;
549     }
550     if ($max+1 < $LastUnicodeCodepoint) {
551         $New->AppendRange($max+1, $LastUnicodeCodepoint);
552     }
553     return $New;
554 }
555
556 ##
557 ## Merges any number of other tables with $self, returning the new table.
558 ## (existing tables are not modified)
559 ##
560 ##
561 ## Args may be Tables, or individual code points (as integers).
562 ##
563 ## Can be called as either a constructor or a method.
564 ##
565 sub Table::Merge
566 {
567     shift(@_) if not ref $_[0]; ## if called as a constructor, lose the class
568     my @Tables = @_;
569
570     ## Accumulate all records from all tables
571     my @Records;
572     for my $Arg (@Tables)
573     {
574         if (ref $Arg) {
575             ## arg is a table -- get its ranges
576             push @Records, @$Arg;
577         } else {
578             ## arg is a codepoint, make a range
579             push @Records, [ $Arg, $Arg ]
580         }
581     }
582
583     ## sort by range start, with longer ranges coming first.
584     my ($first, @Rest) = sort {
585         ($a->[RANGE_START] <=> $b->[RANGE_START])
586           or
587         ($b->[RANGE_END]   <=> $b->[RANGE_END])
588     } @Records;
589
590     my $New = Table->New();
591
592     ## Ensuring the first range is there makes the subsequent loop easier
593     $New->AppendRange($first->[RANGE_START],
594                       $first->[RANGE_END]);
595
596     ## Fold in records so long as they add new information.
597     for my $set (@Rest)
598     {
599         my $start = $set->[RANGE_START];
600         my $end   = $set->[RANGE_END];
601         if ($start > $New->Max) {
602             $New->AppendRange($start, $end);
603         } elsif ($end > $New->Max) {
604             $New->ExtendNoCheck($end);
605         }
606     }
607
608     return $New;
609 }
610
611 ##
612 ## Given a filename, write a representation of the Table to a file.
613 ## May have an optional comment as a 2nd arg.
614 ## Filename may actually be an arrayref of directories
615 ##
616 sub Table::Write
617 {
618     my $Table    = shift; #self
619     my $filename = shift;
620     my $comment  = shift;
621
622     my @OUT = $HEADER;
623
624     # files in subdirectories are internal-use-only
625     push @OUT, $INTERNAL_ONLY if ref $filename;
626
627     if (defined $comment) {
628         $comment =~ s/\s+\Z//;
629         $comment =~ s/^/# /gm;
630         push @OUT, "#\n$comment\n#\n";
631     }
632     push @OUT, "return <<'END';\n";
633
634     for my $set (@$Table)
635     {
636         my $start = $set->[RANGE_START];
637         my $end   = $set->[RANGE_END];
638         my $name  = $set->[RANGE_NAME];
639
640         if ($start == $end) {
641             push @OUT, sprintf "%04X\t\t%s\n", $start, $name;
642         } else {
643             push @OUT, sprintf "%04X\t%04X\t%s\n", $start, $end, $name;
644         }
645     }
646
647     push @OUT, "END\n";
648
649     WriteIfChanged($filename, @OUT);
650 }
651
652 ## This used only for making the test script.
653 ## helper function
654 sub IsUsable($)
655 {
656     my $code = shift;
657     return 0 if $code <= 0x0000;                       ## don't use null
658     return 0 if $code >= $LastUnicodeCodepoint;        ## keep in range
659     return 0 if ($code >= 0xD800 and $code <= 0xDFFF); ## no surrogates
660     return 0 if ($code >= 0xFDD0 and $code <= 0xFDEF); ## utf8.c says no good
661     return 0 if (($code & 0xFFFF) == 0xFFFE);          ## utf8.c says no good
662     return 0 if (($code & 0xFFFF) == 0xFFFF);          ## utf8.c says no good
663     return 1;
664 }
665
666 ## Return a code point that's part of the table.
667 ## Returns nothing if the table is empty (or covers only surrogates).
668 ## This used only for making the test script.
669 sub Table::ValidCode
670 {
671     my $Table = shift; #self
672     for my $set (@$Table) {
673         return $set->[RANGE_END] if IsUsable($set->[RANGE_END]);
674     }
675     return ();
676 }
677
678 ## Return a code point that's not part of the table
679 ## Returns nothing if the table covers all code points.
680 ## This used only for making the test script.
681 sub Table::InvalidCode
682 {
683     my $Table = shift; #self
684
685     return 0x1234 if not @$Table;
686
687     for my $set (@$Table)
688     {
689         if (IsUsable($set->[RANGE_END] + 1))
690         {
691             return $set->[RANGE_END] + 1;
692         }
693
694         if (IsUsable($set->[RANGE_START] - 1))
695         {
696             return $set->[RANGE_START] - 1;
697         }
698     }
699     return ();
700 }
701
702 ###########################################################################
703 ###########################################################################
704 ###########################################################################
705
706
707 ##
708 ## Called like:
709 ##     New_Alias(Is => 'All', SameAs => 'Any', Fuzzy => 1);
710 ##
711 ## The args must be in that order, although the Fuzzy pair may be omitted.
712 ##
713 ## This creates 'IsAll' as an alias for 'IsAny'
714 ##
715 sub New_Alias($$$@)
716 {
717     my $Type   = shift; ## "Is" or "In"
718     my $Alias  = shift;
719     my $SameAs = shift; # expecting "SameAs" -- just ignored
720     my $Name   = shift;
721
722     ## remaining args are optional key/val
723     my %Args = @_;
724
725     my $Fuzzy = delete $Args{Fuzzy};
726
727     ## sanity check a few args
728     if (%Args or ($Type ne 'Is' and $Type ne 'In') or $SameAs ne 'SameAs') {
729         confess "$0: bad args to New_Alias"
730     }
731
732     $Alias = CanonicalName($Alias) if $Fuzzy;
733
734     if (not $TableInfo{$Type}->{$Name})
735     {
736         my $CName = CanonicalName($Name);
737         if ($TableInfo{$Type}->{$CName}) {
738             confess "$0: Use canonical form '$CName' instead of '$Name' for alias.";
739         } else {
740             confess "$0: don't have original $Type => $Name to make alias\n";
741         }
742     }
743     if ($TableInfo{$Alias}) {
744         confess "$0: already have original $Type => $Alias; can't make alias";
745     }
746     $AliasInfo{$Type}->{$Name} = $Alias;
747     if ($Fuzzy) {
748         $FuzzyNames{$Type}->{$Alias} = $Name;
749     }
750
751 }
752
753
754 ## All assigned code points
755 my $Assigned = Table->New(Is    => 'Assigned',
756                           Desc  => "All assigned code points",
757                           Fuzzy => 0);
758
759 my $Name     = Table->New(); ## all characters, individually by name
760 my $General  = Table->New(); ## all characters, grouped by category
761 my %General;
762 my %Cat;
763
764 ## Simple Data::Dumper like. Good enough for our needs. We can't use the real
765 ## thing as we have to run under miniperl
766 sub simple_dumper {
767     my @lines;
768     my $item;
769     foreach $item (@_) {
770         if (ref $item) {
771             if (ref $item eq 'ARRAY') {
772                 push @lines, "[\n", simple_dumper (@$item), "],\n";
773             } elsif (ref $item eq 'HASH') {
774                 push @lines, "{\n", simple_dumper (%$item), "},\n";
775             } else {
776                 die "Can't cope with $item";
777             }
778         } else {
779             if (defined $item) {
780                 my $copy = $item;
781                 $copy =~ s/([\'\\])/\\$1/gs;
782                 push @lines, "'$copy',\n";
783             } else {
784                 push @lines, "undef,\n";
785             }
786         }
787     }
788     @lines;
789 }
790
791 ##
792 ## Process UnicodeData.txt (Categories, etc.)
793 ##
794 # These are the character mappings as defined in the POSIX standard
795 # and in the case of PerlSpace and PerlWord as is defined in the test macros
796 # for binary strings. IOW, PerlWord is [A-Za-z_] and PerlSpace is [\f\r\n\t ]
797 # This differs from Word and the existing SpacePerl (note the prefix/suffix difference)
798 # which is basically the Unicode WhiteSpace without the vertical tab included
799 #
800 my %TRUE_POSIX_PERL_CC= (
801     PosixAlnum => { map { $_ => 1 } ( 0x0030..0x0039, 0x0041..0x005a, 0x0061..0x007a )},
802     PosixAlpha => { map { $_ => 1 } ( 0x0041..0x005a, 0x0061..0x007a )},
803     # Not Needed: Ascii => { map { $_ => 1 } ( 0x0000..0x007f )},
804     PosixBlank => { map { $_ => 1 } ( 0x0009, 0x0020 )},
805     PosixCntrl => { map { $_ => 1 } ( 0x0000..0x001f, 0x007f )},
806     PosixGraph => { map { $_ => 1 } ( 0x0021..0x007e )},
807     PosixLower => { map { $_ => 1 } ( 0x0061..0x007a )},
808     PosixPrint => { map { $_ => 1 } ( 0x0020..0x007e )},
809     PosixPunct => { map { $_ => 1 } ( 0x0021..0x002f, 0x003a..0x0040, 0x005b..0x0060, 0x007b..0x007e )},
810     PosixSpace => { map { $_ => 1 } ( 0x0009..0x000d, 0x0020 )},
811     PosixUpper => { map { $_ => 1 } ( 0x0041..0x005a )},
812     # Not needed:  PosixXdigit => { map { $_ => 1 } ( 0x0030..0x0039, 0x0041..0x0046, 0x0061..0x0066 )},
813     PosixDigit => { map { $_ => 1 } ( 0x0030..0x0039 )},
814     
815     PerlSpace  => { map { $_ => 1 } ( 0x0009..0x000a, 0x000c..0x000d, 0x0020 )},
816     PerlWord   => { map { $_ => 1 } ( 0x0030..0x0039, 0x0041..0x005a, 0x005f, 0x0061..0x007a )},
817 );
818
819 sub UnicodeData_Txt()
820 {
821     my $Bidi     = Table->New();
822     my $Deco     = Table->New();
823     my $Comb     = Table->New();
824     my $Number   = Table->New();
825     my $Mirrored = Table->New();#Is    => 'Mirrored',
826                               #Desc  => "Mirrored in bidirectional text",
827                               #Fuzzy => 0);
828
829     my %DC;
830     my %Bidi;
831     my %Number;
832     $DC{Can} = Table->New();
833     $DC{Com} = Table->New();
834
835     ## Initialize Broken Perl-generated categories
836     ## (Categories from UnicodeData.txt are auto-initialized in gencat)
837     $Cat{Alnum}  =
838         Table->New(Is => 'Alnum',  Desc => "[[:Alnum:]]",  Fuzzy => 0);
839     $Cat{Alpha}  =
840         Table->New(Is => 'Alpha',  Desc => "[[:Alpha:]]",  Fuzzy => 0);
841     $Cat{ASCII}  =
842         Table->New(Is => 'ASCII',  Desc => "[[:ASCII:]]",  Fuzzy => 0);
843     $Cat{Blank}  =
844         Table->New(Is => 'Blank',  Desc => "[[:Blank:]]",  Fuzzy => 0);
845     $Cat{Cntrl}  =
846         Table->New(Is => 'Cntrl',  Desc => "[[:Cntrl:]]",  Fuzzy => 0);
847     $Cat{Digit}  =
848         Table->New(Is => 'Digit',  Desc => "[[:Digit:]]",  Fuzzy => 0);
849     $Cat{Graph}  =
850         Table->New(Is => 'Graph',  Desc => "[[:Graph:]]",  Fuzzy => 0);
851     $Cat{Lower}  =
852         Table->New(Is => 'Lower',  Desc => "[[:Lower:]]",  Fuzzy => 0);
853     $Cat{Print}  =
854         Table->New(Is => 'Print',  Desc => "[[:Print:]]",  Fuzzy => 0);
855     $Cat{Punct}  =
856         Table->New(Is => 'Punct',  Desc => "[[:Punct:]]",  Fuzzy => 0);
857     $Cat{Space}  =
858         Table->New(Is => 'Space',  Desc => "[[:Space:]]",  Fuzzy => 0);
859     $Cat{Title}  =
860         Table->New(Is => 'Title',  Desc => "[[:Title:]]",  Fuzzy => 0);
861     $Cat{Upper}  =
862         Table->New(Is => 'Upper',  Desc => "[[:Upper:]]",  Fuzzy => 0);
863     $Cat{XDigit} =
864         Table->New(Is => 'XDigit', Desc => "[[:XDigit:]]", Fuzzy => 0);
865     $Cat{Word}   =
866         Table->New(Is => 'Word',   Desc => "[[:Word:]]",   Fuzzy => 0);
867     $Cat{SpacePerl} =
868         Table->New(Is => 'SpacePerl', Desc => '\s', Fuzzy => 0);
869     $Cat{VertSpace} =
870         Table->New(Is => 'VertSpace', Desc => '\v', Fuzzy => 0);
871     $Cat{HorizSpace} =
872         Table->New(Is => 'HorizSpace', Desc => '\h', Fuzzy => 0);
873     my %To;
874     $To{Upper} = Table->New();
875     $To{Lower} = Table->New();
876     $To{Title} = Table->New();
877     $To{Digit} = Table->New();
878
879     foreach my $cat (keys %TRUE_POSIX_PERL_CC) {
880         $Cat{$cat} = Table->New(Is=>$cat, Fuzzy => 0);
881     }
882
883     sub gencat($$$$)
884     {
885         my ($name, ## Name ("LATIN CAPITAL LETTER A")
886             $cat,  ## Category ("Lu", "Zp", "Nd", etc.)
887             $code, ## Code point (as an integer)
888             $op) = @_;
889
890         my $MajorCat = substr($cat, 0, 1); ## L, M, Z, S, etc
891
892         $Assigned->$op($code);
893         $Name->$op($code, $name);
894         $General->$op($code, $cat);
895
896         ## add to the sub category (e.g. "Lu", "Nd", "Cf", ..)
897         $Cat{$cat}      ||= Table->New(Is   => $cat,
898                                        Desc => "General Category '$cat'",
899                                        Fuzzy => 0);
900         $Cat{$cat}->$op($code);
901
902         ## add to the major category (e.g. "L", "N", "C", ...)
903         $Cat{$MajorCat} ||= Table->New(Is => $MajorCat,
904                                        Desc => "Major Category '$MajorCat'",
905                                        Fuzzy => 0);
906         $Cat{$MajorCat}->$op($code);
907
908         ($General{$name} ||= Table->New)->$op($code, $name);
909
910         # 005F: SPACING UNDERSCORE
911         $Cat{Word}->$op($code)  if $cat =~ /^[LMN]|Pc/;
912         $Cat{Alnum}->$op($code) if $cat =~ /^[LM]|Nd/;
913         $Cat{Alpha}->$op($code) if $cat =~ /^[LM]/;
914
915         my $isspace = 
916             ($cat =~ /Zs|Zl|Zp/ &&
917              $code != 0x200B)   # 200B is ZWSP which is for line break control
918                                 # and therefore it is not part of "space" even
919                                 # while it is "Zs" in some versions of Unicode.
920                                 # In 5.1 it is Cf, so this line is no longer
921                                 # necessary.
922                                 || $code == 0x0009  # 0009: HORIZONTAL TAB
923                                 || $code == 0x000A  # 000A: LINE FEED
924                                 || $code == 0x000B  # 000B: VERTICAL TAB
925                                 || $code == 0x000C  # 000C: FORM FEED
926                                 || $code == 0x000D  # 000D: CARRIAGE RETURN
927                                 || $code == 0x0085  # 0085: NEL
928
929             ;
930
931         $Cat{Space}->$op($code) if $isspace;
932
933         $Cat{SpacePerl}->$op($code) if $isspace
934                                        && $code != 0x000B; # Backward compat.
935
936         $Cat{VertSpace}->$op($code) if grep {$code == $_} 
937             ( 0x0A..0x0D,0x85,0x2028,0x2029 );
938
939         $Cat{HorizSpace}->$op($code) if grep {$code == $_} (
940             0x09,   0x20,   0xa0,   0x1680, 0x180e, 0x2000, 0x2001, 0x2002,
941             0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
942             0x202f, 0x205f, 0x3000
943         ); 
944
945         $Cat{Blank}->$op($code) if $isspace
946                                 && !($code == 0x000A ||
947                                      $code == 0x000B ||
948                                      $code == 0x000C ||
949                                      $code == 0x000D ||
950                                      $code == 0x0085 ||
951                                      $cat =~ /^Z[lp]/);
952
953         $Cat{Digit}->$op($code) if $cat eq "Nd";
954         $Cat{Upper}->$op($code) if $cat eq "Lu";
955         $Cat{Lower}->$op($code) if $cat eq "Ll";
956         $Cat{Title}->$op($code) if $cat eq "Lt";
957         $Cat{ASCII}->$op($code) if $code <= 0x007F;
958         $Cat{Cntrl}->$op($code) if $cat =~ /^C/;
959         my $isgraph = !$isspace && $cat !~ /Cc|Cs|Cn/;
960         $Cat{Graph}->$op($code) if $isgraph;
961         $Cat{Print}->$op($code) if $isgraph || $isspace;
962         $Cat{Punct}->$op($code) if $cat =~ /^P/;
963
964         $Cat{XDigit}->$op($code) if ($code >= 0x30 && $code <= 0x39)  ## 0..9
965                                  || ($code >= 0x41 && $code <= 0x46)  ## A..F
966                                  || ($code >= 0x61 && $code <= 0x66); ## a..f
967         if ($code<=0x7F) {
968             foreach my $cat (keys %TRUE_POSIX_PERL_CC) {
969                 if ($TRUE_POSIX_PERL_CC{$cat}{$code}) {
970                     $Cat{$cat}->$op($code);
971                 }
972             }
973         }
974     }
975
976     ## open and read file.....
977     if (not open IN, "UnicodeData.txt") {
978         die "$0: UnicodeData.txt: $!\n";
979     }
980
981     ##
982     ## For building \p{_CombAbove} and \p{_CanonDCIJ}
983     ##
984     my %_Above_HexCodes; ## Hexcodes for chars with $comb == 230 ("ABOVE")
985
986     my %CodeToDeco;      ## Maps code to decomp. list for chars with first
987                          ## decomp. char an "i" or "j" (for \p{_CanonDCIJ})
988
989     ## This is filled in as we go....
990     my $CombAbove = Table->New(Is   => '_CombAbove',
991                                Desc  => '(for internal casefolding use)',
992                                Fuzzy => 0);
993
994     while (<IN>)
995     {
996         next unless /^[0-9A-Fa-f]+;/;
997         s/\s+$//;
998
999         my ($hexcode,   ## code point in hex (e.g. "0041")
1000             $name,      ## character name (e.g. "LATIN CAPITAL LETTER A")
1001             $cat,       ## category (e.g. "Lu")
1002             $comb,      ## Canonical combining class (e.g. "230")
1003             $bidi,      ## directional category (e.g. "L")
1004             $deco,      ## decomposition mapping
1005             $decimal,   ## decimal digit value
1006             $digit,     ## digit value
1007             $number,    ## numeric value
1008             $mirrored,  ## mirrored
1009             $unicode10, ## name in Unicode 1.0
1010             $comment,   ## comment field
1011             $upper,     ## uppercase mapping
1012             $lower,     ## lowercase mapping
1013             $title,     ## titlecase mapping
1014               ) = split(/\s*;\s*/);
1015
1016         # Note that in Unicode 3.2 there will be names like
1017         # LINE FEED (LF), which probably means that \N{} needs
1018         # to cope also with LINE FEED and LF.
1019         $name = $unicode10 if $name eq '<control>' && $unicode10 ne '';
1020
1021         my $code = hex($hexcode);
1022
1023         if ($comb and $comb == 230) {
1024             $CombAbove->Append($code);
1025             $_Above_HexCodes{$hexcode} = 1;
1026         }
1027
1028         ## Used in building \p{_CanonDCIJ}
1029         if ($deco and $deco =~ m/^006[9A]\b/) {
1030             $CodeToDeco{$code} = $deco;
1031         }
1032
1033         ##
1034         ## There are a few pairs of lines like:
1035         ##   AC00;<Hangul Syllable, First>;Lo;0;L;;;;;N;;;;;
1036         ##   D7A3;<Hangul Syllable, Last>;Lo;0;L;;;;;N;;;;;
1037         ## that define ranges.
1038         ##
1039         if ($name =~ /^<(.+), (First|Last)>$/)
1040         {
1041             $name = $1;
1042             gencat($name, $cat, $code, $2 eq 'First' ? 'Append' : 'Extend');
1043             #New_Prop(In => $name, $General{$name}, Fuzzy => 1);
1044         }
1045         else
1046         {
1047             ## normal (single-character) lines
1048             gencat($name, $cat, $code, 'Append');
1049
1050             # No Append() here since since several codes may map into one.
1051             $To{Upper}->RawAppendRange($code, $code, $upper) if $upper;
1052             $To{Lower}->RawAppendRange($code, $code, $lower) if $lower;
1053             $To{Title}->RawAppendRange($code, $code, $title) if $title;
1054             $To{Digit}->Append($code, $decimal) if length $decimal;
1055
1056             $Bidi->Append($code, $bidi);
1057             $Comb->Append($code, $comb) if $comb;
1058             $Number->Append($code, $number) if length $number;
1059
1060             length($decimal) and ($Number{De} ||= Table->New())->Append($code)
1061               or
1062             length($digit)   and ($Number{Di} ||= Table->New())->Append($code)
1063               or
1064             length($number)  and ($Number{Nu} ||= Table->New())->Append($code);
1065
1066             $Mirrored->Append($code) if $mirrored eq "Y";
1067
1068             $Bidi{$bidi} ||= Table->New();#Is    => "bt/$bidi",
1069                                         #Desc  => "Bi-directional category '$bidi'",
1070                                         #Fuzzy => 0);
1071             $Bidi{$bidi}->Append($code);
1072
1073             if ($deco)
1074             {
1075                 $Deco->Append($code, $deco);
1076                 if ($deco =~/^<(\w+)>/)
1077                 {
1078                     my $dshort = $PVA_reverse{dt}{ucfirst lc $1};
1079                     $DC{Com}->Append($code);
1080                     $dshort = $PVA_reverse{dt}{lc $1} unless $dshort ne "";
1081                     die "No reverse for $1'" unless $dshort ne "";
1082                     #$dshort = lc $dshort;   # use lower case only
1083                     $DC{$dshort} ||= Table->New();
1084                     $DC{$dshort}->Append($code);
1085                 }
1086                 else
1087                 {
1088                     $DC{Can}->Append($code);
1089                 }
1090             }
1091         }
1092     }
1093     close IN;
1094
1095     ## Read in the NameAliases.txt.  It contains other normative names of code
1096     ## points not listed in UnicodeData.txt.  This happens when there is an
1097     ## error in the name found after the data base was published, but instead of
1098     ## changing it, to avoid breaking any code that came to rely on the
1099     ## erroneous version, the correct name is added as an alias.
1100     
1101     my $NameAliases = Table->New();
1102
1103     if (not open IN, "NameAliases.txt") {
1104         die "$0: NameAliases.txt: $!\n";
1105     }
1106
1107     while (<IN>)
1108     {
1109         next unless /^[0-9A-Fa-f]+;/;
1110         s/\s+$//;
1111
1112         my ($hexcode,   ## code point in hex (e.g. "0041")
1113             $name,      ## character name (e.g. "LATIN CAPITAL LETTER A")
1114               ) = split(/\s*;\s*/);
1115
1116         my $code = hex($hexcode);
1117
1118         ## One is supposed to enter elements into tables in strictly increasing
1119         ## order, but this in fact works to append duplicate code points at
1120         ## the end of the table.  The table is intended to be indexed by name
1121         ## anyway.
1122
1123         $Name->RawAppendRange($code, $code, $name);
1124     }
1125     close IN;
1126
1127
1128     ##
1129     ## Tidy up a few special cases....
1130     ##
1131
1132     $Cat{Cn} = $Assigned->Invert; ## Cn is everything that doesn't exist
1133     New_Prop(Is => 'Cn',
1134              $Cat{Cn},
1135              Desc => "General Category 'Cn' [not functional in Perl]",
1136              Fuzzy => 0);
1137
1138     ## Unassigned is the same as 'Cn'
1139     New_Alias(Is => 'Unassigned', SameAs => 'Cn', Fuzzy => 0);
1140
1141     $Cat{C}->Replace($Cat{C}->Merge($Cat{Cn}));  ## Now merge in Cn into C
1142
1143
1144     # LC is Ll, Lu, and Lt.
1145     # (used to be L& or L_, but PropValueAliases.txt defines it as LC)
1146     New_Prop(Is => 'LC',
1147              Table->Merge(@Cat{qw[Ll Lu Lt]}),
1148              Desc  => '[\p{Ll}\p{Lu}\p{Lt}]',
1149              Fuzzy => 0);
1150
1151     ## Any and All are all code points.
1152     my $Any = Table->New(Is    => 'Any',
1153                          Desc  => sprintf("[\\x{0000}-\\x{%X}]",
1154                                           $LastUnicodeCodepoint),
1155                          Fuzzy => 0);
1156     $Any->RawAppendRange(0, $LastUnicodeCodepoint);
1157
1158     New_Alias(Is => 'All', SameAs => 'Any', Fuzzy => 0);
1159
1160     ##
1161     ## Build special properties for Perl's internal case-folding needs:
1162     ##    \p{_CaseIgnorable}
1163     ##    \p{_CanonDCIJ}
1164     ##    \p{_CombAbove}
1165     ## _CombAbove was built above. Others are built here....
1166     ##
1167
1168     ## \p{_CaseIgnorable} is [\p{Mn}\0x00AD\x2010]
1169     New_Prop(Is => '_CaseIgnorable',
1170              Table->Merge($Cat{Mn},
1171                           0x00AD,    #SOFT HYPHEN
1172                           0x2010),   #HYPHEN
1173              Desc  => '(for internal casefolding use)',
1174              Fuzzy => 0);
1175
1176
1177     ## \p{_CanonDCIJ} is fairly complex...
1178     my $CanonCDIJ = Table->New(Is    => '_CanonDCIJ',
1179                                Desc  => '(for internal casefolding use)',
1180                                Fuzzy => 0);
1181     ## It contains the ASCII 'i' and 'j'....
1182     $CanonCDIJ->Append(0x0069); # ASCII ord("i")
1183     $CanonCDIJ->Append(0x006A); # ASCII ord("j")
1184     ## ...and any character with a decomposition that starts with either of
1185     ## those code points, but only if the decomposition does not have any
1186     ## combining character with the "ABOVE" canonical combining class.
1187     for my $code (sort { $a <=> $b} keys %CodeToDeco)
1188     {
1189         ## Need to ensure that all decomposition characters do not have
1190         ## a %HexCodeToComb in %AboveCombClasses.
1191         my $want = 1;
1192         for my $deco_hexcode (split / /, $CodeToDeco{$code})
1193         {
1194             if (exists $_Above_HexCodes{$deco_hexcode}) {
1195                 ## one of the decmposition chars has an ABOVE combination
1196                 ## class, so we're not interested in this one
1197                 $want = 0;
1198                 last;
1199             }
1200         }
1201         if ($want) {
1202             $CanonCDIJ->Append($code);
1203         }
1204     }
1205
1206
1207
1208     ##
1209     ## Now dump the files.
1210     ##
1211     $Name->Write("Name.pl");
1212
1213     {
1214         my @PVA = $HEADER;
1215         foreach my $name (qw (PropertyAlias PA_reverse PropValueAlias
1216                               PVA_reverse PVA_abbr_map)) {
1217             # Should I really jump through typeglob hoops just to avoid a
1218             # symbolic reference? (%{"utf8::$name})
1219             push @PVA, "\n", "\%utf8::$name = (\n",
1220                 simple_dumper (%{$utf8::{$name}}), ");\n";
1221         }
1222         push @PVA, "1;\n";
1223         WriteIfChanged("PVA.pl", @PVA);
1224     }
1225
1226     # $Bidi->Write("Bidirectional.pl");
1227     for (keys %Bidi) {
1228         $Bidi{$_}->Write(
1229             ["lib","bc","$_.pl"],
1230             "BidiClass category '$PropValueAlias{bc}{$_}'"
1231         );
1232     }
1233
1234     $Comb->Write("CombiningClass.pl");
1235     for (keys %{ $PropValueAlias{ccc} }) {
1236         my ($code, $name) = @{ $PropValueAlias{ccc}{$_} };
1237         (my $c = Table->New())->Append($code);
1238         $c->Write(
1239             ["lib","ccc","$_.pl"],
1240             "CombiningClass category '$name'"
1241         );
1242     }
1243
1244     $Deco->Write("Decomposition.pl");
1245     for (keys %DC) {
1246         $DC{$_}->Write(
1247             ["lib","dt","$_.pl"],
1248             "DecompositionType category '$PropValueAlias{dt}{$_}'"
1249         );
1250     }
1251
1252     # $Number->Write("Number.pl");
1253     for (keys %Number) {
1254         $Number{$_}->Write(
1255             ["lib","nt","$_.pl"],
1256             "NumericType category '$PropValueAlias{nt}{$_}'"
1257         );
1258     }
1259
1260     # $General->Write("Category.pl");
1261
1262     for my $to (sort keys %To) {
1263         $To{$to}->Write(["To","$to.pl"]);
1264     }
1265
1266     for (keys %{ $PropValueAlias{gc} }) {
1267         New_Alias(Is => $PropValueAlias{gc}{$_}, SameAs => $_, Fuzzy => 1);
1268     }
1269 }
1270
1271 ##
1272 ## Process LineBreak.txt
1273 ##
1274 sub LineBreak_Txt()
1275 {
1276     if (not open IN, "LineBreak.txt") {
1277         die "$0: LineBreak.txt: $!\n";
1278     }
1279
1280     my $Lbrk = Table->New();
1281     my %Lbrk;
1282
1283     while (<IN>)
1284     {
1285         next unless /^([0-9A-Fa-f]+)(?:\.\.([0-9A-Fa-f]+))?\s*;\s*(\w+)/;
1286
1287         my ($first, $last, $lbrk) = (hex($1), hex($2||""), $3);
1288
1289         $Lbrk->Append($first, $lbrk);
1290
1291         $Lbrk{$lbrk} ||= Table->New();
1292         $Lbrk{$lbrk}->Append($first);
1293
1294         if ($last) {
1295             $Lbrk->Extend($last);
1296             $Lbrk{$lbrk}->Extend($last);
1297         }
1298     }
1299     close IN;
1300
1301     # $Lbrk->Write("Lbrk.pl");
1302
1303
1304     for (keys %Lbrk) {
1305         $Lbrk{$_}->Write(
1306             ["lib","lb","$_.pl"],
1307             "Linebreak category '$PropValueAlias{lb}{$_}'"
1308         );
1309     }
1310 }
1311
1312 ##
1313 ## Process ArabicShaping.txt.
1314 ##
1315 sub ArabicShaping_txt()
1316 {
1317     if (not open IN, "ArabicShaping.txt") {
1318         die "$0: ArabicShaping.txt: $!\n";
1319     }
1320
1321     my $ArabLink      = Table->New();
1322     my $ArabLinkGroup = Table->New();
1323
1324     my %JoinType;
1325
1326     while (<IN>)
1327     {
1328         next unless /^[0-9A-Fa-f]+;/;
1329         s/\s+$//;
1330
1331         my ($hexcode, $name, $link, $linkgroup) = split(/\s*;\s*/);
1332         my $code = hex($hexcode);
1333         $ArabLink->Append($code, $link);
1334         $ArabLinkGroup->Append($code, $linkgroup);
1335
1336         $JoinType{$link} ||= Table->New(Is => "JoinType$link");
1337         $JoinType{$link}->Append($code);
1338     }
1339     close IN;
1340
1341     # $ArabLink->Write("ArabLink.pl");
1342     # $ArabLinkGroup->Write("ArabLnkGrp.pl");
1343
1344
1345     for (keys %JoinType) {
1346         $JoinType{$_}->Write(
1347             ["lib","jt","$_.pl"],
1348             "JoiningType category '$PropValueAlias{jt}{$_}'"
1349         );
1350     }
1351 }
1352
1353 ##
1354 ## Process EastAsianWidth.txt.
1355 ##
1356 sub EastAsianWidth_txt()
1357 {
1358     if (not open IN, "EastAsianWidth.txt") {
1359         die "$0: EastAsianWidth.txt: $!\n";
1360     }
1361
1362     my %EAW;
1363
1364     while (<IN>)
1365     {
1366         next unless /^[0-9A-Fa-f]+(\.\.[0-9A-Fa-f]+)?;/;
1367         s/#.*//;
1368         s/\s+$//;
1369
1370         my ($hexcodes, $pv) = split(/\s*;\s*/);
1371         $EAW{$pv} ||= Table->New(Is => "EastAsianWidth$pv");
1372       my ($start, $end) = split(/\.\./, $hexcodes);
1373       if (defined $end) {
1374         $EAW{$pv}->AppendRange(hex($start), hex($end));
1375       } else {
1376         $EAW{$pv}->Append(hex($start));
1377       }
1378     }
1379     close IN;
1380
1381
1382     for (keys %EAW) {
1383         $EAW{$_}->Write(
1384             ["lib","ea","$_.pl"],
1385             "EastAsianWidth category '$PropValueAlias{ea}{$_}'"
1386         );
1387     }
1388 }
1389
1390 ##
1391 ## Process HangulSyllableType.txt.
1392 ##
1393 sub HangulSyllableType_txt()
1394 {
1395     if (not open IN, "HangulSyllableType.txt") {
1396         die "$0: HangulSyllableType.txt: $!\n";
1397     }
1398
1399     my %HST;
1400
1401     while (<IN>)
1402     {
1403         next unless /^([0-9A-Fa-f]+)(?:\.\.([0-9A-Fa-f]+))?\s*;\s*(\w+)/;
1404         my ($first, $last, $pv) = (hex($1), hex($2||""), $3);
1405
1406         $HST{$pv} ||= Table->New(Is => "HangulSyllableType$pv");
1407         $HST{$pv}->Append($first);
1408
1409         if ($last) { $HST{$pv}->Extend($last) }
1410     }
1411     close IN;
1412
1413     for (keys %HST) {
1414         $HST{$_}->Write(
1415             ["lib","hst","$_.pl"],
1416             "HangulSyllableType category '$PropValueAlias{hst}{$_}'"
1417         );
1418     }
1419 }
1420
1421 ##
1422 ## Process Jamo.txt.
1423 ##
1424 sub Jamo_txt()
1425 {
1426     if (not open IN, "Jamo.txt") {
1427         die "$0: Jamo.txt: $!\n";
1428     }
1429     my $Short = Table->New();
1430
1431     while (<IN>)
1432     {
1433         next unless /^([0-9A-Fa-f]+)\s*;\s*(\w*)/;
1434         my ($code, $short) = (hex($1), $2);
1435
1436         $Short->Append($code, $short);
1437     }
1438     close IN;
1439     # $Short->Write("JamoShort.pl");
1440 }
1441
1442 ##
1443 ## Process Scripts.txt.
1444 ##
1445 sub Scripts_txt()
1446 {
1447     my @ScriptInfo;
1448
1449     if (not open(IN, "Scripts.txt")) {
1450         die "$0: Scripts.txt: $!\n";
1451     }
1452     while (<IN>) {
1453         next unless /^([0-9A-Fa-f]+)(?:\.\.([0-9A-Fa-f]+))?\s*;\s*(.+?)\s*\#/;
1454
1455         # Wait until all the scripts have been read since
1456         # they are not listed in numeric order.
1457         push @ScriptInfo, [ hex($1), hex($2||""), $3 ];
1458     }
1459     close IN;
1460
1461     # Now append the scripts properties in their code point order.
1462
1463     my %Script;
1464     my $Scripts = Table->New();
1465
1466     for my $script (sort { $a->[0] <=> $b->[0] } @ScriptInfo)
1467     {
1468         my ($first, $last, $name) = @$script;
1469         $Scripts->Append($first, $name);
1470
1471         $Script{$name} ||= Table->New(Is    => $name,
1472                                       Desc  => "Script '$name'",
1473                                       Fuzzy => 1);
1474         $Script{$name}->Append($first, $name);
1475
1476         if ($last) {
1477             $Scripts->Extend($last);
1478             $Script{$name}->Extend($last);
1479         }
1480     }
1481
1482     # $Scripts->Write("Scripts.pl");
1483
1484     ## Common is everything not explicitly assigned to a Script
1485     ##
1486     ##    ***shouldn't this be intersected with \p{Assigned}? ******
1487     ##
1488     New_Prop(Is => 'Common',
1489              $Scripts->Invert,
1490              Desc  => 'Pseudo-Script of codepoints not in other Unicode scripts',
1491              Fuzzy => 1);
1492 }
1493
1494 ##
1495 ## Given a name like "Close Punctuation", return a regex (that when applied
1496 ## with /i) matches any valid form of that name (e.g. "ClosePunctuation",
1497 ## "Close-Punctuation", etc.)
1498 ##
1499 ## Accept any space, dash, or underbar where in the official name there is
1500 ## space or a dash (or underbar, but there never is).
1501 ##
1502 ##
1503 sub NameToRegex($)
1504 {
1505     my $Name = shift;
1506     $Name =~ s/[- _]/(?:[-_]|\\s+)?/g;
1507     return $Name;
1508 }
1509
1510 ##
1511 ## Process Blocks.txt.
1512 ##
1513 sub Blocks_txt()
1514 {
1515     my $Blocks = Table->New();
1516     my %Blocks;
1517
1518     if (not open IN, "Blocks.txt") {
1519         die "$0: Blocks.txt: $!\n";
1520     }
1521
1522     while (<IN>)
1523     {
1524         #next if not /Private Use$/;
1525         next if not /^([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+)\s*;\s*(.+?)\s*$/;
1526
1527         my ($first, $last, $name) = (hex($1), hex($2), $3);
1528
1529         $Blocks->Append($first, $name);
1530
1531         $Blocks{$name} ||= Table->New(In    => $name,
1532                                       Desc  => "Block '$name'",
1533                                       Fuzzy => 1);
1534         $Blocks{$name}->Append($first, $name);
1535
1536         if ($last and $last != $first) {
1537             $Blocks->Extend($last);
1538             $Blocks{$name}->Extend($last);
1539         }
1540     }
1541     close IN;
1542
1543     # $Blocks->Write("Blocks.pl");
1544 }
1545
1546 ##
1547 ## Read in the PropList.txt.  It contains extended properties not
1548 ## listed in the UnicodeData.txt, such as 'Other_Alphabetic':
1549 ## alphabetic but not of the general category L; many modifiers
1550 ## belong to this extended property category: while they are not
1551 ## alphabets, they are alphabetic in nature.
1552 ##
1553 sub PropList_txt()
1554 {
1555     my @PropInfo;
1556
1557     if (not open IN, "PropList.txt") {
1558         die "$0: PropList.txt: $!\n";
1559     }
1560
1561     while (<IN>)
1562     {
1563         next unless /^([0-9A-Fa-f]+)(?:\.\.([0-9A-Fa-f]+))?\s*;\s*(.+?)\s*\#/;
1564
1565         # Wait until all the extended properties have been read since
1566         # they are not listed in numeric order.
1567         push @PropInfo, [ hex($1), hex($2||""), $3 ];
1568     }
1569     close IN;
1570
1571     # Now append the extended properties in their code point order.
1572     my $Props = Table->New();
1573     my %Prop;
1574
1575     for my $prop (sort { $a->[0] <=> $b->[0] } @PropInfo)
1576     {
1577         my ($first, $last, $name) = @$prop;
1578         $Props->Append($first, $name);
1579
1580         $Prop{$name} ||= Table->New(Is    => $name,
1581                                     Desc  => "Extended property '$name'",
1582                                     Fuzzy => 1);
1583         $Prop{$name}->Append($first, $name);
1584
1585         if ($last) {
1586             $Props->Extend($last);
1587             $Prop{$name}->Extend($last);
1588         }
1589     }
1590
1591     for (keys %Prop) {
1592         (my $file = $PA_reverse{$_}) =~ tr/_//d;
1593         # XXX I'm assuming that the names from %Prop don't suffer 8.3 clashes.
1594         $BaseNames{lc $file}++;
1595         $Prop{$_}->Write(
1596             ["lib","gc_sc","$file.pl"],
1597             "Binary property '$_'"
1598         );
1599     }
1600
1601     # Alphabetic is L, Nl, and Other_Alphabetic.
1602     New_Prop(Is    => 'Alphabetic',
1603              Table->Merge($Cat{L}, $Cat{Nl}, $Prop{Other_Alphabetic}),
1604              Desc  => '[\p{L}\p{Nl}\p{OtherAlphabetic}]', # canonical names
1605              Fuzzy => 1);
1606
1607     # Lowercase is Ll and Other_Lowercase.
1608     New_Prop(Is    => 'Lowercase',
1609              Table->Merge($Cat{Ll}, $Prop{Other_Lowercase}),
1610              Desc  => '[\p{Ll}\p{OtherLowercase}]', # canonical names
1611              Fuzzy => 1);
1612
1613     # Uppercase is Lu and Other_Uppercase.
1614     New_Prop(Is => 'Uppercase',
1615              Table->Merge($Cat{Lu}, $Prop{Other_Uppercase}),
1616              Desc  => '[\p{Lu}\p{OtherUppercase}]', # canonical names
1617              Fuzzy => 1);
1618
1619     # Math is Sm and Other_Math.
1620     New_Prop(Is => 'Math',
1621              Table->Merge($Cat{Sm}, $Prop{Other_Math}),
1622              Desc  => '[\p{Sm}\p{OtherMath}]', # canonical names
1623              Fuzzy => 1);
1624
1625     # ID_Start is Ll, Lu, Lt, Lm, Lo, Nl, and Other_ID_Start.
1626     New_Prop(Is => 'ID_Start',
1627              Table->Merge(@Cat{qw[Ll Lu Lt Lm Lo Nl]}, $Prop{Other_ID_Start}),
1628              Desc  => '[\p{Ll}\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{OtherIDStart}]',
1629              Fuzzy => 1);
1630
1631     # ID_Continue is ID_Start, Mn, Mc, Nd, Pc, and Other_ID_Continue.
1632     New_Prop(Is => 'ID_Continue',
1633              Table->Merge(@Cat{qw[Ll Lu Lt Lm Lo Nl Mn Mc Nd Pc ]},
1634                           @Prop{qw[Other_ID_Start Other_ID_Continue]}),
1635              Desc  => '[\p{ID_Start}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{OtherIDContinue}]',
1636              Fuzzy => 1);
1637
1638     # Default_Ignorable_Code_Point = Other_Default_Ignorable_Code_Point
1639     #                     + Cf + Cc + Cs + Noncharacter + Variation_Selector
1640     #                     - WhiteSpace - FFF9..FFFB (Annotation Characters)
1641
1642     my $Annotation = Table->New();
1643     $Annotation->RawAppendRange(0xFFF9, 0xFFFB);
1644
1645     New_Prop(Is => 'Default_Ignorable_Code_Point',
1646              Table->Merge(@Cat{qw[Cf Cc Cs]},
1647                           $Prop{Noncharacter_Code_Point},
1648                           $Prop{Variation_Selector},
1649                           $Prop{Other_Default_Ignorable_Code_Point})
1650                   ->Invert
1651                   ->Merge($Prop{White_Space}, $Annotation)
1652                   ->Invert,
1653              Desc  => '(?![\p{WhiteSpace}\x{FFF9}-\x{FFFB}])[\p{Cf}\p{Cc}'.
1654                       '\p{Cs}\p{NoncharacterCodePoint}\p{VariationSelector}'.
1655                       '\p{OtherDefaultIgnorableCodePoint}]',
1656              Fuzzy => 1);
1657
1658 }
1659
1660
1661 ##
1662 ## These are used in:
1663 ##   MakePropTestScript()
1664 ##   WriteAllMappings()
1665 ## for making the test script.
1666 ##
1667 my %FuzzyNameToTest;
1668 my %ExactNameToTest;
1669
1670
1671 ## This used only for making the test script
1672 sub GenTests($$$$)
1673 {
1674     my $FH = shift;
1675     my $Prop = shift;
1676     my $MatchCode = shift;
1677     my $FailCode = shift;
1678
1679     if (defined $MatchCode) {
1680         printf $FH qq/Expect(1, "\\x{%04X}", '\\p{$Prop}' );\n/, $MatchCode;
1681         printf $FH qq/Expect(0, "\\x{%04X}", '\\p{^$Prop}');\n/, $MatchCode;
1682         printf $FH qq/Expect(0, "\\x{%04X}", '\\P{$Prop}' );\n/, $MatchCode;
1683         printf $FH qq/Expect(1, "\\x{%04X}", '\\P{^$Prop}');\n/, $MatchCode;
1684     }
1685     if (defined $FailCode) {
1686         printf $FH qq/Expect(0, "\\x{%04X}", '\\p{$Prop}' );\n/, $FailCode;
1687         printf $FH qq/Expect(1, "\\x{%04X}", '\\p{^$Prop}');\n/, $FailCode;
1688         printf $FH qq/Expect(1, "\\x{%04X}", '\\P{$Prop}' );\n/, $FailCode;
1689         printf $FH qq/Expect(0, "\\x{%04X}", '\\P{^$Prop}');\n/, $FailCode;
1690     }
1691 }
1692
1693 ## This used only for making the test script
1694 sub ExpectError($$)
1695 {
1696     my $FH = shift;
1697     my $prop = shift;
1698
1699     print $FH qq/Error('\\p{$prop}');\n/;
1700     print $FH qq/Error('\\P{$prop}');\n/;
1701 }
1702
1703 ## This used only for making the test script
1704 my @GoodSeps = (
1705                 " ",
1706                 "-",
1707                 " \t ",
1708                 "",
1709                 "",
1710                 "_",
1711                );
1712 my @BadSeps = (
1713                "--",
1714                "__",
1715                " _",
1716                "/"
1717               );
1718
1719 ## This used only for making the test script
1720 sub RandomlyFuzzifyName($;$)
1721 {
1722     my $Name = shift;
1723     my $WantError = shift;  ## if true, make an error
1724
1725     my @parts;
1726     for my $part (split /[-\s_]+/, $Name)
1727     {
1728         if (@parts) {
1729             if ($WantError and rand() < 0.3) {
1730                 push @parts, $BadSeps[rand(@BadSeps)];
1731                 $WantError = 0;
1732             } else {
1733                 push @parts, $GoodSeps[rand(@GoodSeps)];
1734             }
1735         }
1736         my $switch = int rand(4);
1737         if ($switch == 0) {
1738             push @parts, uc $part;
1739         } elsif ($switch == 1) {
1740             push @parts, lc $part;
1741         } elsif ($switch == 2) {
1742             push @parts, ucfirst $part;
1743         } else {
1744             push @parts, $part;
1745         }
1746     }
1747     my $new = join('', @parts);
1748
1749     if ($WantError) {
1750         if (rand() >= 0.5) {
1751             $new .= $BadSeps[rand(@BadSeps)];
1752         } else {
1753             $new = $BadSeps[rand(@BadSeps)] . $new;
1754         }
1755     }
1756     return $new;
1757 }
1758
1759 ## This used only for making the test script
1760 sub MakePropTestScript()
1761 {
1762     ## this written directly -- it's huge.
1763     force_unlink ("TestProp.pl");
1764     if (not open OUT, ">TestProp.pl") {
1765         die "$0: TestProp.pl: $!\n";
1766     }
1767     print OUT <DATA>;
1768
1769     while (my ($Name, $Table) = each %ExactNameToTest)
1770     {
1771         GenTests(*OUT, $Name, $Table->ValidCode, $Table->InvalidCode);
1772         ExpectError(*OUT, uc $Name) if uc $Name ne $Name;
1773         ExpectError(*OUT, lc $Name) if lc $Name ne $Name;
1774     }
1775
1776
1777     while (my ($Name, $Table) = each %FuzzyNameToTest)
1778     {
1779         my $Orig  = $CanonicalToOrig{$Name};
1780         my %Names = (
1781                      $Name => 1,
1782                      $Orig => 1,
1783                      RandomlyFuzzifyName($Orig) => 1
1784                     );
1785
1786         for my $N (keys %Names) {
1787             GenTests(*OUT, $N, $Table->ValidCode, $Table->InvalidCode);
1788         }
1789
1790         ExpectError(*OUT, RandomlyFuzzifyName($Orig, 'ERROR'));
1791     }
1792
1793     print OUT "Finished();\n";
1794     close OUT;
1795 }
1796
1797
1798 ##
1799 ## These are used only in:
1800 ##   RegisterFileForName()
1801 ##   WriteAllMappings()
1802 ##
1803 my %Exact;      ## will become %utf8::Exact;
1804 my %Canonical;  ## will become %utf8::Canonical;
1805 my %CaComment;  ## Comment for %Canonical entry of same key
1806
1807 ##
1808 ## Given info about a name and a datafile that it should be associated with,
1809 ## register that assocation in %Exact and %Canonical.
1810 sub RegisterFileForName($$$$)
1811 {
1812     my $Type     = shift;
1813     my $Name     = shift;
1814     my $IsFuzzy  = shift;
1815     my $filename = shift;
1816
1817     ##
1818     ## Now in details for the mapping. $Type eq 'Is' has the
1819     ## Is removed, as it will be removed in utf8_heavy when this
1820     ## data is being checked. In keeps its "In", but a second
1821     ## sans-In record is written if it doesn't conflict with
1822     ## anything already there.
1823     ##
1824     if (not $IsFuzzy)
1825     {
1826         if ($Type eq 'Is') {
1827             die "oops[$Name]" if $Exact{$Name};
1828             $Exact{$Name} = $filename;
1829         } else {
1830             die "oops[$Type$Name]" if $Exact{"$Type$Name"};
1831             $Exact{"$Type$Name"} = $filename;
1832             $Exact{$Name} = $filename if not $Exact{$Name};
1833         }
1834     }
1835     else
1836     {
1837         my $CName = lc $Name;
1838         if ($Type eq 'Is') {
1839             die "oops[$CName]" if $Canonical{$CName};
1840             $Canonical{$CName} = $filename;
1841             $CaComment{$CName} = $Name if $Name =~ tr/A-Z// >= 2;
1842         } else {
1843             die "oops[$Type$CName]" if $Canonical{lc "$Type$CName"};
1844             $Canonical{lc "$Type$CName"} = $filename;
1845             $CaComment{lc "$Type$CName"} = "$Type$Name";
1846             if (not $Canonical{$CName}) {
1847                 $Canonical{$CName} = $filename;
1848                 $CaComment{$CName} = "$Type$Name";
1849             }
1850         }
1851     }
1852 }
1853
1854 ##
1855 ## Writes the info accumulated in
1856 ##
1857 ##       %TableInfo;
1858 ##       %FuzzyNames;
1859 ##       %AliasInfo;
1860 ##
1861 ##
1862 sub WriteAllMappings()
1863 {
1864     my @MAP;
1865
1866     ## 'Is' *MUST* come first, so its names have precidence over 'In's
1867     for my $Type ('Is', 'In')
1868     {
1869         my %RawNameToFile; ## a per-$Type cache
1870
1871         for my $Name (sort {length $a <=> length $b} keys %{$TableInfo{$Type}})
1872         {
1873             ## Note: $Name is already canonical
1874             my $Table   = $TableInfo{$Type}->{$Name};
1875             my $IsFuzzy = $FuzzyNames{$Type}->{$Name};
1876
1877             ## Need an 8.3 safe filename (which means "an 8 safe" $filename)
1878             my $filename;
1879             {
1880                 ## 'Is' items lose 'Is' from the basename.
1881                 $filename = $Type eq 'Is' ?
1882                     ($PVA_reverse{sc}{$Name} || $Name) :
1883                     "$Type$Name";
1884
1885                 $filename =~ s/[^\w_]+/_/g; # "L&" -> "L_"
1886                 substr($filename, 8) = '' if length($filename) > 8;
1887
1888                 ##
1889                 ## Make sure the basename doesn't conflict with something we
1890                 ## might have already written. If we have, say,
1891                 ##     InGreekExtended1
1892                 ##     InGreekExtended2
1893                 ## they become
1894                 ##     InGreekE
1895                 ##     InGreek2
1896                 ##
1897                 while (my $num = $BaseNames{lc $filename}++)
1898                 {
1899                     $num++; ## so basenames with numbers start with '2', which
1900                             ## just looks more natural.
1901                     ## Want to append $num, but if it'll make the basename longer
1902                     ## than 8 characters, pre-truncate $filename so that the result
1903                     ## is acceptable.
1904                     my $delta = length($filename) + length($num) - 8;
1905                     if ($delta > 0) {
1906                         substr($filename, -$delta) = $num;
1907                     } else {
1908                         $filename .= $num;
1909                     }
1910                 }
1911             };
1912
1913             ##
1914             ## Construct a nice comment to add to the file, and build data
1915             ## for the "./Properties" file along the way.
1916             ##
1917             my $Comment;
1918             {
1919                 my $Desc = $TableDesc{$Type}->{$Name} || "";
1920                 ## get list of names this table is reference by
1921                 my @Supported = $Name;
1922                 while (my ($Orig, $Alias) = each %{ $AliasInfo{$Type} })
1923                 {
1924                     if ($Orig eq $Name) {
1925                         push @Supported, $Alias;
1926                     }
1927                 }
1928
1929                 my $TypeToShow = $Type eq 'Is' ? "" : $Type;
1930                 my $OrigProp;
1931
1932                 $Comment = "This file supports:\n";
1933                 for my $N (@Supported)
1934                 {
1935                     my $IsFuzzy = $FuzzyNames{$Type}->{$N};
1936                     my $Prop    = "\\p{$TypeToShow$Name}";
1937                     $OrigProp = $Prop if not $OrigProp; #cache for aliases
1938                     if ($IsFuzzy) {
1939                         $Comment .= "\t$Prop (and fuzzy permutations)\n";
1940                     } else {
1941                         $Comment .= "\t$Prop\n";
1942                     }
1943                     my $MyDesc = ($N eq $Name) ? $Desc : "Alias for $OrigProp ($Desc)";
1944
1945                     push @MAP, sprintf("%s %-42s %s\n",
1946                                        $IsFuzzy ? '*' : ' ', $Prop, $MyDesc);
1947                 }
1948                 if ($Desc) {
1949                     $Comment .= "\nMeaning: $Desc\n";
1950                 }
1951
1952             }
1953             ##
1954             ## Okay, write the file...
1955             ##
1956             $Table->Write(["lib","gc_sc","$filename.pl"], $Comment);
1957
1958             ## and register it
1959             $RawNameToFile{$Name} = $filename;
1960             RegisterFileForName($Type => $Name, $IsFuzzy, $filename);
1961
1962             if ($IsFuzzy)
1963             {
1964                 my $CName = CanonicalName($Type . '_'. $Name);
1965                 $FuzzyNameToTest{$Name}  = $Table if !$FuzzyNameToTest{$Name};
1966                 $FuzzyNameToTest{$CName} = $Table if !$FuzzyNameToTest{$CName};
1967             } else {
1968                 $ExactNameToTest{$Name} = $Table;
1969             }
1970
1971         }
1972
1973         ## Register aliase info
1974         for my $Name (sort {length $a <=> length $b} keys %{$AliasInfo{$Type}})
1975         {
1976             my $Alias    = $AliasInfo{$Type}->{$Name};
1977             my $IsFuzzy  = $FuzzyNames{$Type}->{$Alias};
1978             my $filename = $RawNameToFile{$Name};
1979             die "oops [$Alias]->[$Name]" if not $filename;
1980             RegisterFileForName($Type => $Alias, $IsFuzzy, $filename);
1981
1982             my $Table = $TableInfo{$Type}->{$Name};
1983             die "oops" if not $Table;
1984             if ($IsFuzzy)
1985             {
1986                 my $CName = CanonicalName($Type .'_'. $Alias);
1987                 $FuzzyNameToTest{$Alias} = $Table if !$FuzzyNameToTest{$Alias};
1988                 $FuzzyNameToTest{$CName} = $Table if !$FuzzyNameToTest{$CName};
1989             } else {
1990                 $ExactNameToTest{$Alias} = $Table;
1991             }
1992         }
1993     }
1994
1995     ##
1996     ## Write out the property list
1997     ##
1998     {
1999         my @OUT = (
2000                    "##\n",
2001                    "## This file created by $0\n",
2002                    "## List of built-in \\p{...}/\\P{...} properties.\n",
2003                    "##\n",
2004                    "## '*' means name may be 'fuzzy'\n",
2005                    "##\n\n",
2006                    sort { substr($a,2) cmp substr($b, 2) } @MAP,
2007                   );
2008         WriteIfChanged('Properties', @OUT);
2009     }
2010
2011     ## Write Exact.pl
2012     {
2013         my @OUT = (
2014                    $HEADER,
2015                    "##\n",
2016                    "## Data in this file used by ../utf8_heavy.pl\n",
2017                    "##\n\n",
2018                    "## Mapping from name to filename in ./lib/gc_sc\n",
2019                    "%utf8::Exact = (\n",
2020                   );
2021
2022         $Exact{InGreek} = 'InGreekA';  # this is evil kludge
2023         for my $Name (sort keys %Exact)
2024         {
2025             my $File = $Exact{$Name};
2026             $Name = $Name =~ m/\W/ ? qq/'$Name'/ : " $Name ";
2027             my $Text = sprintf("%-15s => %s,\n", $Name, qq/'$File'/);
2028             push @OUT, Text::Tabs::unexpand($Text);
2029         }
2030         push @OUT, ");\n1;\n";
2031
2032         WriteIfChanged('Exact.pl', @OUT);
2033     }
2034
2035     ## Write Canonical.pl
2036     {
2037         my @OUT = (
2038                    $HEADER,
2039                    "##\n",
2040                    "## Data in this file used by ../utf8_heavy.pl\n",
2041                    "##\n\n",
2042                    "## Mapping from lc(canonical name) to filename in ./lib\n",
2043                    "%utf8::Canonical = (\n",
2044                   );
2045         my $Trail = ""; ## used just to keep the spacing pretty
2046         for my $Name (sort keys %Canonical)
2047         {
2048             my $File = $Canonical{$Name};
2049             if ($CaComment{$Name}) {
2050                 push @OUT, "\n" if not $Trail;
2051                 push @OUT, " # $CaComment{$Name}\n";
2052                 $Trail = "\n";
2053             } else {
2054                 $Trail = "";
2055             }
2056             $Name = $Name =~ m/\W/ ? qq/'$Name'/ : " $Name ";
2057             my $Text = sprintf("  %-41s => %s,\n$Trail", $Name, qq/'$File'/);
2058             push @OUT, Text::Tabs::unexpand($Text);
2059         }
2060         push @OUT, ");\n1\n";
2061         WriteIfChanged('Canonical.pl', @OUT);
2062     }
2063
2064     MakePropTestScript() if $MakeTestScript;
2065 }
2066
2067
2068 sub SpecialCasing_txt()
2069 {
2070     #
2071     # Read in the special cases.
2072     #
2073
2074     my %CaseInfo;
2075
2076     if (not open IN, "SpecialCasing.txt") {
2077         die "$0: SpecialCasing.txt: $!\n";
2078     }
2079     while (<IN>) {
2080         next unless /^[0-9A-Fa-f]+;/;
2081         s/\#.*//;
2082         s/\s+$//;
2083
2084         my ($code, $lower, $title, $upper, $condition) = split(/\s*;\s*/);
2085
2086         if ($condition) { # not implemented yet
2087             print "# SKIPPING $_\n" if $Verbose;
2088             next;
2089         }
2090
2091         # Wait until all the special cases have been read since
2092         # they are not listed in numeric order.
2093         my $ix = hex($code);
2094         push @{$CaseInfo{Lower}}, [ $ix, $code, $lower ]
2095             unless $code eq $lower;
2096         push @{$CaseInfo{Title}}, [ $ix, $code, $title ]
2097             unless $code eq $title;
2098         push @{$CaseInfo{Upper}}, [ $ix, $code, $upper ]
2099             unless $code eq $upper;
2100     }
2101     close IN;
2102
2103     # Now write out the special cases properties in their code point order.
2104     # Prepend them to the To/{Upper,Lower,Title}.pl.
2105
2106     for my $case (qw(Lower Title Upper))
2107     {
2108         my $NormalCase = do "To/$case.pl" || die "$0: $@\n";
2109
2110         my @OUT =
2111             (
2112              $HEADER, $INTERNAL_ONLY, "\n",
2113              "# The key: UTF-8 _bytes_, the value: UTF-8 (speed hack)\n",
2114              "%utf8::ToSpec$case =\n(\n",
2115             );
2116
2117         for my $prop (sort { $a->[0] <=> $b->[0] } @{$CaseInfo{$case}}) {
2118             my ($ix, $code, $to) = @$prop;
2119             my $tostr =
2120               join "", map { sprintf "\\x{%s}", $_ } split ' ', $to;
2121             push @OUT, sprintf qq["%s" => "$tostr",\n], join("", map { sprintf "\\x%02X", $_ } unpack("U0C*", pack("U", $ix)));
2122             # Remove any single-character mappings for
2123             # the same character since we are going for
2124             # the special casing rules.
2125             $NormalCase =~ s/^$code\t\t\w+\n//m;
2126         }
2127         push @OUT, (
2128                     ");\n\n",
2129                     "return <<'END';\n",
2130                     $NormalCase,
2131                     "END\n"
2132                     );
2133         WriteIfChanged(["To","$case.pl"], @OUT);
2134     }
2135 }
2136
2137 #
2138 # Read in the case foldings.
2139 #
2140 # We will do full case folding, C + F + I (see CaseFolding.txt).  Note that
2141 # there are no I entries starting with Unicode 3.2, but leaving it in allows
2142 # for backward compatibility.
2143 #
2144 sub CaseFolding_txt()
2145 {
2146     if (not open IN, "CaseFolding.txt") {
2147         die "$0: CaseFolding.txt: $!\n";
2148     }
2149
2150     my $Fold = Table->New();
2151     my %Fold;
2152
2153     while (<IN>) {
2154         # Skip status 'S', simple case folding
2155         next unless /^([0-9A-Fa-f]+)\s*;\s*([CFI])\s*;\s*([0-9A-Fa-f]+(?: [0-9A-Fa-f]+)*)\s*;/;
2156
2157         my ($code, $status, $fold) = (hex($1), $2, $3);
2158
2159         if ($status eq 'C') { # Common: one-to-one folding
2160             # No append() since several codes may fold into one.
2161             $Fold->RawAppendRange($code, $code, $fold);
2162         } else { # F: full, or I: dotted uppercase I -> dotless lowercase I
2163             $Fold{$code} = $fold;
2164         }
2165     }
2166     close IN;
2167
2168     $Fold->Write("To/Fold.pl");
2169
2170     #
2171     # Prepend the special foldings to the common foldings.
2172     #
2173     my $CommonFold = do "To/Fold.pl" || die "$0: To/Fold.pl: $!\n";
2174
2175     my @OUT =
2176         (
2177          $HEADER, $INTERNAL_ONLY, "\n",
2178          "#  The key: UTF-8 _bytes_, the value: UTF-8 (speed hack)\n",
2179          "%utf8::ToSpecFold =\n(\n",
2180         );
2181     for my $code (sort { $a <=> $b } keys %Fold) {
2182         my $foldstr =
2183           join "", map { sprintf "\\x{%s}", $_ } split ' ', $Fold{$code};
2184         push @OUT, sprintf qq["%s" => "$foldstr",\n], join("", map { sprintf "\\x%02X", $_ } unpack("U0C*", pack("U", $code)));
2185     }
2186     push @OUT, (
2187                 ");\n\n",
2188                 "return <<'END';\n",
2189                 $CommonFold,
2190                 "END\n",
2191                );
2192
2193     WriteIfChanged(["To","Fold.pl"], @OUT);
2194 }
2195
2196 ## Do it....
2197
2198 Build_Aliases();
2199 UnicodeData_Txt();
2200 PropList_txt();
2201
2202 Scripts_txt();
2203 Blocks_txt();
2204
2205 WriteAllMappings();
2206
2207 LineBreak_Txt();
2208 ArabicShaping_txt();
2209 EastAsianWidth_txt();
2210 HangulSyllableType_txt();
2211 Jamo_txt();
2212 SpecialCasing_txt();
2213 CaseFolding_txt();
2214
2215 if ( $FileList and $MakeList ) {
2216     
2217     print "Updating '$FileList'\n"
2218         if ($Verbose);
2219         
2220     open my $ofh,">",$FileList 
2221         or die "Can't write to '$FileList':$!";
2222     print $ofh <<"EOFHEADER";
2223 #
2224 # mktables.lst -- File list for mktables.
2225 #
2226 #   Autogenerated on @{[scalar localtime]}
2227 #
2228 # - First section is input files
2229 #   (mktables itself is automatically included)
2230 # - Section seperator is /^=+\$/
2231 # - Second section is a list of output files.
2232 # - Lines matching /^\\s*#/ are treated as comments
2233 #   which along with blank lines are ignored.
2234 #
2235
2236 # Input files:
2237
2238 EOFHEADER
2239     my @input=("version",glob('*.txt'));
2240     print $ofh "$_\n" for 
2241         sort(@input),
2242         "\n=================================\n",
2243         "# Output files:\n",
2244         # special files
2245         "Properties";
2246         
2247     
2248     require File::Find;
2249     my @output_files;
2250     File::Find::find({
2251         no_chdir=>1,
2252         wanted=>sub {
2253           if (/\.pl$/) {
2254             s!^\./!!;
2255             push @output_files, "$_\n";
2256           }
2257         },
2258     },"."); 
2259     
2260     print $ofh sort @output_files;
2261     print $ofh "\n# ",scalar(@input)," input files\n",
2262                "# ",scalar(@output_files)+1," output files\n\n",
2263                "# End list\n";  
2264     close $ofh 
2265         or warn "Failed to close $ofh: $!";
2266     
2267     print "Filelist has ",scalar(@input)," input files and ",
2268           scalar(@output_files)+1," output files\n"
2269         if $Verbose;
2270 }
2271 print "All done\n" if $Verbose;
2272 exit(0);
2273
2274 ## TRAILING CODE IS USED BY MakePropTestScript()
2275 __DATA__
2276 use strict;
2277 use warnings;
2278
2279 my $Tests = 0;
2280 my $Fails = 0;
2281
2282 sub Expect($$$)
2283 {
2284     my $Expect = shift;
2285     my $String = shift;
2286     my $Regex  = shift;
2287     my $Line   = (caller)[2];
2288
2289     $Tests++;
2290     my $RegObj;
2291     my $result = eval {
2292         $RegObj = qr/$Regex/;
2293         $String =~ $RegObj ? 1 : 0
2294     };
2295     
2296     if (not defined $result) {
2297         print "couldn't compile /$Regex/ on $0 line $Line: $@\n";
2298         $Fails++;
2299     } elsif ($result ^ $Expect) {
2300         print "bad result (expected $Expect) on $0 line $Line: $@\n";
2301         $Fails++;
2302     }
2303 }
2304
2305 sub Error($)
2306 {
2307     my $Regex  = shift;
2308     $Tests++;
2309     if (eval { 'x' =~ qr/$Regex/; 1 }) {
2310         $Fails++;
2311         my $Line = (caller)[2];
2312         print "expected error for /$Regex/ on $0 line $Line: $@\n";
2313     }
2314 }
2315
2316 sub Finished()
2317 {
2318    if ($Fails == 0) {
2319       print "All $Tests tests passed.\n";
2320       exit(0);
2321    } else {
2322       print "$Tests tests, $Fails failed!\n";
2323       exit(-1);
2324    }
2325 }