This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
mktables: if modify during run, regen tables
[perl5.git] / lib / unicore / mktables
index 44355de..3cd8c46 100644 (file)
@@ -6,7 +6,23 @@
 
 # Needs 'no overloading' to run faster on miniperl.  Code commented out at the
 # subroutine objaddr can be used instead to work as far back (untested) as
-# 5.8: needs pack "U".
+# 5.8: needs pack "U".  But almost all occurrences of objaddr have been
+# removed in favor of using 'no overloading'.  You also would have to go
+# through and replace occurrences like:
+#       my $addr = do { no overloading; pack 'J', $self; }
+# with
+#       my $addr = main::objaddr $self;
+# (or reverse commit 9b01bafde4b022706c3d6f947a0963f821b2e50b
+# that instituted the change to main::objaddr, and subsequent commits that
+# changed 0+$self to pack 'J', $self.)
+
+my $start_time;
+BEGIN { # Get the time the script started running; do it at compiliation to
+        # get it as close as possible
+    $start_time= time;
+}
+
+
 require 5.010_001;
 use strict;
 use warnings;
@@ -39,26 +55,6 @@ sub DEBUG () { 0 }  # Set to 0 for production; 1 for development
 # have been checked for somewhat more than just sanity.  It can handle all
 # existing Unicode character properties in those releases.
 #
-# This program needs to be able to run under miniperl.  Therefore, it uses a
-# minimum of other modules, and hence implements some things itself that could
-# be gotten from CPAN
-#
-# This program uses inputs published by the Unicode Consortium.  These can
-# change incompatibly between releases without the Perl maintainers realizing
-# it.  Therefore this program is now designed to try to flag these.  It looks
-# at the directories where the inputs are, and flags any unrecognized files.
-# It keeps track of all the properties in the files it handles, and flags any
-# that it doesn't know how to handle.  It also flags any input lines that
-# don't match the expected syntax, among other checks.
-# It is also designed so if a new input file matches one of the known
-# templates, one hopefully just needs to add it to a list to have it
-# processed.
-#
-# It tries to keep fatal errors to a minimum, to generate something usable for
-# testing purposes.  It always looks for files that could be inputs, and will
-# warn about any that it doesn't know how to handle (the -q option suppresses
-# the warning).
-#
 # This program is mostly about Unicode character (or code point) properties.
 # A property describes some attribute or quality of a code point, like if it
 # is lowercase or not, its name, what version of Unicode it was first defined
@@ -145,7 +141,7 @@ my $map_directory = 'To';        # Where map files go.
 # writing, such as the path to each one's file.  There is a heading in each
 # map table that gives the format of its entries, and what the map is for all
 # the code points missing from it.  (This allows tables to be more compact.)
-
+#
 # The Property data structure contains one or more tables.  All properties
 # contain a map table (except the $perl property which is a
 # pseudo-property containing only match tables), and any properties that
@@ -167,7 +163,7 @@ my $map_directory = 'To';        # Where map files go.
 # constructs will.  Generally a property will have either its map table or its
 # match tables written but not both.  Again, what gets written is controlled
 # by lists which can easily be changed.
-
+#
 # For information about the Unicode properties, see Unicode's UAX44 document:
 
 my $unicode_reference_url = 'http://www.unicode.org/reports/tr44/';
@@ -188,9 +184,9 @@ my $unicode_reference_url = 'http://www.unicode.org/reports/tr44/';
 # introductory comments.
 #
 # This program works on all properties as of 5.2, though the files for some
-# are suppressed from apparent lack of demand for.  You can change which are
-# output by changing lists in this program.
-
+# are suppressed from apparent lack of demand for them.  You can change which
+# are output by changing lists in this program.
+#
 # The old version of mktables emphasized the term "Fuzzy" to mean Unocde's
 # loose matchings rules (from Unicode TR18):
 #
@@ -204,7 +200,7 @@ my $unicode_reference_url = 'http://www.unicode.org/reports/tr44/';
 # The program still allows Fuzzy to override its determination of if loose
 # matching should be used, but it isn't currently used, as it is no longer
 # needed; the calculations it makes are good enough.
-
+#
 # SUMMARY OF HOW IT WORKS:
 #
 #   Process arguments
@@ -234,20 +230,10 @@ my $unicode_reference_url = 'http://www.unicode.org/reports/tr44/';
 #        The Perl-defined properties are created and populated.  Many of these
 #            require data determined from the earlier steps
 #        Any Perl-defined synonyms are created, and name clashes between Perl
-#            and Unicode are reconciled.
+#            and Unicode are reconciled and warned about.
 #        All the properties are written to files
 #        Any other files are written, and final warnings issued.
-
-# As mentioned above, some properties are given in more than one file.  In
-# particular, the files in the extracted directory are supposedly just
-# reformattings of the others.  But they contain information not easily
-# derivable from the other files, including results for Unihan, which this
-# program doesn't ordinarily look at, and for unassigned code points.  They
-# also have historically had errors or been incomplete.  In an attempt to
-# create the best possible data, this program thus processes them first to
-# glean information missing from the other files; then processes those other
-# files to override any errors in the extracted ones.
-
+#
 # For clarity, a number of operators have been overloaded to work on tables:
 #   ~ means invert (take all characters not in the set).  The more
 #       conventional '!' is not used because of the possibility of confusing
@@ -261,48 +247,116 @@ my $unicode_reference_url = 'http://www.unicode.org/reports/tr44/';
 # Operations are done on references and affect the underlying structures, so
 # that the copy constructors for them have been overloaded to not return a new
 # clone, but the input object itself.
-
+#
 # The bool operator is deliberately not overloaded to avoid confusion with
 # "should it mean if the object merely exists, or also is non-empty?".
-
 #
 # WHY CERTAIN DESIGN DECISIONS WERE MADE
-
-# XXX These comments need more work.
+#
+# This program needs to be able to run under miniperl.  Therefore, it uses a
+# minimum of other modules, and hence implements some things itself that could
+# be gotten from CPAN
+#
+# This program uses inputs published by the Unicode Consortium.  These can
+# change incompatibly between releases without the Perl maintainers realizing
+# it.  Therefore this program is now designed to try to flag these.  It looks
+# at the directories where the inputs are, and flags any unrecognized files.
+# It keeps track of all the properties in the files it handles, and flags any
+# that it doesn't know how to handle.  It also flags any input lines that
+# don't match the expected syntax, among other checks.
+#
+# It is also designed so if a new input file matches one of the known
+# templates, one hopefully just needs to add it to a list to have it
+# processed.
+#
+# As mentioned earlier, some properties are given in more than one file.  In
+# particular, the files in the extracted directory are supposedly just
+# reformattings of the others.  But they contain information not easily
+# derivable from the other files, including results for Unihan, which this
+# program doesn't ordinarily look at, and for unassigned code points.  They
+# also have historically had errors or been incomplete.  In an attempt to
+# create the best possible data, this program thus processes them first to
+# glean information missing from the other files; then processes those other
+# files to override any errors in the extracted ones.  Much of the design was
+# driven by this need to store things and then possibly override them.
+#
+# It tries to keep fatal errors to a minimum, to generate something usable for
+# testing purposes.  It always looks for files that could be inputs, and will
+# warn about any that it doesn't know how to handle (the -q option suppresses
+# the warning).
 #
 # Why have files written out for binary 'N' matches?
 #   For binary properties, if you know the mapping for either Y or N; the
-#   other is trivial to construct, so could be done at Perl run-time instead
-#   of having a file for it.  That is, if someone types in \p{foo: N}, Perl
-#   could translate that to \P{foo: Y} and not need a file.   The problem is
-#   communicating to Perl that a given property is binary.  Perl can't figure
-#   it out from looking at the N (or No), as some non-binary properties have
-#   these as property values.
-# Why
-# There are several types of properties, based on what form their values can
-# take on.  These are described in more detail below in the DATA STRUCTURES
-# section of these comments, but for now, you should know that there are
-# string properties, whose values are strings of one or more code points (such
-# as the Uppercase_mapping property); every other property maps to some other
-# form, like true or false, or a number, or a name, etc.  The reason there are
-# two directories for map files is because of the way utf8.c works.  It
-# expects that any files there are string properties, that is that the
-# mappings are each to one code point, with mappings in multiple code points
-# handled specially in an extra hash data structure.  Digit.pl is a table that
-# is written there for historical reasons, even though it doesn't fit that
-# mold.  Thus it can't currently be looked at by the Perl core.
+#   other is trivial to construct, so could be done at Perl run-time by just
+#   complementing the result, instead of having a file for it.  That is, if
+#   someone types in \p{foo: N}, Perl could translate that to \P{foo: Y} and
+#   not need a file.   The problem is communicating to Perl that a given
+#   property is binary.  Perl can't figure it out from looking at the N (or
+#   No), as some non-binary properties have these as property values.  So
+#   rather than inventing a way to communicate this info back to the core,
+#   which would have required changes there as well, it was simpler just to
+#   add the extra tables.
+#
+# Why is there more than one type of range?
+#   This simplified things.  There are some very specialized code points that
+#   have to be handled specially for output, such as Hangul syllable names.
+#   By creating a range type (done late in the development process), it
+#   allowed this to be stored with the range, and overridden by other input.
+#   Originally these were stored in another data structure, and it became a
+#   mess trying to decide if a second file that was for the same property was
+#   overriding the earlier one or not.
+#
+# Why are there two kinds of tables, match and map?
+#   (And there is a base class shared by the two as well.)  As stated above,
+#   they actually are for different things.  Development proceeded much more
+#   smoothly when I (khw) realized the distinction.  Map tables are used to
+#   give the property value for every code point (actually every code point
+#   that doesn't map to a default value).  Match tables are used for regular
+#   expression matches, and are essentially the inverse mapping.  Separating
+#   the two allows more specialized methods, and error checks so that one
+#   can't just take the intersection of two map tables, for example, as that
+#   is nonsensical.
 #
 # There are no match tables generated for matches of the null string.  These
-# would like like \p{JSN=}.  Perhaps something like them could be added if
-# necessary.  The JSN does have a real code point U+110B that maps to the null
-# string, but it is a contributory property, and therefore not output by
-# default.
+# would look like qr/\p{JSN=}/ currently without modifying the regex code.
+# Perhaps something like them could be added if necessary.  The JSN does have
+# a real code point U+110B that maps to the null string, but it is a
+# contributory property, and therefore not output by default.  And it's easily
+# handled so far by making the null string the default where it is a
+# possibility.
 #
 # DEBUGGING
 #
-# XXX Add more stuff here.   use perl instead of miniperl to find problems with
-# Scalar::Util
-
+# This program is written so it will run under miniperl.  Occasionally changes
+# will cause an error where the backtrace doesn't work well under miniperl.
+# To diagnose the problem, you can instead run it under regular perl, if you
+# have one compiled.
+#
+# There is a good trace facility.  To enable it, first sub DEBUG must be set
+# to return true.  Then a line like
+#
+# local $to_trace = 1 if main::DEBUG;
+#
+# can be added to enable tracing in its lexical scope or until you insert
+# another line:
+#
+# local $to_trace = 0 if main::DEBUG;
+#
+# then use a line like "trace $a, @b, %c, ...;
+#
+# Some of the more complex subroutines already have trace statements in them.
+# Permanent trace statements should be like:
+#
+# trace ... if main::DEBUG && $to_trace;
+#
+# If there is just one or a few files that you're debugging, you can easily
+# cause most everything else to be skipped.  Change the line
+#
+# my $debug_skip = 0;
+#
+# to 1, and every file whose object is in @input_file_objects and doesn't have
+# a, 'non_skip => 1,' in its constructor will be skipped.
+#
 # FUTURE ISSUES
 #
 # The program would break if Unicode were to change its names so that
@@ -335,7 +389,7 @@ my $unicode_reference_url = 'http://www.unicode.org/reports/tr44/';
 # synonym would have to be used for the new property.  This is ugly, and
 # manual intervention would certainly be easier to do in the short run; lets
 # hope it never comes to this.
-
+#
 # A NOTE ON UNIHAN
 #
 # This program can generate tables from the Unihan database.  But it doesn't
@@ -366,19 +420,68 @@ my $unicode_reference_url = 'http://www.unicode.org/reports/tr44/';
 # kPrimaryNumeric property have commas and an unexpected comment.  A filter
 # could be added for these; or for a particular installation, the Unihan.txt
 # file could be edited to fix them.
-# have to be
 #
-# HOW TO ADD A FILE
-
-# Unicode Versions Notes
-
-# alpha's numbers halve in 2.1.9, answer cjk block at 4E00 were removed from PropList; not changed, could add gc Letter, put back in in 3.1.0
-# Some versions of 2.1.x Jamo.txt have the wrong value for 1105, which causes
-# real problems for the algorithms for Jamo calculations, so it is changed
-# here.
-#   White space vs Space.  in 3.2 perl has +205F=medium math space, fixed in 4.0, and ok in 3.1.1 because not there in unicode. synonym introduced in 4.1
-# ATBL = 202.  202 changed to ATB, and all code points stayed there.  So if you were useing ATBL you were out of luck.
-# Hrkt Katakana_Or_Hiragana came in 4.01, before was Unknown.
+# HOW TO ADD A FILE TO BE PROCESSED
+#
+# A new file from Unicode needs to have an object constructed for it in
+# @input_file_objects, probably at the end or at the end of the extracted
+# ones.  The program should warn you if its name will clash with others on
+# restrictive file systems, like DOS.  If so, figure out a better name, and
+# add lines to the README.perl file giving that.  If the file is a character
+# property, it should be in the format that Unicode has by default
+# standardized for such files for the more recently introduced ones.
+# If so, the Input_file constructor for @input_file_objects can just be the
+# file name and release it first appeared in.  If not, then it should be
+# possible to construct an each_line_handler() to massage the line into the
+# standardized form.
+#
+# For non-character properties, more code will be needed.  You can look at
+# the existing entries for clues.
+#
+# UNICODE VERSIONS NOTES
+#
+# The Unicode UCD has had a number of errors in it over the versions.  And
+# these remain, by policy, in the standard for that version.  Therefore it is
+# risky to correct them, because code may be expecting the error.  So this
+# program doesn't generally make changes, unless the error breaks the Perl
+# core.  As an example, some versions of 2.1.x Jamo.txt have the wrong value
+# for U+1105, which causes real problems for the algorithms for Jamo
+# calculations, so it is changed here.
+#
+# But it isn't so clear cut as to what to do about concepts that are
+# introduced in a later release; should they extend back to earlier releases
+# where the concept just didn't exist?  It was easier to do this than to not,
+# so that's what was done.  For example, the default value for code points not
+# in the files for various properties was probably undefined until changed by
+# some version.  No_Block for blocks is such an example.  This program will
+# assign No_Block even in Unicode versions that didn't have it.  This has the
+# benefit that code being written doesn't have to special case earlier
+# versions; and the detriment that it doesn't match the Standard precisely for
+# the affected versions.
+#
+# Here are some observations about some of the issues in early versions:
+#
+# The number of code points in \p{alpha} halve in 2.1.9.  It turns out that
+# the reason is that the CJK block starting at 4E00 was removed from PropList,
+# and was not put back in until 3.1.0
+#
+# Unicode introduced the synonym Space for White_Space in 4.1.  Perl has
+# always had a \p{Space}.  In release 3.2 only, they are not synonymous.  The
+# reason is that 3.2 introduced U+205F=medium math space, which was not
+# classed as white space, but Perl figured out that it should have been. 4.0
+# reclassified it correctly.
+#
+# Another change between 3.2 and 4.0 is the CCC property value ATBL.  In 3.2
+# this was erroneously a synonym for 202.  In 4.0, ATB became 202, and ATBL
+# was left with no code points, as all the ones that mapped to 202 stayed
+# mapped to 202.  Thus if your program used the numeric name for the class,
+# it would not have been affected, but if it used the mnemonic, it would have
+# been.
+#
+# \p{Script=Hrkt} (Katakana_Or_Hiragana) came in 4.0.1.  Before that code
+# points which eventually came to have this script property value, instead
+# mapped to "Unknown".  But in the next release all these code points were
+# moved to \p{sc=common} instead.
 #
 # The default for missing code points for BidiClass is complicated.  Starting
 # in 3.1.1, the derived file DBidiClass.txt handles this, but this program
@@ -461,17 +564,8 @@ our $to_trace = 0;
             }
         }
 
-        if ($print_caller) {
-            if (defined $line_number) {
-                    print STDERR sprintf "%4d: ", $line_number;
-            }
-            else {
-                    print STDERR "     ";
-            }
-            $caller_name .= ": ";
-            print STDERR $caller_name;
-        }
-
+        print STDERR sprintf "%4d: ", $line_number if defined $line_number;
+        print STDERR "$caller_name: " if $print_caller;
         print STDERR $output, "\n";
         return;
     }
@@ -498,6 +592,12 @@ sub uniques {
     # Encapsulated Cleverness".  p. 455 in first edition.
 
     my %seen;
+    # Arguably this breaks encapsulation, if the goal is to permit multiple
+    # distinct objects to stringify to the same value, and be interchangeable.
+    # However, for this program, no two objects stringify identically, and all
+    # lists passed to this function are either objects or strings. So this
+    # doesn't affect correctness, but it does give a couple of percent speedup.
+    no overloading;
     return grep { ! $seen{$_}++ } @_;
 }
 
@@ -523,6 +623,10 @@ my $glob_list = 0;             # ? Should we try to include unknown .txt files
                                # in the input.
 my $output_range_counts = 1;   # ? Should we include the number of code points
                                # in ranges in the output
+my $output_names = 0;          # ? Should character names be in the output
+my @viacode;                   # Contains the 1 million character names, if
+                               # $output_names is true
+
 # Verbosity levels; 0 is quiet
 my $NORMAL_VERBOSITY = 1;
 my $PROGRESS = 2;
@@ -578,6 +682,9 @@ while (@ARGV) {
     elsif ($arg eq '-c') {
         $output_range_counts = ! $output_range_counts
     }
+    elsif ($arg eq '-output_names') {
+        $output_names = 1;
+    }
     else {
         my $with_c = 'with';
         $with_c .= 'out' if $output_range_counts;   # Complements the state
@@ -602,6 +709,9 @@ usage: $0 [-c|-p|-q|-v|-w] [-C dir] [-L filelist] [ -P pod_dir ]
   -maketest   : Make test script 'TestProp.pl' in current (or -C directory),
                 overrides -T
   -makelist   : Rewrite the file list $file_list based on current setup
+  -output_names : Output each character's name in the table files; useful for
+                doing what-ifs, looking at diffs; is slow, memory intensive,
+                resulting tables are usable but very large.
   -check A B  : Executes $0 only if A and B are the same
 END
     }
@@ -609,7 +719,7 @@ END
 
 # Stores the most-recently changed file.  If none have changed, can skip the
 # build
-my $youngest = -M $0;   # Do this before the chdir!
+my $youngest = (stat $0)[9];   # Do this before the chdir!
 
 # Change directories now, because need to read 'version' early.
 if ($use_directory) {
@@ -688,7 +798,7 @@ if ($v_version gt v3.2.0) {
 # unless explicitly added.
 if ($v_version ge v5.2.0) {
     my $unihan = 'Unihan; remove from list if using Unihan';
-    foreach my $table qw (
+    foreach my $table (qw (
                            kAccountingNumeric
                            kOtherNumeric
                            kPrimaryNumeric
@@ -704,7 +814,7 @@ if ($v_version ge v5.2.0) {
                            kIRG_USource
                            kIRG_VSource
                            kRSUnicode
-                        )
+                        ))
     {
         $why_suppress_if_empty_warn_if_not{$table} = $unihan;
     }
@@ -911,7 +1021,7 @@ my %ignored_files = (
     'StandardizedVariants.txt' => 'Only for glyph changes, not a Unicode character property.  Does not fit into current scheme where one code point is mapped',
 );
 
-################ End of externally interesting definitions ###############
+### End of externally interesting definitions, except for @input_file_objects
 
 my $HEADER=<<"EOF";
 # !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!!
@@ -1007,6 +1117,7 @@ my $MULTIPLE = 4;          # Don't replace, but add a duplicate record if
 my $NORMAL = "";
 my $SUPPRESSED = 'z';   # The character should never actually be seen, since
                         # it is suppressed
+my $PLACEHOLDER = 'P';  # Implies no pod entry generated
 my $DEPRECATED = 'D';
 my $a_bold_deprecated = "a 'B<$DEPRECATED>'";
 my $A_bold_deprecated = "A 'B<$DEPRECATED>'";
@@ -1028,7 +1139,7 @@ my %status_past_participles = (
     $SUPPRESSED => 'should never be generated',
     $STABILIZED => 'stabilized',
     $OBSOLETE => 'obsolete',
-    $DEPRECATED => 'deprecated'
+    $DEPRECATED => 'deprecated',
 );
 
 # The format of the values of the map tables:
@@ -1047,7 +1158,7 @@ my %map_table_formats = (
     $INTEGER_FORMAT => 'integer',
     $HEX_FORMAT => 'positive hex whole number; a code point',
     $RATIONAL_FORMAT => 'rational: an integer or a fraction',
-    $STRING_FORMAT => 'arbitrary string',
+    $STRING_FORMAT => 'string',
 );
 
 # Unicode didn't put such derived files in a separate directory at first.
@@ -1065,11 +1176,16 @@ my %loose_property_name_of; # Loosely maps property names to standard form
 
 # These constants names and values were taken from the Unicode standard,
 # version 5.1, section 3.12.  They are used in conjunction with Hangul
-# syllables
-my $SBase = 0xAC00;
-my $LBase = 0x1100;
-my $VBase = 0x1161;
-my $TBase = 0x11A7;
+# syllables.  The '_string' versions are so generated tables can retain the
+# hex format, which is the more familiar value
+my $SBase_string = "0xAC00";
+my $SBase = CORE::hex $SBase_string;
+my $LBase_string = "0x1100";
+my $LBase = CORE::hex $LBase_string;
+my $VBase_string = "0x1161";
+my $VBase = CORE::hex $VBase_string;
+my $TBase_string = "0x11A7";
+my $TBase = CORE::hex $TBase_string;
 my $SCount = 11172;
 my $LCount = 19;
 my $VCount = 21;
@@ -1083,6 +1199,7 @@ my %Jamo_L;     # Leading consonants
 my %Jamo_V;     # Vowels
 my %Jamo_T;     # Trailing consonants
 
+my @backslash_X_tests;     # List of tests read in for testing \X
 my @unhandled_properties;  # Will contain a list of properties found in
                            # the input that we didn't process.
 my @match_properties;      # Properties that have match tables, to be
@@ -1104,6 +1221,8 @@ my $MAX_FLOATING_SLOP = 10 ** - $MIN_FRACTION_LENGTH; # And in floating terms
 my $gc;
 my $perl;
 my $block;
+my $perl_charname;
+my $print;
 
 # Are there conflicting names because of beginning with 'In_', or 'Is_'
 my $has_In_conflicts = 0;
@@ -1135,7 +1254,7 @@ sub objaddr($) {
     no overloading; # If overloaded, numifying below won't work.
 
     # Numifying a ref gives its address.
-    return 0 + $_[0];
+    return pack 'J', $_[0];
 }
 
 # Commented code below should work on Perl 5.8.
@@ -1160,7 +1279,7 @@ sub objaddr($) {
 #    bless $_[0], 'main::Fake';
 #
 #    # Numifying a ref gives its address.
-#    my $addr = 0 + $_[0];
+#    my $addr = pack 'J', $_[0];
 #
 #    # Return to original class
 #    bless $_[0], $pkg;
@@ -1206,6 +1325,11 @@ package Carp;
 
 our $Verbose = 1 if main::DEBUG;  # Useful info when debugging
 
+# This is a work-around suggested by Nicholas Clark to fix a problem with Carp
+# and overload trying to load Scalar:Util under miniperl.  See
+# http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2009-11/msg01057.html
+undef $overload::VERSION;
+
 sub my_carp {
     my $message = shift || "";
     my $nofold = shift || 0;
@@ -1345,7 +1469,7 @@ package main;
             # Use typeglob to give the anonymous subroutine the name we want
             *$destroy_name = sub {
                 my $self = shift;
-                my $addr = main::objaddr($self);
+                my $addr = do { no overloading; pack 'J', $self; };
 
                 $self->$destroy_callback if $destroy_callback;
                 foreach my $field (keys %{$package_fields{$package}}) {
@@ -1391,7 +1515,7 @@ package main;
         # "protection" is only by convention.  All that happens is that the
         # accessor functions' names begin with an underscore.  So instead of
         # calling set_foo, the call is _set_foo.  (Real protection could be
-        # accomplished by having a new subroutine, end_package called at the
+        # accomplished by having a new subroutine, end_package, called at the
         # end of each package, and then storing the __LINE__ ranges and
         # checking them on every accessor.  But that is way overkill.)
 
@@ -1444,16 +1568,15 @@ package main;
                     return Carp::carp_too_few_args(\@_, 2) if main::DEBUG && @_ < 2;
                     my $self = shift;
                     my $value = shift;
+                    my $addr = do { no overloading; pack 'J', $self; };
                     Carp::carp_extra_args(\@_) if main::DEBUG && @_;
                     if (ref $value) {
-                        return if grep { $value == $_ }
-                                            @{$field->{main::objaddr $self}};
+                        return if grep { $value == $_ } @{$field->{$addr}};
                     }
                     else {
-                        return if grep { $value eq $_ }
-                                            @{$field->{main::objaddr $self}};
+                        return if grep { $value eq $_ } @{$field->{$addr}};
                     }
-                    push @{$field->{main::objaddr $self}}, $value;
+                    push @{$field->{$addr}}, $value;
                     return;
                 }
             }
@@ -1479,7 +1602,7 @@ package main;
                     *$subname = sub {
                         use strict "refs";
                         Carp::carp_extra_args(\@_) if main::DEBUG && @_ > 1;
-                        my $addr = main::objaddr $_[0];
+                        my $addr = do { no overloading; pack 'J', $_[0]; };
                         if (ref $field->{$addr} ne 'ARRAY') {
                             my $type = ref $field->{$addr};
                             $type = 'scalar' unless $type;
@@ -1501,7 +1624,8 @@ package main;
                     *$subname = sub {
                         use strict "refs";
                         Carp::carp_extra_args(\@_) if main::DEBUG && @_ > 1;
-                        return $field->{main::objaddr $_[0]};
+                        no overloading;
+                        return $field->{pack 'J', $_[0]};
                     }
                 }
             }
@@ -1515,7 +1639,8 @@ package main;
                         Carp::carp_extra_args(\@_) if @_ > 2;
                     }
                     # $self is $_[0]; $value is $_[1]
-                    $field->{main::objaddr $_[0]} = $_[1];
+                    no overloading;
+                    $field->{pack 'J', $_[0]} = $_[1];
                     return;
                 }
             }
@@ -1575,7 +1700,6 @@ package Input_file;
 
 sub trace { return main::trace(@_); }
 
-
 { # Closure
     # Keep track of fields that are to be put into the constructor.
     my %constructor_fields;
@@ -1609,6 +1733,15 @@ sub trace { return main::trace(@_); }
     # processed when you set the $debug_skip global.
     main::set_access('non_skip', \%non_skip, 'c');
 
+    my %skip;
+    # This is used to skip processing of this input file semi-permanently.
+    # It is used for files that we aren't planning to process anytime soon,
+    # but want to allow to be in the directory and not raise a message that we
+    # are not handling.  Mostly for test files.  This is in contrast to the
+    # non_skip element, which is supposed to be used very temporarily for
+    # debugging.  Sets 'optional' to 1
+    main::set_access('skip', \%skip, 'c');
+
     my %each_line_handler;
     # list of subroutines to look at and filter each non-comment line in the
     # file.  defaults to none.  The subroutines are called in order, each is
@@ -1667,11 +1800,12 @@ sub trace { return main::trace(@_); }
         my $class = shift;
 
         my $self = bless \do{ my $anonymous_scalar }, $class;
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Set defaults
         $handler{$addr} = \&main::process_generic_property_file;
         $non_skip{$addr} = 0;
+        $skip{$addr} = 0;
         $has_missings_defaults{$addr} = $NO_DEFAULTS;
         $handle{$addr} = undef;
         $added_lines{$addr} = [ ];
@@ -1680,6 +1814,7 @@ sub trace { return main::trace(@_); }
         $missings{$addr} = [ ];
 
         # Two positional parameters.
+        return Carp::carp_too_few_args(\@_, 2) if main::DEBUG && @_ < 2;
         $file{$addr} = main::internal_file_to_platform(shift);
         $first_released{$addr} = shift;
 
@@ -1727,6 +1862,8 @@ sub trace { return main::trace(@_); }
             print "Warning: " . __PACKAGE__ . " constructor for $file{$addr} has useless 'non_skip' in it\n";
         }
 
+        $optional{$addr} = 1 if $skip{$addr};
+
         return $self;
     }
 
@@ -1754,7 +1891,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my $file = $file{$addr};
 
@@ -1775,7 +1912,7 @@ sub trace { return main::trace(@_); }
         }
 
         # File could be optional
-        if ($optional{$addr}){
+        if ($optional{$addr}) {
             return unless -e $file;
             my $result = eval $optional{$addr};
             if (! defined $result) {
@@ -1808,11 +1945,12 @@ sub trace { return main::trace(@_); }
         }
         else {
 
-            # Here, the file exists
+            # Here, the file exists.  Some platforms may change the case of
+            # its name
             if ($seen_non_extracted_non_age) {
-                if ($file =~ /$EXTRACTED/) {
+                if ($file =~ /$EXTRACTED/i) {
                     Carp::my_carp_bug(join_lines(<<END
-$file should be processed just after the 'Prop..Alias' files, and before
+$file should be processed just after the 'Prop...Alias' files, and before
 anything not in the $EXTRACTED_DIR directory.  Proceeding, but the results may
 have subtle problems
 END
@@ -1821,8 +1959,8 @@ END
             }
             elsif ($EXTRACTED_DIR
                     && $first_released{$addr} ne v0
-                    && $file !~ /$EXTRACTED/
-                    && $file ne 'DAge.txt')
+                    && $file !~ /$EXTRACTED/i
+                    && lc($file) ne 'dage.txt')
             {
                 # We don't set this (by the 'if' above) if we have no
                 # extracted directory, so if running on an early version,
@@ -1834,10 +1972,20 @@ END
             # isn't a file we are expecting.  As we process the files,
             # they are deleted from the hash, so any that remain at the
             # end of the program are files that we didn't process.
+            my $fkey = File::Spec->rel2abs($file);
+            my $expecting = delete $potential_files{$fkey};
+            $expecting = delete $potential_files{lc($fkey)} unless defined $expecting;
             Carp::my_carp("Was not expecting '$file'.") if
-                    ! delete $potential_files{File::Spec->rel2abs($file)}
+                    ! $expecting
                     && ! defined $handle{$addr};
 
+            # Having deleted from expected files, we can quit if not to do
+            # anything.  Don't print progress unless really want verbosity
+            if ($skip{$addr}) {
+                print "Skipping $file.\n" if $verbosity >= $VERBOSE;
+                return;
+            }
+
             # Open the file, converting the slashes used in this program
             # into the proper form for the OS
             my $file_handle;
@@ -1913,7 +2061,7 @@ END
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Here the file is open (or if the handle is not a ref, is an open
         # 'virtual' file).  Get the next line; any inserted lines get priority
@@ -2058,7 +2206,7 @@ END
 #        # an each_line_handler() on the line.
 #
 #        my $self = shift;
-#        my $addr = main::objaddr $self;
+#        my $addr = do { no overloading; pack 'J', $self; };
 #
 #        foreach my $inserted_ref (@{$added_lines{$addr}}) {
 #            my ($adjusted, $line) = @{$inserted_ref};
@@ -2099,7 +2247,8 @@ END
         # Each inserted line is an array, with the first element being 0 to
         # indicate that this line hasn't been adjusted, and needs to be
         # processed.
-        push @{$added_lines{main::objaddr $self}}, map { [ 0, $_ ] } @_;
+        no overloading;
+        push @{$added_lines{pack 'J', $self}}, map { [ 0, $_ ] } @_;
         return;
     }
 
@@ -2122,7 +2271,8 @@ END
 
         # Each inserted line is an array, with the first element being 1 to
         # indicate that this line has been adjusted
-        push @{$added_lines{main::objaddr $self}}, map { [ 1, $_ ] } @_;
+        no overloading;
+        push @{$added_lines{pack 'J', $self}}, map { [ 1, $_ ] } @_;
         return;
     }
 
@@ -2135,7 +2285,7 @@ END
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # If not accepting a list return, just return the first one.
         return shift @{$missings{$addr}} unless wantarray;
@@ -2148,7 +2298,9 @@ END
     sub _insert_property_into_line {
         # Add a property field to $_, if this file requires it.
 
-        my $property = $property{main::objaddr shift};
+        my $self = shift;
+        my $addr = do { no overloading; pack 'J', $self; };
+        my $property = $property{$addr};
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
         $_ =~ s/(;|$)/; $property$1/;
@@ -2166,7 +2318,7 @@ END
         my $message = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $message = 'Unexpected line' unless $message;
 
@@ -2177,7 +2329,7 @@ END
         # increment the count of how many times it has occurred
         unless ($errors{$addr}->{$message}) {
             Carp::my_carp("$message in '$_' in "
-                            . $file{main::objaddr $self}
+                            . $file{$addr}
                             . " at line $..  Skipping this line;");
             $errors{$addr}->{$message} = 1;
         }
@@ -2231,7 +2383,7 @@ package Multi_Default;
         my $class = shift;
 
         my $self = bless \do{my $anonymous_scalar}, $class;
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         while (@_ > 1) {
             my $default = shift;
@@ -2249,7 +2401,7 @@ package Multi_Default;
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return each %{$class_defaults{$addr}};
     }
@@ -2296,7 +2448,7 @@ package Alias;
         my $class = shift;
 
         my $self = bless \do { my $anonymous_scalar }, $class;
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $name{$addr} = shift;
         $loose_match{$addr} = shift;
@@ -2358,7 +2510,7 @@ sub trace { return main::trace(@_); }
         my $class = shift;
 
         my $self = bless \do { my $anonymous_scalar }, $class;
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $start{$addr} = shift;
         $end{$addr} = shift;
@@ -2388,7 +2540,7 @@ sub trace { return main::trace(@_); }
 
     sub _operator_stringify {
         my $self = shift;
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Output it like '0041..0065 (value)'
         my $return = sprintf("%04X", $start{$addr})
@@ -2411,7 +2563,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return $standard_form{$addr} if defined $standard_form{$addr};
         return $value{$addr};
@@ -2424,7 +2576,7 @@ sub trace { return main::trace(@_); }
         my $indent = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my $return = $indent
                     . sprintf("%04X", $start{$addr})
@@ -2459,13 +2611,6 @@ package _Range_List_Base;
 # There are a number of methods to manipulate range lists, and some operators
 # are overloaded to handle them.
 
-# Because of the slowness of pure Perl objaddr() on miniperl, and measurements
-# showing this package was using a lot of real time calculating that, the code
-# was changed to only calculate it once per call stack.  This is done by
-# consistently using the package variable $addr in routines, and only calling
-# objaddr() if it isn't defined, and setting that to be local, so that callees
-# will have it already.  It would be a good thing to change this. XXX
-
 sub trace { return main::trace(@_); }
 
 { # Closure
@@ -2513,7 +2658,7 @@ sub trace { return main::trace(@_); }
         return _union($class, $initialize, %args) if defined $initialize;
 
         $self = bless \do { my $anonymous_scalar }, $class;
-        local $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Optional parent object, only for debug info.
         $owner_name_of{$addr} = delete $args{'Owner'};
@@ -2545,7 +2690,7 @@ sub trace { return main::trace(@_); }
 
     sub _operator_stringify {
         my $self = shift;
-        local $addr = main::objaddr($self) if !defined $addr;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return "Range_List attached to '$owner_name_of{$addr}'"
                                                 if $owner_name_of{$addr};
@@ -2603,7 +2748,8 @@ sub trace { return main::trace(@_); }
             if (! defined $arg) {
                 my $message = "";
                 if (defined $self) {
-                    $message .= $owner_name_of{main::objaddr $self};
+                    no overloading;
+                    $message .= $owner_name_of{pack 'J', $self};
                 }
                 Carp::my_carp_bug($message .= "Undefined argument to _union.  No union done.");
                 return;
@@ -2624,7 +2770,8 @@ sub trace { return main::trace(@_); }
             else {
                 my $message = "";
                 if (defined $self) {
-                    $message .= $owner_name_of{main::objaddr $self};
+                    no overloading;
+                    $message .= $owner_name_of{pack 'J', $self};
                 }
                 Carp::my_carp_bug($message . "Cannot take the union of a $type.  No union done.");
                 return;
@@ -2664,9 +2811,8 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr($self) if ! defined $addr;
-
-        return scalar @{$ranges{$addr}};
+        no overloading;
+        return scalar @{$ranges{pack 'J', $self}};
     }
 
     sub min {
@@ -2679,7 +2825,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr($self) if ! defined $addr;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # If the range list is empty, return a large value that isn't adjacent
         # to any that could be in the range list, for simpler tests
@@ -2696,8 +2842,6 @@ sub trace { return main::trace(@_); }
         my $codepoint = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr $self if ! defined $addr;
-
         my $i = $self->_search_ranges($codepoint);
         return 0 unless defined $i;
 
@@ -2705,24 +2849,51 @@ sub trace { return main::trace(@_); }
         #   range[$i-1]->end < $codepoint <= range[$i]->end
         # So is in the table if and only iff it is at least the start position
         # of range $i.
-        return 0 if $ranges{$addr}->[$i]->start > $codepoint;
+        no overloading;
+        return 0 if $ranges{pack 'J', $self}->[$i]->start > $codepoint;
         return $i + 1;
     }
 
-    sub value_of {
-        # Returns the value associated with the code point, undef if none
+    sub containing_range {
+        # Returns the range object that contains the code point, undef if none
 
         my $self = shift;
         my $codepoint = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr $self if ! defined $addr;
-
         my $i = $self->contains($codepoint);
         return unless $i;
 
         # contains() returns 1 beyond where we should look
-        return $ranges{$addr}->[$i-1]->value;
+        no overloading;
+        return $ranges{pack 'J', $self}->[$i-1];
+    }
+
+    sub value_of {
+        # Returns the value associated with the code point, undef if none
+
+        my $self = shift;
+        my $codepoint = shift;
+        Carp::carp_extra_args(\@_) if main::DEBUG && @_;
+
+        my $range = $self->containing_range($codepoint);
+        return unless defined $range;
+
+        return $range->value;
+    }
+
+    sub type_of {
+        # Returns the type of the range containing the code point, undef if
+        # the code point is not in the table
+
+        my $self = shift;
+        my $codepoint = shift;
+        Carp::carp_extra_args(\@_) if main::DEBUG && @_;
+
+        my $range = $self->containing_range($codepoint);
+        return unless defined $range;
+
+        return $range->type;
     }
 
     sub _search_ranges {
@@ -2736,7 +2907,7 @@ sub trace { return main::trace(@_); }
         my $code_point = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr $self if ! defined $addr;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return if $code_point > $max{$addr};
         my $r = $ranges{$addr};                # The current list of ranges
@@ -2841,10 +3012,10 @@ sub trace { return main::trace(@_); }
         #
         # The range list is kept sorted so that the range with the lowest
         # starting position is first in the list, and generally, adjacent
-        # ranges with the same values are merged into single larger one (see
+        # ranges with the same values are merged into single larger one (see
         # exceptions below).
         #
-        # There are more parameters, all are key => value pairs:
+        # There are more parameters; all are key => value pairs:
         #   Type    gives the type of the value.  It is only valid for '+'.
         #           All ranges have types; if this parameter is omitted, 0 is
         #           assumed.  Ranges with type 0 are assumed to obey the
@@ -2868,7 +3039,7 @@ sub trace { return main::trace(@_); }
         #       => $IF_NOT_EQUIVALENT means to replace the existing values
         #                         with this one if they are not equivalent.
         #                         Ranges are equivalent if their types are the
-        #                         same, and they are the same string, or if
+        #                         same, and they are the same string; or if
         #                         both are type 0 ranges, if their Unicode
         #                         standard forms are identical.  In this last
         #                         case, the routine chooses the more "modern"
@@ -2887,8 +3058,8 @@ sub trace { return main::trace(@_); }
         #                         multiple times.
         #       => anything else  is the same as => $IF_NOT_EQUIVALENT
         #
-        # "same value" means identical for type-0 ranges, and it means having
-        # the same standard forms for non-type-0 ranges.
+        # "same value" means identical for non-type-0 ranges, and it means
+        # having the same standard forms for type-0 ranges.
 
         return Carp::carp_too_few_args(\@_, 5) if main::DEBUG && @_ < 5;
 
@@ -2910,7 +3081,7 @@ sub trace { return main::trace(@_); }
 
         Carp::carp_extra_args(\%args) if main::DEBUG && %args;
 
-        local $addr = main::objaddr($self) if ! defined $addr;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         if ($operation ne '+' && $operation ne '-') {
             Carp::my_carp_bug("$owner_name_of{$addr}First parameter to _add_delete must be '+' or '-'.  No action taken.");
@@ -3494,9 +3665,8 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr $self if ! defined $addr;
-
-        undef $each_range_iterator{$addr};
+        no overloading;
+        undef $each_range_iterator{pack 'J', $self};
         return;
     }
 
@@ -3507,7 +3677,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr($self) if ! defined $addr;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return if $self->is_empty;
 
@@ -3524,7 +3694,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr($self) if ! defined $addr;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my $count = 0;
         foreach my $range (@{$ranges{$addr}}) {
@@ -3547,8 +3717,8 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr($self) if ! defined $addr;
-        return scalar @{$ranges{$addr}} == 0;
+        no overloading;
+        return scalar @{$ranges{pack 'J', $self}} == 0;
     }
 
     sub hash {
@@ -3559,7 +3729,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        local $addr = main::objaddr($self) if ! defined $addr;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # These are quickly computable.  Return looks like 'min..max;count'
         return $self->min . "..$max{$addr};" . scalar @{$ranges{$addr}};
@@ -3831,8 +4001,6 @@ sub trace { return main::trace(@_); }
         return $self->_add_delete('+', $start, $end, "");
     }
 
-    my $non_ASCII = (ord('A') == 65);   # Assumes test on same platform
-
     sub is_code_point_usable {
         # This used only for making the test script.  See if the input
         # proposed trial code point is one that Perl will handle.  If second
@@ -3845,17 +4013,6 @@ sub trace { return main::trace(@_); }
 
         return 0 if $code < 0;                # Never use a negative
 
-        # For non-ASCII, we shun the characters that don't have Perl encoding-
-        # independent symbols for them.  'A' is such a symbol, so is "\n".
-        # Note, this program hopefully will work on 5.8 Perls, and \v is not
-        # such a symbol in them.
-        return $try_hard if $non_ASCII
-                            && $code <= 0xFF
-                            && ($code >= 0x7F
-                                || ($code >= 0x0E && $code <= 0x1F)
-                                || ($code >= 0x01 && $code <= 0x06)
-                                || $code == 0x0B);  # \v introduced after 5.8
-
         # shun null.  I'm (khw) not sure why this was done, but NULL would be
         # the character very frequently used.
         return $try_hard if $code == 0x0000;
@@ -3880,7 +4037,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # On first pass, don't choose less desirable code points; if no good
         # one is found, repeat, allowing a less desirable one to be selected.
@@ -4055,6 +4212,12 @@ sub trace { return main::trace(@_); }
     # standard.
     main::set_access('perl_extension', \%perl_extension, 'r');
 
+    my %output_range_counts;
+    # A boolean set iff this table is to have comments written in the
+    # output file that contain the number of code points in the range.
+    # The constructor can override the global flag of the same name.
+    main::set_access('output_range_counts', \%output_range_counts, 'r');
+
     sub new {
         # All arguments are key => value pairs, which you can see below, most
         # of which match fields documented above.  Otherwise: Pod_Entry,
@@ -4066,7 +4229,7 @@ sub trace { return main::trace(@_); }
         my $class = shift;
 
         my $self = bless \do { my $anonymous_scalar }, $class;
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my %args = @_;
 
@@ -4076,18 +4239,20 @@ sub trace { return main::trace(@_); }
         my $complete_name = $complete_name{$addr}
                           = delete $args{'Complete_Name'};
         $internal_only{$addr} = delete $args{'Internal_Only_Warning'} || 0;
-        $perl_extension{$addr} = delete $args{'Perl_Extension'} || 0;
+        $output_range_counts{$addr} = delete $args{'Output_Range_Counts'};
         $property{$addr} = delete $args{'_Property'};
         $range_list{$addr} = delete $args{'_Range_List'};
         $status{$addr} = delete $args{'Status'} || $NORMAL;
         $status_info{$addr} = delete $args{'_Status_Info'} || "";
         $range_size_1{$addr} = delete $args{'Range_Size_1'} || 0;
+        $range_size_1{$addr} = 1 if $output_names;  # Make sure 1 name per line
 
         my $description = delete $args{'Description'};
         my $externally_ok = delete $args{'Externally_Ok'};
         my $loose_match = delete $args{'Fuzzy'};
         my $note = delete $args{'Note'};
         my $make_pod_entry = delete $args{'Pod_Entry'};
+        my $perl_extension = delete $args{'Perl_Extension'};
 
         # Shouldn't have any left over
         Carp::carp_extra_args(\%args) if main::DEBUG && %args;
@@ -4095,6 +4260,8 @@ sub trace { return main::trace(@_); }
         # Can't use || above because conceivably the name could be 0, and
         # can't use // operator in case this program gets used in Perl 5.8
         $full_name{$addr} = $name{$addr} if ! defined $full_name{$addr};
+        $output_range_counts{$addr} = $output_range_counts if
+                                        ! defined $output_range_counts{$addr};
 
         $aliases{$addr} = [ ];
         $comment{$addr} = [ ];
@@ -4106,12 +4273,25 @@ sub trace { return main::trace(@_); }
         push @{$description{$addr}}, $description if $description;
         push @{$note{$addr}}, $note if $note;
 
-        # If hasn't set its status already, see if it is on one of the lists
-        # of properties or tables that have particular statuses; if not, is
-        # normal.  The lists are prioritized so the most serious ones are
-        # checked first
-        if (! $status{$addr}) {
-            if (exists $why_suppressed{$complete_name}) {
+        if ($status{$addr} eq $PLACEHOLDER) {
+
+            # A placeholder table doesn't get documented, is a perl extension,
+            # and quite likely will be empty
+            $make_pod_entry = 0 if ! defined $make_pod_entry;
+            $perl_extension = 1 if ! defined $perl_extension;
+            push @tables_that_may_be_empty, $complete_name{$addr};
+        }
+        elsif (! $status{$addr}) {
+
+            # If hasn't set its status already, see if it is on one of the
+            # lists of properties or tables that have particular statuses; if
+            # not, is normal.  The lists are prioritized so the most serious
+            # ones are checked first
+            if (exists $why_suppressed{$complete_name}
+                # Don't suppress if overriden
+                && ! grep { $_ eq $complete_name{$addr} }
+                                                    @output_mapped_properties)
+            {
                 $status{$addr} = $SUPPRESSED;
             }
             elsif (exists $why_deprecated{$complete_name}) {
@@ -4146,6 +4326,8 @@ sub trace { return main::trace(@_); }
             }
         }
 
+        $perl_extension{$addr} = $perl_extension || 0;
+
         # By convention what typically gets printed only or first is what's
         # first in the list, so put the full name there for good output
         # clarity.  Other routines rely on the full name being first on the
@@ -4172,10 +4354,10 @@ sub trace { return main::trace(@_); }
 
     # Here are the methods that are required to be defined by any derived
     # class
-    for my $sub qw(
+    for my $sub (qw(
                     append_to_body
                     pre_body
-                )
+                ))
                 # append_to_body and pre_body are called in the write() method
                 # to add stuff after the main body of the table, but before
                 # its close; and to prepend stuff before the beginning of the
@@ -4200,7 +4382,8 @@ sub trace { return main::trace(@_); }
     sub ranges {
         # Returns the array of ranges associated with this table.
 
-        return $range_list{main::objaddr shift}->ranges;
+        no overloading;
+        return $range_list{pack 'J', shift}->ranges;
     }
 
     sub add_alias {
@@ -4236,7 +4419,7 @@ sub trace { return main::trace(@_); }
         # release
         $name = ucfirst($name) unless $name =~ /^k[A-Z]/;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Figure out if should be loosely matched if not already specified.
         if (! defined $loose_match) {
@@ -4297,7 +4480,8 @@ sub trace { return main::trace(@_); }
 
         # This name may be shorter than any existing ones, so clear the cache
         # of the shortest, so will have to be recalculated.
-        undef $short_name{main::objaddr $self};
+        no overloading;
+        undef $short_name{pack 'J', $self};
         return;
     }
 
@@ -4320,7 +4504,7 @@ sub trace { return main::trace(@_); }
         my $nominal_length_ptr = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # For efficiency, don't recalculate, but this means that adding new
         # aliases could change what the shortest is, so the code that does
@@ -4395,7 +4579,8 @@ sub trace { return main::trace(@_); }
         chomp $description;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        push @{$description{main::objaddr $self}}, $description;
+        no overloading;
+        push @{$description{pack 'J', $self}}, $description;
 
         return;
     }
@@ -4407,7 +4592,8 @@ sub trace { return main::trace(@_); }
         chomp $note;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        push @{$note{main::objaddr $self}}, $note;
+        no overloading;
+        push @{$note{pack 'J', $self}}, $note;
 
         return;
     }
@@ -4419,7 +4605,9 @@ sub trace { return main::trace(@_); }
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
         chomp $comment;
-        push @{$comment{main::objaddr $self}}, $comment;
+
+        no overloading;
+        push @{$comment{pack 'J', $self}}, $comment;
 
         return;
     }
@@ -4432,7 +4620,8 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my @list = @{$comment{main::objaddr $self}};
+        my $addr = do { no overloading; pack 'J', $self; };
+        my @list = @{$comment{$addr}};
         return @list if wantarray;
         my $return = "";
         foreach my $sentence (@list) {
@@ -4449,13 +4638,14 @@ sub trace { return main::trace(@_); }
         # initialization for range lists.
 
         my $self = shift;
+        my $addr = do { no overloading; pack 'J', $self; };
         my $initialization = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
         # Replace the current range list with a new one of the same exact
         # type.
-        my $class = ref $range_list{main::objaddr $self};
-        $range_list{main::objaddr $self} = $class->new(Owner => $self,
+        my $class = ref $range_list{$addr};
+        $range_list{$addr} = $class->new(Owner => $self,
                                         Initialize => $initialization);
         return;
 
@@ -4471,7 +4661,8 @@ sub trace { return main::trace(@_); }
         my $return = "";
         $return .= $DEVELOPMENT_ONLY if $compare_versions;
         $return .= $HEADER;
-        $return .= $INTERNAL_ONLY if $internal_only{main::objaddr $self};
+        no overloading;
+        $return .= $INTERNAL_ONLY if $internal_only{pack 'J', $self};
         return $return;
     }
 
@@ -4486,7 +4677,7 @@ sub trace { return main::trace(@_); }
                                      # the range
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Start with the header
         my @OUT = $self->header;
@@ -4524,8 +4715,24 @@ sub trace { return main::trace(@_); }
 
                 # If has or wants a single point range output
                 if ($start == $end || $range_size_1) {
-                    for my $i ($start .. $end) {
-                        push @OUT, sprintf "%04X\t\t%s\n", $i, $value;
+                    if (ref $range_size_1 eq 'CODE') {
+                        for my $i ($start .. $end) {
+                            push @OUT, &$range_size_1($i, $value);
+                        }
+                    }
+                    else {
+                        for my $i ($start .. $end) {
+                            push @OUT, sprintf "%04X\t\t%s\n", $i, $value;
+                            if ($output_names) {
+                                if (! defined $viacode[$i]) {
+                                    $viacode[$i] =
+                                        Property::property_ref('Perl_Charnames')
+                                                                    ->value_of($i)
+                                        || "";
+                                }
+                                $OUT[-1] =~ s/\n/\t# $viacode[$i]\n/;
+                            }
+                        }
                     }
                 }
                 else  {
@@ -4534,7 +4741,7 @@ sub trace { return main::trace(@_); }
                     # Add a comment with the size of the range, if requested.
                     # Expand Tabs to make sure they all start in the same
                     # column, and then unexpand to use mostly tabs.
-                    if (! $output_range_counts) {
+                    if (! $output_range_counts{$addr}) {
                         $OUT[-1] .= "\n";
                     }
                     else {
@@ -4574,7 +4781,7 @@ sub trace { return main::trace(@_); }
         my $info = shift;   # Any message associated with it.
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $status{$addr} = $status;
         $status_info{$addr} = $info;
@@ -4589,7 +4796,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $locked{$addr} = "";
 
@@ -4617,7 +4824,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return 0 if ! $locked{$addr};
         Carp::my_carp_bug("Can't modify a locked table. Stack trace of locking:\n$locked{$addr}\n\n");
@@ -4628,13 +4835,15 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         # Rest of parameters passed on
 
-        @{$file_path{main::objaddr $self}} = @_;
+        no overloading;
+        @{$file_path{pack 'J', $self}} = @_;
         return
     }
 
     # Accessors for the range list stored in this table.  First for
     # unconditional
-    for my $sub qw(
+    for my $sub (qw(
+                    containing_range
                     contains
                     count
                     each_range
@@ -4644,21 +4853,23 @@ sub trace { return main::trace(@_); }
                     min
                     range_count
                     reset_each_range
+                    type_of
                     value_of
-                )
+                ))
     {
         no strict "refs";
         *$sub = sub {
             use strict "refs";
             my $self = shift;
-            return $range_list{main::objaddr $self}->$sub(@_);
+            no overloading;
+            return $range_list{pack 'J', $self}->$sub(@_);
         }
     }
 
     # Then for ones that should fail if locked
-    for my $sub qw(
+    for my $sub (qw(
                     delete_range
-                )
+                ))
     {
         no strict "refs";
         *$sub = sub {
@@ -4666,7 +4877,8 @@ sub trace { return main::trace(@_); }
             my $self = shift;
 
             return if $self->carp_if_locked;
-            return $range_list{main::objaddr $self}->$sub(@_);
+            no overloading;
+            return $range_list{pack 'J', $self}->$sub(@_);
         }
     }
 
@@ -4772,7 +4984,7 @@ sub trace { return main::trace(@_); }
                                     _Range_List => $range_list,
                                     %args);
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $anomalous_entries{$addr} = [];
         $core_access{$addr} = $core_access;
@@ -4824,7 +5036,7 @@ sub trace { return main::trace(@_); }
         # Can't change the table if locked.
         return if $self->carp_if_locked;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $has_specials{$addr} = 1 if $type;
 
@@ -4842,7 +5054,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return "" unless @{$anomalous_entries{$addr}};
         return join("\n", @{$anomalous_entries{$addr}}) . "\n";
@@ -4869,8 +5081,8 @@ sub trace { return main::trace(@_); }
             return;
         }
 
-        my $addr = main::objaddr $self;
-        my $other_addr = main::objaddr $other;
+        my $addr = do { no overloading; pack 'J', $self; };
+        my $other_addr = do { no overloading; pack 'J', $other; };
 
         local $to_trace = 0 if main::DEBUG;
 
@@ -4903,7 +5115,7 @@ sub trace { return main::trace(@_); }
         my $map = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Convert the input to the standard equivalent, if any (won't have any
         # for $STRING properties)
@@ -4948,7 +5160,7 @@ sub trace { return main::trace(@_); }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # If overridden, use that
         return $to_output_map{$addr} if defined $to_output_map{$addr};
@@ -4993,7 +5205,7 @@ sub trace { return main::trace(@_); }
         # No sense generating a comment if aren't going to write it out.
         return if ! $self->to_output_map;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my $property = $self->property;
 
@@ -5140,7 +5352,7 @@ START\\tSTOP\\tMAPPING where START is the starting code point of the
 range, in hex; STOP is the ending point, or if omitted, the range has just one
 code point; MAPPING is what each code point between START and STOP maps to.
 END
-                if ($output_range_counts) {
+                if ($self->output_range_counts) {
                     $comment .= <<END;
 Numbers in comments in [brackets] indicate how many code points are in the
 range (omitted when the range is a single code point or if the mapping is to
@@ -5165,7 +5377,7 @@ END
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my $name = $self->property->swash_name;
 
@@ -5297,7 +5509,9 @@ END
 # multiple code points.  These do not appear in the main body, but are defined
 # in the hash below.
 
-# The key: UTF-8 _bytes_, the value: UTF-8 (speed hack)
+# Each key is the string of N bytes that together make up the UTF-8 encoding
+# for the code point.  (i.e. the same as looking at the code point's UTF-8
+# under "use bytes").  Each value is the UTF-8 of the translation, for speed.
 %utf8::ToSpec$name = (
 END
                 $pre_body .= join("\n", @multi_code_point_maps) . "\n);\n";
@@ -5414,14 +5628,14 @@ $jamo_t
     # These constants names and values were taken from the Unicode standard,
     # version 5.1, section 3.12.  They are used in conjunction with Hangul
     # syllables
-    my \$SBase = 0xAC00;
-    my \$LBase = 0x1100;
-    my \$VBase = 0x1161;
-    my \$TBase = 0x11A7;
-    my \$SCount = 11172;
-    my \$LCount = 19;
-    my \$VCount = 21;
-    my \$TCount = 28;
+    my \$SBase = $SBase_string;
+    my \$LBase = $LBase_string;
+    my \$VBase = $VBase_string;
+    my \$TBase = $TBase_string;
+    my \$SCount = $SCount;
+    my \$LCount = $LCount;
+    my \$VCount = $VCount;
+    my \$TCount = $TCount;
     my \$NCount = \$VCount * \$TCount;
 END
                 } # End of has Jamos
@@ -5492,7 +5706,7 @@ END
             my $L = $LBase + $SIndex / $NCount;
             my $V = $VBase + ($SIndex % $NCount) / $TCount;
             my $T = $TBase + $SIndex % $TCount;
-            $name = "$HANGUL_SYLLABLE $Jamo{$L}$Jamo{$V}";
+            $name = "$HANGUL_SYLLABLE$Jamo{$L}$Jamo{$V}";
             $name .= $Jamo{$T} if $T != $TBase;
             return $name;
         }
@@ -5608,7 +5822,7 @@ END
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return $self->SUPER::write(
             ($self->property == $block)
@@ -5618,9 +5832,9 @@ END
     }
 
     # Accessors for the underlying list that should fail if locked.
-    for my $sub qw(
+    for my $sub (qw(
                     add_duplicate
-                )
+                ))
     {
         no strict "refs";
         *$sub = sub {
@@ -5747,7 +5961,6 @@ sub trace { return main::trace(@_); }
         # 'table' (If you change the '=' must also change the ':' in lots of
         # places in this program that assume an equal sign)
         $complete = $property->full_name . "=$complete" if $property != $perl;
-        
 
         my $self = $class->SUPER::new(%args,
                                       Name => $name,
@@ -5756,7 +5969,7 @@ sub trace { return main::trace(@_); }
                                       _Property => $property,
                                       _Range_List => $range_list,
                                       );
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $conflicting{$addr} = [ ];
         $equivalents{$addr} = [ ];
@@ -5797,7 +6010,7 @@ sub trace { return main::trace(@_); }
 
                         return if $self->carp_if_locked;
 
-                        my $addr = main::objaddr $self;
+                        my $addr = do { no overloading; pack 'J', $self; };
 
                         if (ref $other) {
 
@@ -5864,7 +6077,7 @@ sub trace { return main::trace(@_); }
                                         # be an optional parameter.
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Check if the conflicting name is exactly the same as any existing
         # alias in this table (as long as there is a real object there to
@@ -5911,8 +6124,8 @@ sub trace { return main::trace(@_); }
         }
 
         # Two tables are equivalent if they have the same leader.
-        return $leader{main::objaddr $self}
-                == $leader{main::objaddr $other};
+        no overloading;
+        return $leader{pack 'J', $self} == $leader{pack 'J', $other};
         return;
     }
 
@@ -5986,9 +6199,8 @@ sub trace { return main::trace(@_); }
         my $are_equivalent = $self->is_equivalent_to($other);
         return if ! defined $are_equivalent || $are_equivalent;
 
-        my $current_leader = ($related)
-                             ? $parent{main::objaddr $self}
-                             : $leader{main::objaddr $self};
+        my $addr = do { no overloading; pack 'J', $self; };
+        my $current_leader = ($related) ? $parent{$addr} : $leader{$addr};
 
         if ($related &&
             ! $other->perl_extension
@@ -5998,8 +6210,8 @@ sub trace { return main::trace(@_); }
             $related = 0;
         }
 
-        my $leader = main::objaddr $current_leader;
-        my $other_addr = main::objaddr $other;
+        my $leader = do { no overloading; pack 'J', $current_leader; };
+        my $other_addr = do { no overloading; pack 'J', $other; };
 
         # Any tables that are equivalent to or children of this table must now
         # instead be equivalent to or (children) to the new leader (parent),
@@ -6014,7 +6226,7 @@ sub trace { return main::trace(@_); }
             next if $table == $other;
             trace "setting $other to be the leader of $table, status=$status" if main::DEBUG && $to_trace;
 
-            my $table_addr = main::objaddr $table;
+            my $table_addr = do { no overloading; pack 'J', $table; };
             $leader{$table_addr} = $other;
             $matches_all{$table_addr} = $matches_all;
             $self->_set_range_list($other->_range_list);
@@ -6068,7 +6280,7 @@ sub trace { return main::trace(@_); }
                               # an equivalent group
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $leader;
+        my $addr = do { no overloading; pack 'J', $leader; };
 
         if ($leader{$addr} != $leader) {
             Carp::my_carp_bug(<<END
@@ -6123,7 +6335,7 @@ END
                 && $parent == $property->table('N')
                 && defined (my $yes = $property->table('Y')))
             {
-                my $yes_addr = main::objaddr $yes;
+                my $yes_addr = do { no overloading; pack 'J', $yes; };
                 @yes_perl_synonyms
                     = grep { $_->property == $perl }
                                     main::uniques($yes,
@@ -6139,11 +6351,12 @@ END
             my @conflicting;        # Will hold the table conflicts.
 
             # Look at the parent, any yes synonyms, and all the children
+            my $parent_addr = do { no overloading; pack 'J', $parent; };
             for my $table ($parent,
                            @yes_perl_synonyms,
-                           @{$children{main::objaddr $parent}})
+                           @{$children{$parent_addr}})
             {
-                my $table_addr = main::objaddr $table;
+                my $table_addr = do { no overloading; pack 'J', $table; };
                 my $table_property = $table->property;
 
                 # Tables are separated by a blank line to create a grouping.
@@ -6205,7 +6418,17 @@ END
                     my $flag = $property->status
                                 || $table->status
                                 || $table_alias_object->status;
-                    $flags{$flag} = $status_past_participles{$flag} if $flag;
+                    if ($flag) {
+                        if ($flag ne $PLACEHOLDER) {
+                            $flags{$flag} = $status_past_participles{$flag};
+                        } else {
+                            $flags{$flag} = <<END;
+a placeholder because it is not in Version $string_version of Unicode, but is
+needed by the Perl core to work gracefully.  Because it is not in this version
+of Unicode, it will not be listed in $pod_file.pod
+END
+                        }
+                    }
 
                     $loose_count++;
 
@@ -6222,6 +6445,9 @@ END
                 push @note, $table->note;
                 push @conflicting, $table->conflicting;
 
+                # And this for output after all the tables.
+                push @global_comments, $table->comment;
+
                 # Compute an alternate compound name using the final property
                 # synonym and the first table synonym with a colon instead of
                 # the equal sign used elsewhere.
@@ -6307,8 +6533,10 @@ END
         if (%flags) {
             foreach my $flag (sort keys %flags) {
                 $comment .= <<END;
-'$flag' below means that this form is $flags{$flag}.  Consult $pod_file.pod
+'$flag' below means that this form is $flags{$flag}.
 END
+                next if $flag eq $PLACEHOLDER;
+                $comment .= "Consult $pod_file.pod\n";
             }
             $comment .= "\n";
         }
@@ -6318,7 +6546,7 @@ This file returns the $code_points in Unicode Version $string_version that
 $match$synonyms:
 
 $matches_comment
-$pod_file.pod should be consulted for the rules on using $any_of_these,
+$pod_file.pod should be consulted for the syntax rules for $any_of_these,
 including if adding or subtracting white space, underscore, and hyphen
 characters matters or doesn't matter, and other permissible syntactic
 variants.  Upper/lower case distinctions never matter.
@@ -6347,7 +6575,9 @@ END
 
         # And append any comment(s) from the actual tables.  They are all
         # gathered here, so may not read all that well.
-        $comment .= "\n" . join "\n\n", @global_comments if @global_comments;
+        if (@global_comments) {
+            $comment .= "\n" . join("\n\n", @global_comments) . "\n";
+        }
 
         if ($count) {   # The format differs if no code points, and needs no
                         # explanation in that case
@@ -6359,7 +6589,7 @@ END
 START\\tSTOP\\twhere START is the starting code point of the range, in hex;
 STOP is the ending point, or if omitted, the range has just one code point.
 END
-            if ($output_range_counts) {
+            if ($leader->output_range_counts) {
                 $comment .= <<END;
 Numbers in comments in [brackets] indicate how many code points are in the
 range.
@@ -6372,10 +6602,10 @@ END
     }
 
     # Accessors for the underlying list
-    for my $sub qw(
+    for my $sub (qw(
                     get_valid_code_point
                     get_invalid_code_point
-                )
+                ))
     {
         no strict "refs";
         *$sub = sub {
@@ -6543,7 +6773,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         my %args = @_;
 
         $self = bless \do { my $anonymous_scalar }, $class;
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         $directory{$addr} = delete $args{'Directory'};
         $file{$addr} = delete $args{'File'};
@@ -6603,7 +6833,8 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
             return $self;
         }
         else {
-            $map{main::objaddr $self}->delete_range($other, $other);
+            no overloading;
+            $map{pack 'J', $self}->delete_range($other, $other);
         }
         return $self;
     }
@@ -6616,7 +6847,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         my $name = shift;
         my %args = @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my $table = $table_ref{$addr}{$name};
         my $standard_name = main::standardize($name);
@@ -6684,7 +6915,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         my $name = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return $table_ref{$addr}{$name} if defined $table_ref{$addr}{$name};
 
@@ -6702,7 +6933,8 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         # Return a list of pointers to all the match tables attached to this
         # property
 
-        return main::uniques(values %{$table_ref{main::objaddr shift}});
+        no overloading;
+        return main::uniques(values %{$table_ref{pack 'J', shift}});
     }
 
     sub directory {
@@ -6711,7 +6943,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         # priority;  'undef' is returned if the type isn't defined;
         # or $map_directory for everything else.
 
-        my $addr = main::objaddr shift;
+        my $addr = do { no overloading; pack 'J', shift; };
 
         return $directory{$addr} if defined $directory{$addr};
         return undef if $type{$addr} == $UNKNOWN;
@@ -6732,7 +6964,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         return $file{$addr} if defined $file{$addr};
         return $map{$addr}->external_name;
@@ -6748,7 +6980,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         # The whole point of this pseudo property is match tables.
         return 1 if $self == $perl;
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # Don't generate tables of code points that match the property values
         # of a string property.  Such a list would most likely have many
@@ -6782,8 +7014,8 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
             return;
         }
 
-        return $map{main::objaddr $self}->
-                map_add_or_replace_non_nulls($map{main::objaddr $other});
+        no overloading;
+        return $map{pack 'J', $self}->map_add_or_replace_non_nulls($map{pack 'J', $other});
     }
 
     sub set_type {
@@ -6802,7 +7034,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
             return;
         }
 
-        $type{main::objaddr $self} = $type;
+        { no overloading; $type{pack 'J', $self} = $type; }
         return if $type != $BINARY;
 
         my $yes = $self->table('Y');
@@ -6832,7 +7064,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         my $map = shift;    # What the range maps to.
         # Rest of parameters passed on.
 
-        my $addr = main::objaddr $self;
+        my $addr = do { no overloading; pack 'J', $self; };
 
         # If haven't the type of the property, gather information to figure it
         # out.
@@ -6884,7 +7116,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         my $self = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-        my $addr = main::objaddr($self);
+        my $addr = do { no overloading; pack 'J', $self; };
 
         my $type = $type{$addr};
 
@@ -6938,7 +7170,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
 
     # Most of the accessors for a property actually apply to its map table.
     # Setup up accessor functions for those, referring to %map
-    for my $sub qw(
+    for my $sub (qw(
                     add_alias
                     add_anomalous_entry
                     add_comment
@@ -6949,6 +7181,7 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
                     aliases
                     comment
                     complete_name
+                    containing_range
                     core_access
                     count
                     default_map
@@ -6981,9 +7214,10 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
                     status
                     status_info
                     to_output_map
+                    type_of
                     value_of
                     write
-                )
+                ))
                     # 'property' above is for symmetry, so that one can take
                     # the property of a property and get itself, and so don't
                     # have to distinguish between properties and tables in
@@ -6993,7 +7227,8 @@ sub trace { return main::trace(@_) if main::DEBUG && $to_trace }
         *$sub = sub {
             use strict "refs";
             my $self = shift;
-            return $map{main::objaddr $self}->$sub(@_);
+            no overloading;
+            return $map{pack 'J', $self}->$sub(@_);
         }
     }
 
@@ -7281,12 +7516,7 @@ sub write ($\@) {
 
     push @files_actually_output, $file;
 
-    my $text;
-    if (@$lines_ref) {
-        $text = join "", @$lines_ref;
-    }
-    else {
-        $text = "";
+    unless (@$lines_ref) {
         Carp::my_carp("Output file '$file' is empty; writing it anyway;");
     }
 
@@ -7297,10 +7527,12 @@ sub write ($\@) {
         Carp::my_carp("can't open $file for output.  Skipping this file: $!");
         return;
     }
+
+    print $OUT @$lines_ref or die Carp::my_carp("write to '$file' failed: $!");
+    close $OUT or die Carp::my_carp("close '$file' failed: $!");
+
     print "$file written.\n" if $verbosity >= $VERBOSE;
 
-    print $OUT $text;
-    close $OUT;
     return;
 }
 
@@ -7401,10 +7633,11 @@ sub standardize ($) {
         else {
 
             # Keep track of cycles in the input, and refuse to infinitely loop
-            if (defined $already_output{main::objaddr $item}) {
+            my $addr = do { no overloading; pack 'J', $item; };
+            if (defined $already_output{$addr}) {
                 return "${indent}ALREADY OUTPUT: $item\n";
             }
-            $already_output{main::objaddr $item} = $item;
+            $already_output{$addr} = $item;
 
             if (ref $item eq 'ARRAY') {
                 my $using_brackets;
@@ -7521,7 +7754,7 @@ sub dump_inside_out {
     my $fields_ref = shift;
     Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
-    my $addr = main::objaddr $object;
+    my $addr = do { no overloading; pack 'J', $object; };
 
     my %hash;
     foreach my $key (keys %$fields_ref) {
@@ -7549,7 +7782,7 @@ sub _operator_dot {
         }
         else {
             my $ref = ref $$which;
-            my $addr = main::objaddr $$which;
+            my $addr = do { no overloading; pack 'J', $$which; };
             $$which = "$ref ($addr)";
         }
     }
@@ -7567,7 +7800,8 @@ sub _operator_equal {
 
     return 0 unless defined $other;
     return 0 unless ref $other;
-    return main::objaddr $self == main::objaddr $other;
+    no overloading;
+    return $self == $other;
 }
 
 sub _operator_not_equal {
@@ -7718,7 +7952,7 @@ sub finish_property_setup {
         ;
 
         # The defaults apply only to unassigned characters
-        $default_R .= '$gc->table("Cn") & $default;';
+        $default_R .= '$gc->table("Unassigned") & $default;';
 
         if ($v_version lt v3.0.0) {
             $default = Multi_Default->new(R => $default_R, 'L');
@@ -7738,7 +7972,7 @@ sub finish_property_setup {
             if ($v_version ge 3.1.0) {
                 $default_AL .= '$default->delete_range(0xFDD0, 0xFDEF);';
             }
-            $default_AL .= '$gc->table("Cn") & $default';
+            $default_AL .= '$gc->table("Unassigned") & $default';
             $default = Multi_Default->new(AL => $default_AL,
                                           R => $default_R,
                                           'L');
@@ -8353,6 +8587,17 @@ END
     return @return;
 }
 
+sub output_perl_charnames_line ($$) {
+
+    # Output the entries in Perl_charnames specially, using 5 digits instead
+    # of four.  This makes the entries a constant length, and simplifies
+    # charnames.pm which this table is for.  Unicode can have 6 digit
+    # ordinals, but they are all private use or noncharacters which do not
+    # have names, so won't be in this table.
+
+    return sprintf "%05X\t%s\n", $_[0], $_[1];
+}
+
 { # Closure
     # This is used to store the range list of all the code points usable when
     # the little used $compare_versions feature is enabled.
@@ -8528,7 +8773,7 @@ END
                     $file->carp_bad_line("Unexpected property '$property_name'.  Skipped");
                     next LINE;
                 }
-                $property_addr = main::objaddr($property_object);
+                { no overloading; $property_addr = pack 'J', $property_object; }
 
                 # Defer changing names until have a line that is acceptable
                 # (the 'next' statement above means is unacceptable)
@@ -8580,7 +8825,7 @@ END
                                             if $file->has_missings_defaults;
                     foreach my $default_ref (@missings_list) {
                         my $default = $default_ref->[0];
-                        my $addr = objaddr property_ref($default_ref->[1]);
+                        my $addr = do { no overloading; pack 'J', property_ref($default_ref->[1]); };
 
                         # For string properties, the default is just what the
                         # file says, but non-string properties should already
@@ -8627,6 +8872,7 @@ END
                             else {
                                 $default_map = $missings;
                             }
+
                             # And store it with the property for outside use.
                             $property_object->set_default_map($default_map);
                         }
@@ -8794,23 +9040,6 @@ END
     }
 }
 
-# XXX Unused until revise charnames;
-#sub check_and_handle_compound_name {
-#    This looks at Name properties for parenthesized components and splits
-#    them off.  Thus it finds FF as an equivalent to Form Feed.
-#    my $code_point = shift;
-#    my $name = shift;
-#    if ($name =~ /^ ( .*? ) ( \s* ) \( ( [^)]* ) \) (.*) $/x) {
-#        #local $to_trace = 1 if main::DEBUG;
-#        trace $1, $2, $3, $4 if main::DEBUG && $to_trace;
-#        push @more_Names, "$code_point; $1";
-#        push @more_Names, "$code_point; $3";
-#        Carp::my_carp_bug("Expecting blank space before left parenthesis in '$_'.  Proceeding and assuming it was there;") if $2 ne " ";
-#        Carp::my_carp_bug("Not expecting anything after the right parenthesis in '$_'.  Proceeding and ignoring that;") if $4 ne "";
-#    }
-#    return;
-#}
-
 { # Closure for UnicodeData.txt handling
 
     # This file was the first one in the UCD; its design leads to some
@@ -8818,7 +9047,7 @@ END
     # 0041;LATIN CAPITAL LETTER A;Lu;0;L;;;;;N;;;;0061;
     # The fields in order are:
     my $i = 0;            # The code point is in field 0, and is shifted off.
-    my $NAME = $i++;      # character name (e.g. "LATIN CAPITAL LETTER A")
+    my $CHARNAME = $i++;  # character name (e.g. "LATIN CAPITAL LETTER A")
     my $CATEGORY = $i++;  # category (e.g. "Lu")
     my $CCC = $i++;       # Canonical combining class (e.g. "230")
     my $BIDI = $i++;      # directional class (e.g. "L")
@@ -8837,7 +9066,14 @@ END
 
     # This routine in addition outputs these extra fields:
     my $DECOMP_TYPE = $i++; # Decomposition type
-    my $DECOMP_MAP = $i++;  # Must be last; another decomposition mapping
+
+    # These fields are modifications of ones above, and are usually
+    # suppressed; they must come last, as for speed, the loop upper bound is
+    # normally set to ignore them
+    my $NAME = $i++;        # This is the strict name field, not the one that
+                            # charnames uses.
+    my $DECOMP_MAP = $i++;  # Strict decomposition mapping; not the one used
+                            # by Unicode::Normalize
     my $last_field = $i - 1;
 
     # All these are read into an array for each line, with the indices defined
@@ -8850,31 +9086,44 @@ END
     $field_names[$BIDI] = 'Bidi_Class';
     $field_names[$CATEGORY] = 'General_Category';
     $field_names[$CCC] = 'Canonical_Combining_Class';
+    $field_names[$CHARNAME] = 'Perl_Charnames';
     $field_names[$COMMENT] = 'ISO_Comment';
     $field_names[$DECOMP_MAP] = 'Decomposition_Mapping';
     $field_names[$DECOMP_TYPE] = 'Decomposition_Type';
-    $field_names[$LOWER] = 'Simple_Lowercase_Mapping';
+    $field_names[$LOWER] = 'Lowercase_Mapping';
     $field_names[$MIRRORED] = 'Bidi_Mirrored';
     $field_names[$NAME] = 'Name';
     $field_names[$NUMERIC] = 'Numeric_Value';
     $field_names[$NUMERIC_TYPE_OTHER_DIGIT] = 'Numeric_Type';
     $field_names[$PERL_DECIMAL_DIGIT] = 'Perl_Decimal_Digit';
     $field_names[$PERL_DECOMPOSITION] = 'Perl_Decomposition_Mapping';
-    $field_names[$TITLE] = 'Simple_Titlecase_Mapping';
+    $field_names[$TITLE] = 'Titlecase_Mapping';
     $field_names[$UNICODE_1_NAME] = 'Unicode_1_Name';
-    $field_names[$UPPER] = 'Simple_Uppercase_Mapping';
-
-    # Some of these need a little more explanation.  The $PERL_DECIMAL_DIGIT
-    # field does not lead to an official Unicode property, but is used in
-    # calculating the Numeric_Type.  Perl however, creates a file from this
-    # field, so a Perl property is created from it.  Similarly, the Other
-    # Digit field is used only for calculating the Numeric_Type, and so it can
-    # be safely re-used as the place to store the value for Numeric_Type;
-    # hence it is referred to as $NUMERIC_TYPE_OTHER_DIGIT.  The input field
-    # named $PERL_DECOMPOSITION is a combination of both the decomposition
-    # mapping and its type.  Perl creates a file containing exactly this
-    # field, so it is used for that.  The two properties are separated into
-    # two extra output fields, $DECOMP_MAP and $DECOMP_TYPE.
+    $field_names[$UPPER] = 'Uppercase_Mapping';
+
+    # Some of these need a little more explanation:
+    # The $PERL_DECIMAL_DIGIT field does not lead to an official Unicode
+    #   property, but is used in calculating the Numeric_Type.  Perl however,
+    #   creates a file from this field, so a Perl property is created from it.
+    # Similarly, the Other_Digit field is used only for calculating the
+    #   Numeric_Type, and so it can be safely re-used as the place to store
+    #   the value for Numeric_Type; hence it is referred to as
+    #   $NUMERIC_TYPE_OTHER_DIGIT.
+    # The input field named $PERL_DECOMPOSITION is a combination of both the
+    #   decomposition mapping and its type.  Perl creates a file containing
+    #   exactly this field, so it is used for that.  The two properties are
+    #   separated into two extra output fields, $DECOMP_MAP and $DECOMP_TYPE.
+    #   $DECOMP_MAP is usually suppressed (unless the lists are changed to
+    #   output it), as Perl doesn't use it directly.
+    # The input field named here $CHARNAME is used to construct the
+    #   Perl_Charnames property, which is a combination of the Name property
+    #   (which the input field contains), and the Unicode_1_Name property, and
+    #   others from other files.  Since, the strict Name property is not used
+    #   by Perl, this field is used for the table that Perl does use.  The
+    #   strict Name property table is usually suppressed (unless the lists are
+    #   changed to output it), so it is accumulated in a separate field,
+    #   $NAME, which to save time is discarded unless the table is actually to
+    #   be output
 
     # This file is processed like most in this program.  Control is passed to
     # process_generic_property_file() which calls filter_UnicodeData_line()
@@ -8921,14 +9170,33 @@ END
         my $file = shift;
         Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
+        # Create a new property specially located that is a combination of the
+        # various Name properties: Name, Unicode_1_Name, Named Sequences, and
+        # Name_Alias properties.  (The final duplicates elements of the
+        # first.)  A comment for it will later be constructed based on the
+        # actual properties present and used
+        $perl_charname = Property->new('Perl_Charnames',
+                       Core_Access => '\N{...} and "use charnames"',
+                       Default_Map => "",
+                       Directory => File::Spec->curdir(),
+                       File => 'Name',
+                       Internal_Only_Warning => 1,
+                       Perl_Extension => 1,
+                       Range_Size_1 => \&output_perl_charnames_line,
+                       Type => $STRING,
+                       );
+
         my $Perl_decomp = Property->new('Perl_Decomposition_Mapping',
-                                        Directory => '.',
+                                        Directory => File::Spec->curdir(),
                                         File => 'Decomposition',
                                         Format => $STRING_FORMAT,
                                         Internal_Only_Warning => 1,
                                         Perl_Extension => 1,
                                         Default_Map => $CODE_POINT,
 
+                                        # normalize.pm can't cope with these
+                                        Output_Range_Counts => 0,
+
                                         # This is a specially formatted table
                                         # explicitly for normalize.pm, which
                                         # is expecting a particular format,
@@ -8969,12 +9237,18 @@ numerals.
 END
         ));
 
-        # This property is not used for generating anything else, and is
-        # usually not output.  By making it last in the list, we can just
+        # These properties are not used for generating anything else, and are
+        # usually not output.  By making them last in the list, we can just
         # change the high end of the loop downwards to avoid the work of
-        # generating a table that is just going to get thrown away.
-        if (! property_ref('Decomposition_Mapping')->to_output_map) {
-            $last_field--;
+        # generating a table(s) that is/are just going to get thrown away.
+        if (! property_ref('Decomposition_Mapping')->to_output_map
+            && ! property_ref('Name')->to_output_map)
+        {
+            $last_field = min($NAME, $DECOMP_MAP) - 1;
+        } elsif (property_ref('Decomposition_Mapping')->to_output_map) {
+            $last_field = $DECOMP_MAP;
+        } elsif (property_ref('Name')->to_output_map) {
+            $last_field = $NAME;
         }
         return;
     }
@@ -9095,7 +9369,7 @@ END
         # Certain fields just haven't been empty so far in any Unicode
         # version, so don't look at those, namely $MIRRORED, $BIDI, $CCC,
         # $CATEGORY.  This leaves just the two fields, and so we hard-code in
-        # the defaults; which are verly unlikely to ever change.
+        # the defaults; which are very unlikely to ever change.
         $fields[$UPPER] = $CODE_POINT if $fields[$UPPER] eq "";
         $fields[$LOWER] = $CODE_POINT if $fields[$LOWER] eq "";
 
@@ -9108,62 +9382,53 @@ END
         #   D7A3;<Hangul Syllable, Last>;Lo;0;L;;;;;N;;;;;
         # that define ranges.  These should be processed after the fields are
         # adjusted above, as they may override some of them; but mostly what
-        # is left is to possibly adjust the $NAME field.  The names of all the
+        # is left is to possibly adjust the $CHARNAME field.  The names of all the
         # paired lines start with a '<', but this is also true of '<control>,
         # which isn't one of these special ones.
-        if ($fields[$NAME] eq '<control>') {
+        if ($fields[$CHARNAME] eq '<control>') {
 
             # Some code points in this file have the pseudo-name
             # '<control>', but the official name for such ones is the null
-            # string.
+            # string.  For charnames.pm, we use the Unicode version 1 name
             $fields[$NAME] = "";
+            $fields[$CHARNAME] = $fields[$UNICODE_1_NAME];
 
             # We had better not be in between range lines.
             if ($in_range) {
-                $file->carp_bad_line("Expecting a closing range line, not a $fields[$NAME]'.  Trying anyway");
+                $file->carp_bad_line("Expecting a closing range line, not a $fields[$CHARNAME]'.  Trying anyway");
                 $in_range = 0;
             }
         }
-        elsif (substr($fields[$NAME], 0, 1) ne '<') {
+        elsif (substr($fields[$CHARNAME], 0, 1) ne '<') {
 
             # Here is a non-range line.  We had better not be in between range
             # lines.
             if ($in_range) {
-                $file->carp_bad_line("Expecting a closing range line, not a $fields[$NAME]'.  Trying anyway");
+                $file->carp_bad_line("Expecting a closing range line, not a $fields[$CHARNAME]'.  Trying anyway");
                 $in_range = 0;
             }
-            # XXX until charnames catches up.
-#            if ($fields[$NAME] =~ s/- $cp $//x) {
-#
-#                # These are code points whose names end in their code points,
-#                # which means the names are algorithmically derivable from the
-#                # code points.  To shorten the output Name file, the algorithm
-#                # for deriving these is placed in the file instead of each
-#                # code point, so they have map type $CP_IN_NAME
-#                $fields[$NAME] = $CMD_DELIM
-#                                 . $MAP_TYPE_CMD
-#                                 . '='
-#                                 . $CP_IN_NAME
-#                                 . $CMD_DELIM
-#                                 . $fields[$NAME];
-#            }
-
-            # Some official names are really two alternate names with one in
-            # parentheses.  What we do here is use the full official one for
-            # the standard property (stored just above), but for the charnames
-            # table, we add two more entries, one for each of the alternate
-            # ones.
-            # elsif name ne ""
-            #check_and_handle_compound_name($cp, $fields[$NAME]);
-            #check_and_handle_compound_name($cp, $unicode_1_name);
-            # XXX until charnames catches up.
-        }
-        elsif ($fields[$NAME] =~ /^<(.+), First>$/) {
-            $fields[$NAME] = $1;
+            if ($fields[$CHARNAME] =~ s/- $cp $//x) {
+
+                # These are code points whose names end in their code points,
+                # which means the names are algorithmically derivable from the
+                # code points.  To shorten the output Name file, the algorithm
+                # for deriving these is placed in the file instead of each
+                # code point, so they have map type $CP_IN_NAME
+                $fields[$CHARNAME] = $CMD_DELIM
+                                 . $MAP_TYPE_CMD
+                                 . '='
+                                 . $CP_IN_NAME
+                                 . $CMD_DELIM
+                                 . $fields[$CHARNAME];
+            }
+            $fields[$NAME] = $fields[$CHARNAME];
+        }
+        elsif ($fields[$CHARNAME] =~ /^<(.+), First>$/) {
+            $fields[$CHARNAME] = $fields[$NAME] = $1;
 
             # Here we are at the beginning of a range pair.
             if ($in_range) {
-                $file->carp_bad_line("Expecting a closing range line, not a beginning one, $fields[$NAME]'.  Trying anyway");
+                $file->carp_bad_line("Expecting a closing range line, not a beginning one, $fields[$CHARNAME]'.  Trying anyway");
             }
             $in_range = 1;
 
@@ -9173,20 +9438,22 @@ END
             $force_output = 1;
 
         }
-        elsif ($fields[$NAME] !~ s/^<(.+), Last>$/$1/) {
-            $file->carp_bad_line("Unexpected name starting with '<' $fields[$NAME].  Ignoring this line.");
+        elsif ($fields[$CHARNAME] !~ s/^<(.+), Last>$/$1/) {
+            $file->carp_bad_line("Unexpected name starting with '<' $fields[$CHARNAME].  Ignoring this line.");
             $_ = "";
             return;
         }
         else { # Here, we are at the last line of a range pair.
 
             if (! $in_range) {
-                $file->carp_bad_line("Unexpected end of range $fields[$NAME] when not in one.  Ignoring this line.");
+                $file->carp_bad_line("Unexpected end of range $fields[$CHARNAME] when not in one.  Ignoring this line.");
                 $_ = "";
                 return;
             }
             $in_range = 0;
 
+            $fields[$NAME] = $fields[$CHARNAME];
+
             # Check that the input is valid: that the closing of the range is
             # the same as the beginning.
             foreach my $i (0 .. $last_field) {
@@ -9195,8 +9462,8 @@ END
             }
 
             # The processing differs depending on the type of range,
-            # determined by its $NAME
-            if ($fields[$NAME] =~ /^Hangul Syllable/) {
+            # determined by its $CHARNAME
+            if ($fields[$CHARNAME] =~ /^Hangul Syllable/) {
 
                 # Check that the data looks right.
                 if ($decimal_previous_cp != $SBase) {
@@ -9220,20 +9487,22 @@ END
 
                 # This range is stored in our internal structure with its
                 # own map type, different from all others.
-                $previous_fields[$NAME] = $CMD_DELIM
+                $previous_fields[$CHARNAME] = $previous_fields[$NAME]
+                                        = $CMD_DELIM
                                           . $MAP_TYPE_CMD
                                           . '='
                                           . $HANGUL_SYLLABLE
                                           . $CMD_DELIM
-                                          . $fields[$NAME];
+                                          . $fields[$CHARNAME];
             }
-            elsif ($fields[$NAME] =~ /^CJK/) {
+            elsif ($fields[$CHARNAME] =~ /^CJK/) {
 
                 # The name for these contains the code point itself, and all
                 # are defined to have the same base name, regardless of what
                 # is in the file.  They are stored in our internal structure
                 # with a map type of $CP_IN_NAME
-                $previous_fields[$NAME] = $CMD_DELIM
+                $previous_fields[$CHARNAME] = $previous_fields[$NAME]
+                                        = $CMD_DELIM
                                            . $MAP_TYPE_CMD
                                            . '='
                                            . $CP_IN_NAME
@@ -9248,10 +9517,10 @@ END
                 # null, as there are no names for the private use and
                 # surrogate code points.
 
-                $previous_fields[$NAME] = "";
+                $previous_fields[$CHARNAME] = $previous_fields[$NAME] = "";
             }
             else {
-                $file->carp_bad_line("Unexpected code point range $fields[$NAME] because category is $fields[$CATEGORY].  Attempting to process it.");
+                $file->carp_bad_line("Unexpected code point range $fields[$CHARNAME] because category is $fields[$CATEGORY].  Attempting to process it.");
             }
 
             # The first line of the range caused everything else to be output,
@@ -9280,6 +9549,7 @@ END
             # code in this subroutine that does the same thing, but doesn't
             # know about these ranges.
             $_ = "";
+
             return;
         }
 
@@ -9503,6 +9773,18 @@ END
     }
 } # End closure for UnicodeData
 
+sub process_GCB_test {
+
+    my $file = shift;
+    Carp::carp_extra_args(\@_) if main::DEBUG && @_;
+
+    while ($file->next_line) {
+        push @backslash_X_tests, $_;
+    }
+
+    return;
+}
+
 sub process_NamedSequences {
     # NamedSequences.txt entries are just added to an array.  Because these
     # don't look like the other tables, they have their own handler.
@@ -9511,7 +9793,6 @@ sub process_NamedSequences {
     #
     # This just adds the sequence to an array for later handling
 
-    return; # XXX Until charnames catches up
     my $file = shift;
     Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
@@ -9522,7 +9803,12 @@ sub process_NamedSequences {
                 "Doesn't look like 'KHMER VOWEL SIGN OM;17BB 17C6'");
             next;
         }
-        push @named_sequences, "$sequence\t\t$name";
+
+        # Note single \t in keeping with special output format of
+        # Perl_charnames.  But it turns out that the code points don't have to
+        # be 5 digits long, like the rest, based on the internal workings of
+        # charnames.pm.  This could be easily changed for consistency.
+        push @named_sequences, "$sequence\t$name";
     }
     return;
 }
@@ -9597,26 +9883,35 @@ sub filter_arabic_shaping_line {
 
 sub setup_special_casing {
     # SpecialCasing.txt contains the non-simple case change mappings.  The
-    # simple ones are in UnicodeData.txt, and should already have been read
-    # in.
-    # This routine initializes the full mappings to the simple, then as each
-    # line is processed, it overrides the simple ones.
+    # simple ones are in UnicodeData.txt, which should already have been read
+    # in to the full property data structures, so as to initialize these with
+    # the simple ones.  Then the SpecialCasing.txt entries overwrite the ones
+    # which have different full mappings.
+
+    # This routine sees if the simple mappings are to be output, and if so,
+    # copies what has already been put into the full mapping tables, while
+    # they still contain only the simple mappings.
+
+    # The reason it is done this way is that the simple mappings are probably
+    # not going to be output, so it saves work to initialize the full tables
+    # with the simple mappings, and then overwrite those relatively few
+    # entries in them that have different full mappings, and thus skip the
+    # simple mapping tables altogether.
 
     my $file= shift;
     Carp::carp_extra_args(\@_) if main::DEBUG && @_;
 
     # For each of the case change mappings...
     foreach my $case ('lc', 'tc', 'uc') {
+        my $full = property_ref($case);
+        unless (defined $full && ! $full->is_empty) {
+            Carp::my_carp_bug("Need to process UnicodeData before SpecialCasing.  Only special casing will be generated.");
+        }
 
         # The simple version's name in each mapping merely has an 's' in front
         # of the full one's
         my $simple = property_ref('s' . $case);
-        unless (defined $simple && ! $simple->is_empty) {
-            Carp::my_carp_bug("Need to process UnicodeData before SpecialCasing.  Only special casing will be generated.");
-        }
-
-        # Initialize the full case mappings with the simple ones.
-        property_ref($case)->initialize($simple);
+        $simple->initialize($full) if $simple->to_output_map();
     }
 
     return;
@@ -10520,11 +10815,6 @@ sub compile_perl() {
         $ASCII->initialize([ 0..127 ]);
     }
 
-    # A number of the Perl synonyms have a restricted-range synonym whose name
-    # begins with Posix.  This hash gets filled in with them, so that they can
-    # be populated in a small loop.
-    my %posix_equivalent;
-
     # Get the best available case definitions.  Early Unicode versions didn't
     # have Uppercase and Lowercase defined, so use the general category
     # instead for them.
@@ -10537,7 +10827,10 @@ sub compile_perl() {
         $Lower->set_equivalent_to($gc->table('Lowercase_Letter'),
                                                                 Related => 1);
     }
-    $posix_equivalent{'Lower'} = $Lower;
+    $perl->add_match_table("PosixLower",
+                            Description => "[a-z]",
+                            Initialize => $Lower & $ASCII,
+                            );
 
     my $Upper = $perl->add_match_table('Upper');
     my $Unicode_Upper = property_ref('Uppercase');
@@ -10548,7 +10841,10 @@ sub compile_perl() {
         $Upper->set_equivalent_to($gc->table('Uppercase_Letter'),
                                                                 Related => 1);
     }
-    $posix_equivalent{'Upper'} = $Upper;
+    $perl->add_match_table("PosixUpper",
+                            Description => "[A-Z]",
+                            Initialize => $Upper & $ASCII,
+                            );
 
     # Earliest releases didn't have title case.  Initialize it to empty if not
     # otherwise present
@@ -10619,8 +10915,7 @@ sub compile_perl() {
     # one whose name generally begins with Posix that is posix-compliant, and
     # one that matches Unicode characters beyond the Posix, ASCII range
 
-    my $Alpha = $perl->add_match_table('Alpha',
-                        Description => '[[:Alpha:]] extended beyond ASCII');
+    my $Alpha = $perl->add_match_table('Alpha');
 
     # Alphabetic was not present in early releases
     my $Alphabetic = property_ref('Alphabetic');
@@ -10638,14 +10933,21 @@ sub compile_perl() {
                             + $gc->table('Mn')
                             + $gc->table('Mc'));
         $Alpha += $gc->table('Nl') if defined $gc->table('Nl');
+        $Alpha->add_description('Alphabetic');
     }
-    $posix_equivalent{'Alpha'} = $Alpha;
+    $perl->add_match_table("PosixAlpha",
+                            Description => "[A-Za-z]",
+                            Initialize => $Alpha & $ASCII,
+                            );
 
     my $Alnum = $perl->add_match_table('Alnum',
-                        Description => "[[:Alnum:]] extended beyond ASCII",
+                        Description => 'Alphabetic and (Decimal) Numeric',
                         Initialize => $Alpha + $gc->table('Decimal_Number'),
                         );
-    $posix_equivalent{'Alnum'} = $Alnum;
+    $perl->add_match_table("PosixAlnum",
+                            Description => "[A-Za-z0-9]",
+                            Initialize => $Alnum & $ASCII,
+                            );
 
     my $Word = $perl->add_match_table('Word',
                                 Description => '\w, including beyond ASCII',
@@ -10654,7 +10956,7 @@ sub compile_perl() {
     my $Pc = $gc->table('Connector_Punctuation'); # 'Pc' Not in release 1
     $Word += $Pc if defined $Pc;
 
-    # There is no [[:Word:]], so the name doesn't begin with Posix.
+    # This is a Perl extension, so the name doesn't begin with Posix.
     $perl->add_match_table('PerlWord',
                     Description => '\w, restricted to ASCII = [A-Za-z0-9_]',
                     Initialize => $Word & $ASCII,
@@ -10671,7 +10973,10 @@ sub compile_perl() {
                                             -   0x200B, # ZWSP
                                 );
     $Blank->add_alias('HorizSpace');        # Another name for it.
-    $posix_equivalent{'Blank'} = $Blank;
+    $perl->add_match_table("PosixBlank",
+                            Description => "\\t and ' '",
+                            Initialize => $Blank & $ASCII,
+                            );
 
     my $VertSpace = $perl->add_match_table('VertSpace',
                             Description => '\v',
@@ -10686,10 +10991,13 @@ sub compile_perl() {
     # No Posix equivalent for vertical space
 
     my $Space = $perl->add_match_table('Space',
-        Description => '\s including beyond ASCII plus vertical tab = [[:Space:]]',
-        Initialize => $Blank + $VertSpace,
+                Description => '\s including beyond ASCII plus vertical tab',
+                Initialize => $Blank + $VertSpace,
     );
-    $posix_equivalent{'Space'} = $Space;
+    $perl->add_match_table("PosixSpace",
+                            Description => "\\t, \\n, \\cK, \\f, \\r, and ' '.  (\\cK is vertical tab)",
+                            Initialize => $Space & $ASCII,
+                            );
 
     # Perl's traditional space doesn't include Vertical Tab
     my $SpacePerl = $perl->add_match_table('SpacePerl',
@@ -10702,9 +11010,12 @@ sub compile_perl() {
                             );
 
     my $Cntrl = $perl->add_match_table('Cntrl',
-                        Description => "[[:Cntrl:]] extended beyond ASCII");
+                                        Description => 'Control characters');
     $Cntrl->set_equivalent_to($gc->table('Cc'), Related => 1);
-    $posix_equivalent{'Cntrl'} = $Cntrl;
+    $perl->add_match_table("PosixCntrl",
+                            Description => "ASCII control characters: NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, HT, LF, VT, FF, CR, SO, SI, DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EOM, SUB, ESC, FS, GS, RS, US, and DEL",
+                            Initialize => $Cntrl & $ASCII,
+                            );
 
     # $controls is a temporary used to construct Graph.
     my $controls = Range_List->new(Initialize => $gc->table('Unassigned')
@@ -10714,61 +11025,62 @@ sub compile_perl() {
 
     # Graph is  ~space &  ~(Cc|Cs|Cn) = ~(space + $controls)
     my $Graph = $perl->add_match_table('Graph',
-                        Description => "[[:Graph:]] extended beyond ASCII",
+                        Description => 'Characters that are graphical',
                         Initialize => ~ ($Space + $controls),
                         );
-    $posix_equivalent{'Graph'} = $Graph;
+    $perl->add_match_table("PosixGraph",
+                            Description =>
+                                '[-!"#$%&\'()*+,./:;<>?@[\\\]^_`{|}~0-9A-Za-z]',
+                            Initialize => $Graph & $ASCII,
+                            );
 
-    my $Print = $perl->add_match_table('Print',
-                        Description => "[[:Print:]] extended beyond ASCII",
-                        Initialize => $Space + $Graph - $gc->table('Control'),
+    $print = $perl->add_match_table('Print',
+                        Description => 'Characters that are graphical plus space characters (but no controls)',
+                        Initialize => $Blank + $Graph - $gc->table('Control'),
                         );
-    $posix_equivalent{'Print'} = $Print;
+    $perl->add_match_table("PosixPrint",
+                            Description =>
+                              '[- 0-9A-Za-z!"#$%&\'()*+,./:;<>?@[\\\]^_`{|}~]',
+                            Initialize => $print & $ASCII,
+                            );
 
     my $Punct = $perl->add_match_table('Punct');
     $Punct->set_equivalent_to($gc->table('Punctuation'), Related => 1);
 
     # \p{punct} doesn't include the symbols, which posix does
     $perl->add_match_table('PosixPunct',
-                            Description => "[[:Punct:]]",
-                            Initialize => $ASCII & ($gc->table('Punctuation')
-                                                    + $gc->table('Symbol')),
-                            );
+        Description => '[-!"#$%&\'()*+,./:;<>?@[\\\]^_`{|}~]',
+        Initialize => $ASCII & ($gc->table('Punctuation')
+                                + $gc->table('Symbol')),
+        );
 
     my $Digit = $perl->add_match_table('Digit',
                             Description => '\d, extended beyond just [0-9]');
     $Digit->set_equivalent_to($gc->table('Decimal_Number'), Related => 1);
-    $posix_equivalent{'Digit'} = $Digit;
+    my $PosixDigit = $perl->add_match_table("PosixDigit",
+                                            Description => '[0-9]',
+                                            Initialize => $Digit & $ASCII,
+                                            );
 
-    # AHex was not present in early releases
-    my $Xdigit = $perl->add_match_table('XDigit',
-                                        Description => '[0-9A-Fa-f]');
-    my $AHex = property_ref('ASCII_Hex_Digit');
-    if (defined $AHex && ! $AHex->is_empty) {
-        $Xdigit->set_equivalent_to($AHex->table('Y'), Related => 1);
+    # Hex_Digit was not present in first release
+    my $Xdigit = $perl->add_match_table('XDigit');
+    my $Hex = property_ref('Hex_Digit');
+    if (defined $Hex && ! $Hex->is_empty) {
+        $Xdigit->set_equivalent_to($Hex->table('Y'), Related => 1);
     }
     else {
-        # (Have to use hex because could be running on an non-ASCII machine,
-        # and we want the Unicode (ASCII) values)
-        $Xdigit->initialize([ 0x30..0x39, 0x41..0x46, 0x61..0x66 ]);
-    }
-
-    # Now, add the ASCII-restricted tables that get uniform treatment
-    while (my ($name, $table) = each %posix_equivalent) {
-        $perl->add_match_table("Posix$name",
-                                Description => "[[:$name:]]",
-                                Initialize => $table & $ASCII,
-                                );
+        # (Have to use hex instead of e.g. '0', because could be running on an
+        # non-ASCII machine, and we want the Unicode (ASCII) values)
+        $Xdigit->initialize([ 0x30..0x39, 0x41..0x46, 0x61..0x66,
+                              0xFF10..0xFF19, 0xFF21..0xFF26, 0xFF41..0xFF46]);
+        $Xdigit->add_description('[0-9A-Fa-f] and corresponding fullwidth versions, like U+FF10: FULLWIDTH DIGIT ZERO');
     }
-    $perl->table('PosixDigit')->add_description('\d, restricted to ASCII');
-    $perl->table('PosixDigit')->add_description('[0-9]');
-
 
     my $dt = property_ref('Decomposition_Type');
     $dt->add_match_table('Non_Canon', Full_Name => 'Non_Canonical',
         Initialize => ~ ($dt->table('None') + $dt->table('Canonical')),
         Perl_Extension => 1,
-        Note => 'Perl extension consisting of the union of all non-canonical decompositions',
+        Note => 'Union of all non-canonical decompositions',
         );
 
     # _CanonDCIJ is equivalent to Soft_Dotted, but if on a release earlier
@@ -10795,41 +11107,81 @@ sub compile_perl() {
     }
 
     # These are used in Unicode's definition of \X
+    my $begin = $perl->add_match_table('_X_Begin', Perl_Extension => 1);
+    my $extend = $perl->add_match_table('_X_Extend', Perl_Extension => 1);
+
     my $gcb = property_ref('Grapheme_Cluster_Break');
-    #my $extend = $perl->add_match_table('_X_Extend');
-    my $extend = $perl->add_match_table('_GCB_Extend');
-    # XXX until decide what todo my $begin = $perl->add_match_table('_X_Begin');
-    if (defined $gcb) {
-        $extend += $gcb->table('Extend') + $gcb->table('SpacingMark')
-        #$begin += ~ ($gcb->table('Control')
-        #             + $gcb->table('CR')
-        #             + $gcb->table('LF'));
+
+    # The 'extended' grapheme cluster came in 5.1.  The non-extended
+    # definition differs too much from the traditional Perl one to use.
+    if (defined $gcb && defined $gcb->table('SpacingMark')) {
+
+        # Note that assumes HST is defined; it came in an earlier release than
+        # GCB.  In the line below, two negatives means: yes hangul
+        $begin += ~ property_ref('Hangul_Syllable_Type')
+                                                    ->table('Not_Applicable')
+               + ~ ($gcb->table('Control')
+                    + $gcb->table('CR')
+                    + $gcb->table('LF'));
+        $begin->add_comment('For use in \X; matches: Hangul_Syllable | ! Control');
+
+        $extend += $gcb->table('Extend') + $gcb->table('SpacingMark');
+        $extend->add_comment('For use in \X; matches: Extend | SpacingMark');
     }
     else {    # Old definition, used on early releases.
         $extend += $gc->table('Mark')
-                    + 0x200C    # ZWNJ
-                    + 0x200D;    # ZWJ
-        #$begin += ~ $extend;
-    }
-
-    # Create a new property specially located that is a combination of the
-    # various Name properties: Name, Unicode_1_Name, Named Sequences, and
-    # Name_Alias properties.  (The final duplicates elements of the first.)  A
-    # comment for it is constructed based on the actual properties present and
-    # used
-    my $perl_charname = Property->new('Perl_Charnames',
-                                Core_Access => '\N{...} and charnames.pm',
-                                Default_Map => "",
-                                Directory => '.',
-                                File => 'Name',
-                                Internal_Only_Warning => 1,
-                                Perl_Extension => 1,
-                                Range_Size_1 => 1,
-                                Type => $STRING,
-                                Initialize => property_ref('Unicode_1_Name'),
-                                );
-    # Name overrides Unicode_1_Name
-    $perl_charname->property_add_or_replace_non_nulls(property_ref('Name'));
+                + 0x200C    # ZWNJ
+                + 0x200D;   # ZWJ
+        $begin += ~ $extend;
+
+        # Here we may have a release that has the regular grapheme cluster
+        # defined, or a release that doesn't have anything defined.
+        # We set things up so the Perl core degrades gracefully, possibly with
+        # placeholders that match nothing.
+
+        if (! defined $gcb) {
+            $gcb = Property->new('GCB', Status => $PLACEHOLDER);
+        }
+        my $hst = property_ref('HST');
+        if (!defined $hst) {
+            $hst = Property->new('HST', Status => $PLACEHOLDER);
+            $hst->add_match_table('Not_Applicable',
+                                Initialize => $Any,
+                                Matches_All => 1);
+        }
+
+        # On some releases, here we may not have the needed tables for the
+        # perl core, in some releases we may.
+        foreach my $name (qw{ L LV LVT T V prepend }) {
+            my $table = $gcb->table($name);
+            if (! defined $table) {
+                $table = $gcb->add_match_table($name);
+                push @tables_that_may_be_empty, $table->complete_name;
+            }
+
+            # The HST property predates the GCB one, and has identical tables
+            # for some of them, so use it if we can.
+            if ($table->is_empty
+                && defined $hst
+                && defined $hst->table($name))
+            {
+                $table += $hst->table($name);
+            }
+        }
+    }
+
+    # More GCB.  If we found some hangul syllables, populate a combined
+    # table.
+    my $lv_lvt_v = $perl->add_match_table('_X_LV_LVT_V');
+    my $LV = $gcb->table('LV');
+    if ($LV->is_empty) {
+        push @tables_that_may_be_empty, $lv_lvt_v->complete_name;
+    } else {
+        $lv_lvt_v += $LV + $gcb->table('LVT') + $gcb->table('V');
+        $lv_lvt_v->add_comment('For use in \X; matches: HST=LV | HST=LVT | HST=V');
+    }
+
+    # Was previously constructed to contain both Name and Unicode_1_Name
     my @composition = ('Name', 'Unicode_1_Name');
 
     if (@named_sequences) {
@@ -10866,27 +11218,6 @@ END
         $comment .= ", and $composition[-1]";
     }
 
-    # Wait for charnames to catch up
-#    foreach my $entry (@more_Names,
-#                        split "\n", <<"END"
-#000A; LF
-#000C; FF
-#000D; CR
-#0085; NEL
-#200C; ZWNJ
-#200D; ZWJ
-#FEFF; BOM
-#FEFF; BYTE ORDER MARK
-#END
-#    ) {
-#        #local $to_trace = 1 if main::DEBUG;
-#        trace $entry if main::DEBUG && $to_trace;
-#        my ($code_point, $name) = split /\s*;\s*/, $entry;
-#        $code_point = hex $code_point;
-#        trace $code_point, $name if main::DEBUG && $to_trace;
-#        $perl_charname->add_duplicate($code_point, $name);
-#    }
-#    #$perl_charname->add_comment("This file is for charnames.pm.  It is the union of the $comment properties, plus certain commonly used but unofficial names, such as 'FF' and 'ZWNJ'.  Unicode_1_Name entries are used only for otherwise nameless code points.$alias_sentence");
     $perl_charname->add_comment(join_lines( <<END
 This file is for charnames.pm.  It is the union of the $comment properties.
 Unicode_1_Name entries are used only for otherwise nameless code
@@ -10907,7 +11238,7 @@ END
                             Type => $ENUM,
                             Initialize => $ccc,
                             File => 'CombiningClass',
-                            Directory => '.',
+                            Directory => File::Spec->curdir(),
                             );
     $perl_ccc->set_to_output_map(1);
     $perl_ccc->add_comment(join_lines(<<END
@@ -11809,6 +12140,10 @@ sub make_table_pod_entries($) {
             $parenthesized .= ')' if $parenthesized;
 
             push @info, $parenthesized if $parenthesized;
+
+            if ($table_property != $perl && $table->perl_extension) {
+                push @info, '(Perl extension)';
+            }
             push @info, "($string_count)" if $output_range_counts;
 
             # Now, we have both the entry and info so add them to the
@@ -11833,24 +12168,37 @@ sub make_table_pod_entries($) {
 sub pod_alphanumeric_sort {
     # Sort pod entries alphanumerically.
 
-    # The first few character columns are filler; and get rid of all the
-    # trailing stuff, starting with the trailing '}', so as to sort on just
-    # '\p{Name=Value'
-    my $a = lc substr($a, $FILLER);
+    # The first few character columns are filler, plus the '\p{'; and get rid
+    # of all the trailing stuff, starting with the trailing '}', so as to sort
+    # on just 'Name=Value'
+    (my $a = lc $a) =~ s/^ .*? { //x;
     $a =~ s/}.*//;
-    my $b = lc substr($b, $FILLER);
+    (my $b = lc $b) =~ s/^ .*? { //x;
     $b =~ s/}.*//;
 
+    # Determine if the two operands are both internal only or both not.
+    # Character 0 should be a '\'; 1 should be a p; 2 should be '{', so 3
+    # should be the underscore that begins internal only
+    my $a_is_internal = (substr($a, 0, 1) eq '_');
+    my $b_is_internal = (substr($b, 0, 1) eq '_');
+
+    # Sort so the internals come last in the table instead of first (which the
+    # leading underscore would otherwise indicate).
+    if ($a_is_internal != $b_is_internal) {
+        return 1 if $a_is_internal;
+        return -1
+    }
+
     # Determine if the two operands are numeric property values or not.
-    # A numeric property will look like \p{xyz: 3}.  But the number
+    # A numeric property will look like xyz: 3.  But the number
     # can begin with an optional minus sign, and may have a
-    # fraction or rational component, like \p{xyz: 3/2}.  If either
+    # fraction or rational component, like xyz: 3/2.  If either
     # isn't numeric, use alphabetic sort.
     my ($a_initial, $a_number) =
-        ($a =~ /^\\p{ ( [^:=]+ [:=] \s* ) (-? \d+ (?: [.\/] \d+)? )/ix);
+        ($a =~ /^ ( [^:=]+ [:=] \s* ) (-? \d+ (?: [.\/] \d+)? )/ix);
     return $a cmp $b unless defined $a_number;
     my ($b_initial, $b_number) =
-        ($b =~ /^\\p{ ( [^:=]+ [:=] \s* ) (-? \d+ (?: [.\/] \d+)? )/ix);
+        ($b =~ /^ ( [^:=]+ [:=] \s* ) (-? \d+ (?: [.\/] \d+)? )/ix);
     return $a cmp $b unless defined $b_number;
 
     # Here they are both numeric, but use alphabetic sort if the
@@ -12076,7 +12424,7 @@ To change this file, edit $0 instead.
 
 =head1 NAME
 
-$pod_file - Complete index of Unicode Version $string_version properties
+$pod_file - Index of Unicode Version $string_version properties in Perl
 
 =head1 DESCRIPTION
 
@@ -12215,6 +12563,9 @@ single and compound forms if applicable.
 The right column will also caution you if a property means something different
 than what might normally be expected.
 
+All single forms are Perl extensions; a few compound forms are as well, and
+are noted as such.
+
 Numbers in (parentheses) indicate the total number of code points matched by
 the property.  For emphasis, those properties that match no code points at all
 are listed as well in a separate section following the table.
@@ -12286,8 +12637,8 @@ Case_Folding is accessible through the /i modifier in regular expressions.
 
 The Name property is accessible through the \\N{} interpolation in
 double-quoted strings and regular expressions, but both usages require a C<use
-charnames;> to be specified, which also contains related functions viacode()
-and vianame().
+charnames;> to be specified, which also contains related functions viacode(),
+vianame(), and string_vianame().
 
 =head1 Unicode regular expression properties that are NOT accepted by Perl
 
@@ -12327,8 +12678,8 @@ accessible through the Perl core, although some may be accessed indirectly.
 For example, the uc() function implements the Uppercase_Mapping property and
 uses the F<Upper.pl> file found in this directory.
 
-The available files with their properties (short names in parentheses),
-and any flags or comments about them, are:
+The available files in the current installation, with their properties (short
+names in parentheses), and any flags or comments about them, are:
 
 @map_tables_actually_output
 
@@ -12708,10 +13059,14 @@ sub write_all_tables() {
                                 || ! defined $pod_directory
                                 || ! $alias->make_pod_entry;
 
+                        my $rhs = $full_property_name;
+                        if ($property != $perl && $table->perl_extension) {
+                            $rhs .= ' (Perl extension)';
+                        }
                         push @match_properties,
                             format_pod_line($indent_info_column,
                                         '\p{' . $alias->name . ': *}',
-                                        $full_property_name,
+                                        $rhs,
                                         $alias->status);
                     }
                 } # End of non-string-like property code
@@ -12809,12 +13164,11 @@ sub generate_separator($) {
             . $spaces_after;
 }
 
-sub generate_tests($$$$$$) {
+sub generate_tests($$$$$) {
     # This used only for making the test script.  It generates test cases that
     # are expected to compile successfully in perl.  Note that the lhs and
     # rhs are assumed to already be as randomized as the caller wants.
 
-    my $file_handle = shift;   # Where to output the tests
     my $lhs = shift;           # The property: what's to the left of the colon
                                #  or equals separator
     my $rhs = shift;           # The property value; what's to the right
@@ -12831,35 +13185,31 @@ sub generate_tests($$$$$$) {
     # The whole 'property=value'
     my $name = "$lhs$separator$rhs";
 
+    my @output;
     # Create a complete set of tests, with complements.
     if (defined $valid_code) {
-        printf $file_handle
-                    qq/Expect(1, $valid_code, '\\p{$name}', $warning);\n/;
-        printf $file_handle
-                    qq/Expect(0, $valid_code, '\\p{^$name}', $warning);\n/;
-        printf $file_handle
-                    qq/Expect(0, $valid_code, '\\P{$name}', $warning);\n/;
-        printf $file_handle
-                    qq/Expect(1, $valid_code, '\\P{^$name}', $warning);\n/;
+       push @output, <<"EOC"
+Expect(1, $valid_code, '\\p{$name}', $warning);
+Expect(0, $valid_code, '\\p{^$name}', $warning);
+Expect(0, $valid_code, '\\P{$name}', $warning);
+Expect(1, $valid_code, '\\P{^$name}', $warning);
+EOC
     }
     if (defined $invalid_code) {
-        printf $file_handle
-                    qq/Expect(0, $invalid_code, '\\p{$name}', $warning);\n/;
-        printf $file_handle
-                    qq/Expect(1, $invalid_code, '\\p{^$name}', $warning);\n/;
-        printf $file_handle
-                    qq/Expect(1, $invalid_code, '\\P{$name}', $warning);\n/;
-        printf $file_handle
-                    qq/Expect(0, $invalid_code, '\\P{^$name}', $warning);\n/;
-    }
-    return;
+       push @output, <<"EOC"
+Expect(0, $invalid_code, '\\p{$name}', $warning);
+Expect(1, $invalid_code, '\\p{^$name}', $warning);
+Expect(1, $invalid_code, '\\P{$name}', $warning);
+Expect(0, $invalid_code, '\\P{^$name}', $warning);
+EOC
+    }
+    return @output;
 }
 
-sub generate_error($$$$) {
+sub generate_error($$$) {
     # This used only for making the test script.  It generates test cases that
     # are expected to not only not match, but to be syntax or similar errors
 
-    my $file_handle = shift;        # Where to output to.
     my $lhs = shift;                # The property: what's to the left of the
                                     # colon or equals separator
     my $rhs = shift;                # The property value; what's to the right
@@ -12876,9 +13226,10 @@ sub generate_error($$$$) {
 
     my $property = $lhs . $separator . $rhs;
 
-    print $file_handle qq/Error('\\p{$property}');\n/;
-    print $file_handle qq/Error('\\P{$property}');\n/;
-    return;
+    return <<"EOC";
+Error('\\p{$property}');
+Error('\\P{$property}');
+EOC
 }
 
 # These are used only for making the test script
@@ -13044,14 +13395,6 @@ sub make_property_test_script() {
 
     $t_path = 'TestProp.pl' unless defined $t_path; # the traditional name
 
-    force_unlink ($t_path);
-    push @files_actually_output, $t_path;
-    my $OUT;
-    if (not open $OUT, "> $t_path") {
-        Carp::my_carp("Can't open $t_path.  Skipping: $!");
-        return;
-    }
-
     # Keep going down an order of magnitude
     # until find that adding this quantity to
     # 1 remains 1; but put an upper limit on
@@ -13068,7 +13411,10 @@ sub make_property_test_script() {
                             # use previous one
         $min_floating_slop = $next;
     }
-    print $OUT $HEADER, <DATA>;
+
+    # It doesn't matter whether the elements of this array contain single lines
+    # or multiple lines. main::write doesn't count the lines.
+    my @output;
 
     foreach my $property (property_ref('*')) {
         foreach my $table ($property->tables) {
@@ -13103,10 +13449,9 @@ sub make_property_test_script() {
                 my $already_error = ! $table->file_path;
 
                 # Generate error cases for this alias.
-                generate_error($OUT,
-                                $property_name,
-                                $table_name,
-                                $already_error);
+                push @output, generate_error($property_name,
+                                             $table_name,
+                                             $already_error);
 
                 # If the table is guaranteed to always generate an error,
                 # quit now without generating success cases.
@@ -13127,13 +13472,12 @@ sub make_property_test_script() {
                     # Don't output duplicate test cases.
                     if (! exists $test_generated{$test_name}) {
                         $test_generated{$test_name} = 1;
-                        generate_tests($OUT,
-                                        $property_name,
-                                        $standard,
-                                        $valid,
-                                        $invalid,
-                                        $warning,
-                                    );
+                        push @output, generate_tests($property_name,
+                                                     $standard,
+                                                     $valid,
+                                                     $invalid,
+                                                     $warning,
+                                                 );
                     }
                     $random = randomize_loose_name($table_name)
                 }
@@ -13145,13 +13489,12 @@ sub make_property_test_script() {
                 my $test_name = "$property_name=$random";
                 if (! exists $test_generated{$test_name}) {
                     $test_generated{$test_name} = 1;
-                    generate_tests($OUT,
-                                    $property_name,
-                                    $random,
-                                    $valid,
-                                    $invalid,
-                                    $warning,
-                                );
+                    push @output, generate_tests($property_name,
+                                                 $random,
+                                                 $valid,
+                                                 $invalid,
+                                                 $warning,
+                                             );
 
                     # If the name is a rational number, add tests for the
                     # floating point equivalent.
@@ -13193,24 +13536,22 @@ sub make_property_test_script() {
                                         if abs($table_name - $existing)
                                                 < $MAX_FLOATING_SLOP;
                                 }
-                                generate_error($OUT,
-                                            $property_name,
-                                            $table_name,
-                                            1   # 1 => already an error
-                                );
+                                push @output, generate_error($property_name,
+                                                             $table_name,
+                                                             1   # 1 => already an error
+                                              );
                             }
                             else {
 
                                 # Here the number of digits exceeds the
                                 # minimum we think is needed.  So generate a
                                 # success test case for it.
-                                generate_tests($OUT,
-                                                $property_name,
-                                                $table_name,
-                                                $valid,
-                                                $invalid,
-                                                $warning,
-                                );
+                                push @output, generate_tests($property_name,
+                                                             $table_name,
+                                                             $valid,
+                                                             $invalid,
+                                                             $warning,
+                                             );
                             }
                         }
                     }
@@ -13218,8 +13559,11 @@ sub make_property_test_script() {
             }
         }
     }
-    print $OUT "Finished();\n";
-    close $OUT;
+
+    &write($t_path, [<DATA>,
+                    @output,
+                    (map {"Test_X('$_');\n"} @backslash_X_tests),
+                    "Finished();\n"]);
     return;
 }
 
@@ -13367,6 +13711,9 @@ my @input_file_objects = (
     Input_file->new('BidiMirroring.txt', v3.0.1,
                     Property => 'Bidi_Mirroring_Glyph',
                     ),
+    Input_file->new("NormalizationTest.txt", v3.0.1,
+                    Skip => 1,
+                    ),
     Input_file->new('CaseFolding.txt', v3.0.1,
                     Pre_Handler => \&setup_case_folding,
                     Each_Line_Handler =>
@@ -13404,6 +13751,18 @@ my @input_file_objects = (
                     Property => 'Grapheme_Cluster_Break',
                     Has_Missings_Defaults => $NOT_IGNORED,
                     ),
+    Input_file->new("$AUXILIARY/GCBTest.txt", v4.1.0,
+                    Handler => \&process_GCB_test,
+                    ),
+    Input_file->new("$AUXILIARY/LBTest.txt", v4.1.0,
+                    Skip => 1,
+                    ),
+    Input_file->new("$AUXILIARY/SBTest.txt", v4.1.0,
+                    Skip => 1,
+                    ),
+    Input_file->new("$AUXILIARY/WBTest.txt", v4.1.0,
+                    Skip => 1,
+                    ),
     Input_file->new("$AUXILIARY/SentenceBreakProperty.txt", v4.1.0,
                     Property => 'Sentence_Break',
                     Has_Missings_Defaults => $NOT_IGNORED,
@@ -13414,6 +13773,9 @@ my @input_file_objects = (
     Input_file->new('NameAliases.txt', v5.0.0,
                     Property => 'Name_Alias',
                     ),
+    Input_file->new("BidiTest.txt", v5.2.0,
+                    Skip => 1,
+                    ),
     Input_file->new('UnihanIndicesDictionary.txt', v5.2.0,
                     Optional => 1,
                     Each_Line_Handler => \&filter_unihan_line,
@@ -13461,32 +13823,34 @@ END
 
 # Put into %potential_files a list of all the files in the directory structure
 # that could be inputs to this program, excluding those that we should ignore.
-# Also don't consider test files.  Use absolute file names because it makes it
-# easier across machine types.
+# Use absolute file names because it makes it easier across machine types.
 my @ignored_files_full_names = map { File::Spec->rel2abs(
                                      internal_file_to_platform($_))
                                 } keys %ignored_files;
 File::Find::find({
     wanted=>sub {
-        return unless /\.txt$/i;
-        return if /Test\.txt$/i;
-        my $full = File::Spec->rel2abs($_);
+        return unless /\.txt$/i;  # Some platforms change the name's case
+        my $full = lc(File::Spec->rel2abs($_));
         $potential_files{$full} = 1
-                        if ! grep { $full eq $_ } @ignored_files_full_names;
+                    if ! grep { $full eq lc($_) } @ignored_files_full_names;
         return;
     }
 }, File::Spec->curdir());
 
 my @mktables_list_output_files;
+my $old_start_time = 0;
 
-if ($write_unchanged_files) {
+if (! -e $file_list) {
+    print "'$file_list' doesn't exist, so forcing rebuild.\n" if $verbosity >= $VERBOSE;
+    $write_unchanged_files = 1;
+} elsif ($write_unchanged_files) {
     print "Not checking file list '$file_list'.\n" if $verbosity >= $VERBOSE;
 }
 else {
     print "Reading file list '$file_list'\n" if $verbosity >= $VERBOSE;
     my $file_handle;
     if (! open $file_handle, "<", $file_list) {
-        Carp::my_carp("Failed to open '$file_list' (this is expected to be missing the first time); turning on -globlist option instead: $!");
+        Carp::my_carp("Failed to open '$file_list'; turning on -globlist option instead: $!");
         $glob_list = 1;
     }
     else {
@@ -13497,6 +13861,9 @@ else {
         for my $list ( \@input, \@mktables_list_output_files ) {
             while (<$file_handle>) {
                 s/^ \s+ | \s+ $//xg;
+                if (/^ \s* \# .* Autogenerated\ starting\ on\ (\d+)/x) {
+                    $old_start_time = $1;
+                }
                 next if /^ \s* (?: \# .* )? $/x;
                 last if /^ =+ $/x;
                 my ( $file ) = split /\t/;
@@ -13516,9 +13883,9 @@ else {
 
             # The paths are stored with relative names, and with '/' as the
             # delimiter; convert to absolute on this machine
-            my $full = File::Spec->rel2abs(internal_file_to_platform($input));
+            my $full = lc(File::Spec->rel2abs(internal_file_to_platform($input)));
             $potential_files{$full} = 1
-                        if ! grep { $full eq $_ } @ignored_files_full_names;
+                        if ! grep { lc($full) eq lc($_) } @ignored_files_full_names;
         }
     }
 
@@ -13539,7 +13906,7 @@ if ($glob_list) {
 
     my @unknown_input_files;
     foreach my $file (keys %potential_files) {
-        next if grep { $file eq $_ } @known_files;
+        next if grep { lc($file) eq lc($_) } @known_files;
 
         # Here, the file is unknown to us.  Get relative path name
         $file = File::Spec->abs2rel($file);
@@ -13555,7 +13922,7 @@ if ($glob_list) {
         # If the file isn't extracted (meaning none of the directories is the
         # extracted one), just add it to the end of the list of inputs.
         if (! grep { $EXTRACTED_DIR eq $_ } @directories) {
-            push @input_file_objects, Input_file->new($file);
+            push @input_file_objects, Input_file->new($file, v0);
         }
         else {
 
@@ -13567,10 +13934,11 @@ if ($glob_list) {
             # $compare_versions is set.
             for (my $i = 0; $i < @input_file_objects; $i++) {
                 if ($input_file_objects[$i]->first_released ne v0
-                    && $input_file_objects[$i]->file ne 'DAge.txt'
-                    && $input_file_objects[$i]->file !~ /$EXTRACTED_DIR/)
+                    && lc($input_file_objects[$i]->file) ne 'dage.txt'
+                    && $input_file_objects[$i]->file !~ /$EXTRACTED_DIR/i)
                 {
-                    splice @input_file_objects, $i, 0, Input_file->new($file);
+                    splice @input_file_objects, $i, 0,
+                                                Input_file->new($file, v0);
                     last;
                 }
             }
@@ -13584,7 +13952,7 @@ The following files are unknown as to how to handle.  Assuming they are
 typical property files.  You'll know by later error messages if it worked or
 not:
 END
-        ) . join(", ", @unknown_input_files) . "\n\n");
+        ) . " " . join(", ", @unknown_input_files) . "\n\n");
     }
 } # End of looking through directory structure for more .txt files.
 
@@ -13606,9 +13974,9 @@ if ( $verbosity >= $VERBOSE ) {
 # We set $youngest to be the most recently changed input file, including this
 # program itself (done much earlier in this file)
 foreach my $in (@input_files) {
-    my $age = -M $in;
-    next unless defined $age;        # Keep going even if missing a file
-    $youngest = $age if $age < $youngest;
+    next unless -e $in;        # Keep going even if missing a file
+    my $mod_time = (stat $in)[9];
+    $youngest = $mod_time if $mod_time > $youngest;
 
     # See that the input files have distinct names, to warn someone if they
     # are adding a new one
@@ -13621,30 +13989,31 @@ foreach my $in (@input_files) {
     }
 }
 
-my $ok = ! $write_unchanged_files
-        && scalar @mktables_list_output_files;        # If none known, rebuild
+my $rebuild = $write_unchanged_files    # Rebuild: if unconditional rebuild
+              || ! scalar @mktables_list_output_files  # or if no outputs known
+              || $old_start_time < $youngest;          # or out-of-date
 
 # Now we check to see if any output files are older than youngest, if
 # they are, we need to continue on, otherwise we can presumably bail.
-if ($ok) {
+if (! $rebuild) {
     foreach my $out (@mktables_list_output_files) {
         if ( ! file_exists($out)) {
             print "'$out' is missing.\n" if $verbosity >= $VERBOSE;
-            $ok = 0;
+            $rebuild = 1;
             last;
          }
         #local $to_trace = 1 if main::DEBUG;
-        trace $youngest, -M $out if main::DEBUG && $to_trace;
-        if ( -M $out > $youngest ) {
-            #trace "$out: age: ", -M $out, ", youngest: $youngest\n" if main::DEBUG && $to_trace;
+        trace $youngest, (stat $out)[9] if main::DEBUG && $to_trace;
+        if ( (stat $out)[9] <= $youngest ) {
+            #trace "$out:  most recent mod time: ", (stat $out)[9], ", youngest: $youngest\n" if main::DEBUG && $to_trace;
             print "'$out' is too old.\n" if $verbosity >= $VERBOSE;
-            $ok = 0;
+            $rebuild = 1;
             last;
         }
     }
 }
-if ($ok) {
-    print "Files seem to be ok, not bothering to rebuild.\n";
+if (! $rebuild) {
+    print "Files seem to be ok, not bothering to rebuild.  Add '-w' option to force build\n";
     exit(0);
 }
 print "Must rebuild tables.\n" if $verbosity >= $VERBOSE;
@@ -13687,11 +14056,12 @@ if ( $file_list and $make_list ) {
         return
     }
     else {
+        my $localtime = localtime $start_time;
         print $ofh <<"END";
 #
 # $file_list -- File list for $0.
 #
-#   Autogenerated on @{[scalar localtime]}
+#   Autogenerated starting on $start_time ($localtime)
 #
 # - First section is input files
 #   ($0 itself is not listed but is automatically considered an input)
@@ -13744,28 +14114,22 @@ __DATA__
 use strict;
 use warnings;
 
-# Test the \p{} regular expression constructs.  This file is constructed by
-# mktables from the tables it generates, so if mktables is buggy, this won't
-# necessarily catch those bugs.  Tests are generated for all feasible
-# properties; a few aren't currently feasible; see is_code_point_usable()
-# in mktables for details.
+# If run outside the normal test suite on an ASCII platform, you can
+# just create a latin1_to_native() function that just returns its
+# inputs, because that's the only function used from test.pl
+require "test.pl";
+
+# Test qr/\X/ and the \p{} regular expression constructs.  This file is
+# constructed by mktables from the tables it generates, so if mktables is
+# buggy, this won't necessarily catch those bugs.  Tests are generated for all
+# feasible properties; a few aren't currently feasible; see
+# is_code_point_usable() in mktables for details.
 
 # Standard test packages are not used because this manipulates SIG_WARN.  It
 # exits 0 if every non-skipped test succeeded; -1 if any failed.
 
 my $Tests = 0;
 my $Fails = 0;
-my $Skips = 0;
-
-my $non_ASCII = (ord('A') == 65);
-
-# The first 127 ASCII characters in ordinal order, with the ones that don't
-# have Perl names (as of 5.8) replaced by dots.  The 127th is used as the
-# string delimiter
-my $ascii_to_ebcdic = "\0......\a\b\t\n.\f\r.................. !\"#\$\%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
-#for my $i (0..126) {
-#    print $i, ": ", substr($ascii_to_ebcdic, $i, 1), "\n";
-#}
 
 sub Expect($$$$) {
     my $expected = shift;
@@ -13774,39 +14138,15 @@ sub Expect($$$$) {
     my $warning_type = shift;   # Type of warning message, like 'deprecated'
                                 # or empty if none
     my $line   = (caller)[2];
+    $ord = ord(latin1_to_native(chr($ord)));
 
     # Convert the code point to hex form
     my $string = sprintf "\"\\x{%04X}\"", $ord;
 
-    # Convert the non-ASCII code points expressible as characters in Perl 5.8
-    # to their ASCII equivalents, and skip the others.
-    if ($non_ASCII && $ord < 255) {
-
-        # Dots are used as place holders in the conversion string for the
-        # non-convertible ones, so check for it first.
-        if ($ord == 0x2E) {
-            $ord = ord('.');
-        }
-        elsif ($ord < 0x7F
-                  # Any dots returned are non-convertible.
-                 && ((my $char = substr($ascii_to_ebcdic, $ord, 1)) ne '.'))
-        {
-            #print STDERR "$ord, $char, \n";
-            $ord = ord($char);
-        }
-        else {
-            $Tests++;
-            $Skips++;
-            print "ok $Tests - $string =~ $regex # Skipped: non-ASCII\n";
-            return;
-        }
-    }
-
-    # The first time through, use all warnings.
     my @tests = "";
 
-    # If the input should generate a warning, add another time through with
-    # them turned off
+    # The first time through, use all warnings.  If the input should generate
+    # a warning, add another time through with them turned off
     push @tests, "no warnings '$warning_type';" if $warning_type;
 
     foreach my $no_warnings (@tests) {
@@ -13866,9 +14206,139 @@ sub Error($) {
     return;
 }
 
+# GCBTest.txt character that separates grapheme clusters
+my $breakable_utf8 = my $breakable = chr(0xF7);
+utf8::upgrade($breakable_utf8);
+
+# GCBTest.txt character that indicates that the adjoining code points are part
+# of the same grapheme cluster
+my $nobreak_utf8 = my $nobreak = chr(0xD7);
+utf8::upgrade($nobreak_utf8);
+
+sub Test_X($) {
+    # Test qr/\X/ matches.  The input is a line from auxiliary/GCBTest.txt
+    # Each such line is a sequence of code points given by their hex numbers,
+    # separated by the two characters defined just before this subroutine that
+    # indicate that either there can or cannot be a break between the adjacent
+    # code points.  If there isn't a break, that means the sequence forms an
+    # extended grapheme cluster, which means that \X should match the whole
+    # thing.  If there is a break, \X should stop there.  This is all
+    # converted by this routine into a match:
+    #   $string =~ /(\X)/,
+    # Each \X should match the next cluster; and that is what is checked.
+
+    my $template = shift;
+
+    my $line   = (caller)[2];
+
+    # The line contains characters above the ASCII range, but in Latin1.  It
+    # may or may not be in utf8, and if it is, it may or may not know it.  So,
+    # convert these characters to 8 bits.  If knows is in utf8, simply
+    # downgrade.
+    if (utf8::is_utf8($template)) {
+        utf8::downgrade($template);
+    } else {
+
+        # Otherwise, if it is in utf8, but doesn't know it, the next lines
+        # convert the two problematic characters to their 8-bit equivalents.
+        # If it isn't in utf8, they don't harm anything.
+        use bytes;
+        $template =~ s/$nobreak_utf8/$nobreak/g;
+        $template =~ s/$breakable_utf8/$breakable/g;
+    }
+
+    # Get rid of the leading and trailing breakables
+    $template =~ s/^ \s* $breakable \s* //x;
+    $template =~ s/ \s* $breakable \s* $ //x;
+
+    # And no-breaks become just a space.
+    $template =~ s/ \s* $nobreak \s* / /xg;
+
+    # Split the input into segments that are breakable between them.
+    my @segments = split /\s*$breakable\s*/, $template;
+
+    my $string = "";
+    my $display_string = "";
+    my @should_match;
+    my @should_display;
+
+    # Convert the code point sequence in each segment into a Perl string of
+    # characters
+    foreach my $segment (@segments) {
+        my @code_points = split /\s+/, $segment;
+        my $this_string = "";
+        my $this_display = "";
+        foreach my $code_point (@code_points) {
+            $this_string .= latin1_to_native(chr(hex $code_point));
+            $this_display .= "\\x{$code_point}";
+        }
+
+        # The next cluster should match the string in this segment.
+        push @should_match, $this_string;
+        push @should_display, $this_display;
+        $string .= $this_string;
+        $display_string .= $this_display;
+    }
+
+    # If a string can be represented in both non-ut8 and utf8, test both cases
+    UPGRADE:
+    for my $to_upgrade (0 .. 1) {
+
+        if ($to_upgrade) {
+
+            # If already in utf8, would just be a repeat
+            next UPGRADE if utf8::is_utf8($string);
+
+            utf8::upgrade($string);
+        }
+
+        # Finally, do the \X match.
+        my @matches = $string =~ /(\X)/g;
+
+        # Look through each matched cluster to verify that it matches what we
+        # expect.
+        my $min = (@matches < @should_match) ? @matches : @should_match;
+        for my $i (0 .. $min - 1) {
+            $Tests++;
+            if ($matches[$i] eq $should_match[$i]) {
+                print "ok $Tests - ";
+                if ($i == 0) {
+                    print "In \"$display_string\" =~ /(\\X)/g, \\X #1";
+                } else {
+                    print "And \\X #", $i + 1,
+                }
+                print " correctly matched $should_display[$i]; line $line\n";
+            } else {
+                $matches[$i] = join("", map { sprintf "\\x{%04X}", $_ }
+                                                    unpack("U*", $matches[$i]));
+                print "not ok $Tests - In \"$display_string\" =~ /(\\X)/g, \\X #",
+                    $i + 1,
+                    " should have matched $should_display[$i]",
+                    " but instead matched $matches[$i]",
+                    ".  Abandoning rest of line $line\n";
+                next UPGRADE;
+            }
+        }
+
+        # And the number of matches should equal the number of expected matches.
+        $Tests++;
+        if (@matches == @should_match) {
+            print "ok $Tests - Nothing was left over; line $line\n";
+        } else {
+            print "not ok $Tests - There were ", scalar @should_match, " \\X matches expected, but got ", scalar @matches, " instead; line $line\n";
+        }
+    }
+
+    return;
+}
+
 sub Finished() {
     print "1..$Tests\n";
     exit($Fails ? -1 : 0);
 }
 
 Error('\p{Script=InGreek}');    # Bug #69018
+Test_X("1100 $nobreak 1161");  # Bug #70940
+Expect(0, 0x2028, '\p{Print}', ""); # Bug # 71722
+Expect(0, 0x2029, '\p{Print}', ""); # Bug # 71722
+Expect(1, 0xFF10, '\p{XDigit}', ""); # Bug # 71726