This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
regen/regcharclass.pl: Mark intermediate macros as internal
[perl5.git] / regen / regcharclass.pl
index 9c453e2..852ea0d 100755 (executable)
@@ -4,17 +4,15 @@ use strict;
 use 5.008;
 use warnings;
 use warnings FATAL => 'all';
-use Text::Wrap qw(wrap);
 use Data::Dumper;
 $Data::Dumper::Useqq= 1;
-our $hex_fmt= "0x%02X";
 
 sub DEBUG () { 0 }
 $|=1 if DEBUG;
 
-sub ASCII_PLATFORM { (ord('A') == 65) }
-
-require 'regen/regen_lib.pl';
+require './regen/regen_lib.pl';
+require './regen/charset_translations.pl';
+require "./regen/regcharclass_multi_char_folds.pl";
 
 =head1 NAME
 
@@ -22,7 +20,7 @@ CharClass::Matcher -- Generate C macros that match character classes efficiently
 
 =head1 SYNOPSIS
 
-    perl Porting/regcharclass.pl
+    perl regen/regcharclass.pl
 
 =head1 DESCRIPTION
 
@@ -44,7 +42,7 @@ the C<__DATA__> line):
 =item C<is_WHATEVER_safe(s,e,is_utf8)>
 
 Do a lookup as appropriate based on the C<is_utf8> flag. When possible
-comparisons involving octect<128 are done before checking the C<is_utf8>
+comparisons involving octet<128 are done before checking the C<is_utf8>
 flag, hopefully saving time.
 
 The version without the C<_safe> suffix should be used only when the input is
@@ -71,7 +69,7 @@ that C<s> contains at least one character.
 =item C<is_WHATEVER_cp(cp)>
 
 Check to see if the string matches a given codepoint (hypothetically a
-U32). The condition is constructed as as to "break out" as early as
+U32). The condition is constructed as to "break out" as early as
 possible if the codepoint is out of range of the condition.
 
 IOW:
@@ -110,6 +108,13 @@ include it, and it is a NULL.
 
 =back
 
+The above isn't quite complete, as for specialized purposes one can get a
+macro like C<is_WHATEVER_utf8_no_length_checks(s)>, which assumes that it is
+already known that there is enough space to hold the character starting at
+C<s>, but otherwise checks that it is well-formed.  In other words, this is
+intermediary in checking between C<is_WHATEVER_utf8(s)> and
+C<is_WHATEVER_utf8_safe(s,e)>.
+
 =head2 CODE FORMAT
 
 perltidy  -st -bt=1 -bbt=0 -pt=0 -sbt=1 -ce -nwls== "%f"
@@ -161,6 +166,8 @@ License or the Artistic License, as specified in the README file.
 #
 
 sub __uni_latin1 {
+    my $charset = shift;
+    my $a2n= shift;
     my $str= shift;
     my $max= 0;
     my @cp;
@@ -168,31 +175,27 @@ sub __uni_latin1 {
     my $only_has_invariants = 1;
     for my $ch ( split //, $str ) {
         my $cp= ord $ch;
-        push @cp, $cp;
-        push @cp_high, $cp if $cp > 255;
         $max= $cp if $max < $cp;
-        if (! ASCII_PLATFORM && $only_has_invariants) {
-            if ($cp > 255) {
-                $only_has_invariants = 0;
-            }
-            else {
-                my $temp = chr($cp);
-                utf8::upgrade($temp);
-                my @utf8 = unpack "U0C*", $temp;
-                $only_has_invariants = (@utf8 == 1 && $utf8[0] == $cp);
-            }
+        if ($cp > 255) {
+            push @cp, $cp;
+            push @cp_high, $cp;
+        }
+        else {
+            push @cp, $a2n->[$cp];
         }
     }
     my ( $n, $l, $u );
-    $only_has_invariants = $max < 128 if ASCII_PLATFORM;
+    $only_has_invariants = ($charset =~ /ascii/i) ? $max < 128 : $max < 160;
     if ($only_has_invariants) {
         $n= [@cp];
     } else {
         $l= [@cp] if $max && $max < 256;
 
-        $u= $str;
-        utf8::upgrade($u);
-        $u= [ unpack "U0C*", $u ] if defined $u;
+        my @u;
+        for my $ch ( split //, $str ) {
+            push @u, map { ord } split //, cp_2_utfbytes(ord $ch, $charset);
+        }
+        $u = \@u;
     }
     return ( \@cp, \@cp_high, $n, $l, $u );
 }
@@ -222,10 +225,11 @@ sub __clean {
     #       ( ( (cond1) && (cond2) ) ? X : Y )
     # Also similarly handles expressions like:
     #       : (cond1) ? ( (cond2) ? X : Y ) : Y )
-    # Note the inclusion of the close paren in ([:()]) and the open paren in ([()]) is
-    # purely to ensure we have a balanced set of parens in the expression which makes
-    # it easier to understand the pattern in an editor that understands paren's, we do
-    # not expect either of these cases to actually fire. - Yves
+    # Note the inclusion of the close paren in ([:()]) and the open paren in
+    # ([()]) is purely to ensure we have a balanced set of parens in the
+    # expression which makes it easier to understand the pattern in an editor
+    # that understands paren's, we do not expect either of these cases to
+    # actually fire. - Yves
     1 while $expr =~ s/
         ([:()])  \s*
             ($parens) \s*
@@ -281,12 +285,56 @@ sub __incrdepth {
 # returns the new root opcode of the tree.
 sub __cond_join {
     my ( $cond, $yes, $no )= @_;
-    return {
-        test  => $cond,
-        yes   => __incrdepth( $yes ),
-        no    => $no,
-        depth => 0,
-    };
+    if (ref $yes) {
+        return {
+            test  => $cond,
+            yes   => __incrdepth( $yes ),
+            no    => $no,
+            depth => 0,
+        };
+    }
+    else {
+        return {
+            test  => $cond,
+            yes   => $yes,
+            no    => __incrdepth($no),
+            depth => 0,
+        };
+    }
+}
+
+my $hex_fmt= "0x%02X";
+
+sub val_fmt
+{
+    my $self = shift;
+    my $arg = shift;
+
+    # Format 'arg' using the printable character if it has one, or a %x if
+    # not, returning a string containing the result
+
+    # Return what always returned for an unexpected argument
+    return $hex_fmt unless defined $arg && $arg !~ /\D/;
+
+    # We convert only things inside Latin1
+    if ($arg < 256) {
+
+        # Find the ASCII equivalent of this argument (as the current character
+        # set might not be ASCII)
+        my $char = chr $self->{n2a}->[$arg];
+
+        # If printable, return it, escaping \ and '
+        return "'$char'" if $char =~ /[^\\'[:^print:]]/a;
+        return "'\\\\'" if $char eq "\\";
+        return "'\''" if $char eq "'";
+
+        # Handle the mnemonic controls
+        my $pos = index("\a\b\e\f\n\r\t\cK", $char);
+        return "'\\" . substr("abefnrtv", $pos, 1) . "'" if $pos >= 0;
+    }
+
+    # Otherwise, just the input, formatted
+    return sprintf $hex_fmt, $arg;
 }
 
 # Methods
@@ -307,15 +355,18 @@ sub __cond_join {
 # Each string is then stored in the 'strs' subhash as a hash record
 # made up of the results of __uni_latin1, using the keynames
 # 'low','latin1','utf8', as well as the synthesized 'LATIN1', 'high', and
-# 'UTF8' which hold a merge of 'low' and their lowercase equivelents.
+# 'UTF8' which hold a merge of 'low' and their lowercase equivalents.
 #
 # Size data is tracked per type in the 'size' subhash.
 #
 # Return an object
-#
+
+my %n2a;    # Inversion of a2n, for each character set
+
 sub new {
     my $class= shift;
     my %opt= @_;
+    my %hash_return;
     for ( qw(op txt) ) {
         die "in " . __PACKAGE__ . " constructor '$_;' is a mandatory field"
           if !exists $opt{$_};
@@ -325,6 +376,17 @@ sub new {
         op    => $opt{op},
         title => $opt{title} || '',
     }, $class;
+
+    my $charset = $opt{charset};
+    my $a2n = get_a2n($charset);
+
+    # We need to construct the map going the other way if not already done
+    unless (defined $n2a{$charset}) {
+        for (my $i = 0; $i < 256; $i++) {
+            $n2a{$charset}->[$a2n->[$i]] = $i;
+        }
+    }
+
     foreach my $txt ( @{ $opt{txt} } ) {
         my $str= $txt;
         if ( $str =~ /^[""]/ ) {
@@ -332,7 +394,8 @@ sub new {
         } elsif ($str =~ / - /x ) { # A range:  Replace this element on the
                                     # list with its expansion
             my ($lower, $upper) = $str =~ / 0x (.+?) \s* - \s* 0x (.+) /x;
-            die "Format must be like '0xDEAD - 0xBEAF'; instead was '$str'" if ! defined $lower || ! defined $upper;
+            die "Format must be like '0xDEAD - 0xBEAF'; instead was '$str'"
+                                        if ! defined $lower || ! defined $upper;
             foreach my $cp (hex $lower .. hex $upper) {
                 push @{$opt{txt}}, sprintf "0x%X", $cp;
             }
@@ -343,10 +406,6 @@ sub new {
             $str= chr eval $str;
         } elsif ( $str =~ /^0x/ ) {
             $str= eval $str;
-
-            # Convert from Unicode/ASCII to native, if necessary
-            $str = utf8::unicode_to_native($str) if ! ASCII_PLATFORM
-                                                    && $str <= 0xFF;
             $str = chr $str;
         } elsif ( $str =~ / \s* \\p \{ ( .*? ) \} /x) {
             my $property = $1;
@@ -379,18 +438,31 @@ sub new {
             die "eval '$1' failed: $@" if $@;
             push @{$opt{txt}}, @results;
             next;
+        } elsif ($str =~ / ^ % \s* ( .* ) /x) { # user-furnished sub() call
+            %hash_return = eval "$1";
+            die "eval '$1' failed: $@" if $@;
+            push @{$opt{txt}}, keys %hash_return;
+            die "Only one multi character expansion currently allowed per rule"
+                                                        if  $self->{multi_maps};
+            next;
         } else {
             die "Unparsable line: $txt\n";
         }
-        my ( $cp, $cp_high, $low, $latin1, $utf8 )= __uni_latin1( $str );
+        my ( $cp, $cp_high, $low, $latin1, $utf8 )
+                                        = __uni_latin1($charset, $a2n, $str );
+        my $from;
+        if (defined $hash_return{"\"$str\""}) {
+            $from = $hash_return{"\"$str\""};
+            $from = $a2n->[$from] if $from < 256;
+        }
         my $UTF8= $low   || $utf8;
         my $LATIN1= $low || $latin1;
         my $high = (scalar grep { $_ < 256 } @$cp) ? 0 : $utf8;
         #die Dumper($txt,$cp,$low,$latin1,$utf8)
         #    if $txt=~/NEL/ or $utf8 and @$utf8>3;
 
-        @{ $self->{strs}{$str} }{qw( str txt low utf8 latin1 high cp cp_high UTF8 LATIN1 )}=
-          ( $str, $txt, $low, $utf8, $latin1, $high, $cp, $cp_high, $UTF8, $LATIN1 );
+        @{ $self->{strs}{$str} }{qw( str txt low utf8 latin1 high cp cp_high UTF8 LATIN1 from )}=
+          ( $str, $txt, $low, $utf8, $latin1, $high, $cp, $cp_high, $UTF8, $LATIN1, $from );
         my $rec= $self->{strs}{$str};
         foreach my $key ( qw(low utf8 latin1 high cp cp_high UTF8 LATIN1) ) {
             $self->{size}{$key}{ 0 + @{ $self->{strs}{$str}{$key} } }++
@@ -401,7 +473,7 @@ sub new {
         $self->{has_low}   ||= $low && @$low;
         $self->{has_high}  ||= !$low && !$latin1;
     }
-    $self->{val_fmt}= $hex_fmt;
+    $self->{n2a} = $n2a{$charset};
     $self->{count}= 0 + keys %{ $self->{strs} };
     return $self;
 }
@@ -463,27 +535,27 @@ sub pop_count ($) {
 sub _optree {
     my ( $self, $trie, $test_type, $ret_type, $else, $depth )= @_;
     return unless defined $trie;
-    if ( $self->{has_multi} and $ret_type =~ /cp|both/ ) {
-        die "Can't do 'cp' optree from multi-codepoint strings";
-    }
     $ret_type ||= 'len';
     $else= 0  unless defined $else;
     $depth= 0 unless defined $depth;
 
-    # if we have an emptry string as a key it means we are in an
+    # if we have an empty string as a key it means we are in an
     # accepting state and unless we can match further on should
     # return the value of the '' key.
     if (exists $trie->{''} ) {
         # we can now update the "else" value, anything failing to match
         # after this point should return the value from this.
+        my $prefix = $self->{strs}{ $trie->{''} };
         if ( $ret_type eq 'cp' ) {
-            $else= $self->{strs}{ $trie->{''} }{cp}[0];
-            $else= sprintf "$self->{val_fmt}", $else if $else > 9;
+            $else= $prefix->{from};
+            $else= $self->{strs}{ $trie->{''} }{cp}[0] unless defined $else;
+            $else= $self->val_fmt($else) if $else > 9;
         } elsif ( $ret_type eq 'len' ) {
             $else= $depth;
         } elsif ( $ret_type eq 'both') {
-            $else= $self->{strs}{ $trie->{''} }{cp}[0];
-            $else= sprintf "$self->{val_fmt}", $else if $else > 9;
+            $else= $prefix->{from};
+            $else= $self->{strs}{ $trie->{''} }{cp}[0] unless defined $else;
+            $else= $self->val_fmt($else) if $else > 9;
             $else= "len=$depth, $else";
         }
     }
@@ -491,21 +563,22 @@ sub _optree {
     # it means we are an accepting state (end of sequence).
     my @conds= sort { $a <=> $b } grep { length $_ } keys %$trie;
 
-    # if we havent any keys there is no further we can match and we
+    # if we haven't any keys there is no further we can match and we
     # can return the "else" value.
     return $else if !@conds;
 
+    my $test = $test_type =~ /^cp/ ? "cp" : "((const U8*)s)[$depth]";
 
-    my $test= $test_type =~ /^cp/ ? "cp" : "((U8*)s)[$depth]";
-    # first we loop over the possible keys/conditions and find out what they look like
-    # we group conditions with the same optree together.
+    # First we loop over the possible keys/conditions and find out what they
+    # look like; we group conditions with the same optree together.
     my %dmp_res;
     my @res_order;
     local $Data::Dumper::Sortkeys=1;
     foreach my $cond ( @conds ) {
 
         # get the optree for this child/condition
-        my $res= $self->_optree( $trie->{$cond}, $test_type, $ret_type, $else, $depth + 1 );
+        my $res= $self->_optree( $trie->{$cond}, $test_type, $ret_type,
+                                                            $else, $depth + 1 );
         # convert it to a string with Dumper
         my $res_code= Dumper( $res );
 
@@ -516,7 +589,8 @@ sub _optree {
         }
     }
 
-    # now that we have deduped the optrees we construct a new optree containing the merged
+    # now that we have deduped the optrees we construct a new optree
+    # containing the merged
     # results.
     my %root;
     my $node= \%root;
@@ -575,9 +649,11 @@ sub generic_optree {
     } elsif ( $latin1 ) {
         $else= __cond_join( "!( is_utf8 )", $latin1, $else );
     }
-    my $low= $self->make_trie( 'low', $opt{max_depth} );
-    if ( $low ) {
-        $else= $self->_optree( $low, $test_type, $opt{ret_type}, $else, 0 );
+    if ($opt{type} eq 'generic') {
+        my $low= $self->make_trie( 'low', $opt{max_depth} );
+        if ( $low ) {
+            $else= $self->_optree( $low, $test_type, $opt{ret_type}, $else, 0 );
+        }
     }
 
     return $else;
@@ -596,27 +672,127 @@ sub length_optree {
     die "Can't do a length_optree on type 'cp', makes no sense."
       if $type =~ /^cp/;
 
-    my ( @size, $method );
+    my $else= ( $opt{else} ||= 0 );
 
-    if ( $type eq 'generic' ) {
-        $method= 'generic_optree';
+    return $else if $self->{count} == 0;
+
+    my $method = $type =~ /generic/ ? 'generic_optree' : 'optree';
+    if ($method eq 'optree' && scalar keys %{$self->{size}{$type}} == 1) {
+
+        # Here is non-generic output (meaning that we are only generating one
+        # type), and all things that match have the same number ('size') of
+        # bytes.  The length guard is simply that we have that number of
+        # bytes.
+        my @size = keys %{$self->{size}{$type}};
+        my $cond= "((e) - (s)) >= $size[0]";
+        my $optree = $self->$method(%opt);
+        $else= __cond_join( $cond, $optree, $else );
+    }
+    elsif ($self->{has_multi}) {
+        my @size;
+
+        # Here, there can be a match of a multiple character string.  We use
+        # the traditional method which is to have a branch for each possible
+        # size (longest first) and test for the legal values for that size.
         my %sizes= (
             %{ $self->{size}{low}    || {} },
             %{ $self->{size}{latin1} || {} },
             %{ $self->{size}{utf8}   || {} }
         );
-        @size= sort { $a <=> $b } keys %sizes;
-    } else {
-        $method= 'optree';
-        @size= sort { $a <=> $b } keys %{ $self->{size}{$type} };
+        if ($method eq 'generic_optree') {
+            @size= sort { $a <=> $b } keys %sizes;
+        } else {
+            @size= sort { $a <=> $b } keys %{ $self->{size}{$type} };
+        }
+        for my $size ( @size ) {
+            my $optree= $self->$method(%opt, type => $type, max_depth => $size);
+            my $cond= "((e)-(s) > " . ( $size - 1 ).")";
+            $else= __cond_join( $cond, $optree, $else );
+        }
     }
+    else {
+        my $utf8;
+
+        # Here, has more than one possible size, and only matches a single
+        # character.  For non-utf8, the needed length is 1; for utf8, it is
+        # found by array lookup 'UTF8SKIP'.
+
+        # If want just the code points above 255, set up to look for those;
+        # otherwise assume will be looking for all non-UTF-8-invariant code
+        # poiints.
+        my $trie_type = ($type eq 'high') ? 'high' : 'utf8';
+
+        # If we do want more than the 0-255 range, find those, and if they
+        # exist...
+        if (   $opt{type} !~ /latin1/i
+            && ($utf8 = $self->make_trie($trie_type, 0)))
+        {
+
+            # ... get them into an optree, and set them up as the 'else' clause
+            $utf8 = $self->_optree( $utf8, 'depth', $opt{ret_type}, 0, 0 );
+
+            # We could make this
+            #   UTF8_IS_START(*s) && ((e) - (s)) >= UTF8SKIP(s))";
+            # to avoid doing the UTF8SKIP and subsequent branches for invariants
+            # that don't match.  But the current macros that get generated
+            # have only a few things that can match past this, so I (khw)
+            # don't think it is worth it.  (Even better would be to use
+            # calculate_mask(keys %$utf8) instead of UTF8_IS_START, and use it
+            # if it saves a bunch.  We assume that input text likely to be
+            # well-formed .
+            my $cond = "LIKELY(((e) - (s)) >= UTF8SKIP(s))";
+            $else = __cond_join($cond, $utf8, $else);
+
+            # For 'generic', we also will want the latin1 UTF-8 variants for
+            # the case where the input isn't UTF-8.
+            my $latin1;
+            if ($method eq 'generic_optree') {
+                $latin1 = $self->make_trie( 'latin1', 1);
+                $latin1= $self->_optree($latin1, 'depth', $opt{ret_type}, 0, 0);
+            }
 
-    my $else= ( $opt{else} ||= 0 );
-    for my $size ( @size ) {
-        my $optree= $self->$method( %opt, type => $type, max_depth => $size );
-        my $cond= "((e)-(s) > " . ( $size - 1 ).")";
-        $else= __cond_join( $cond, $optree, $else );
+            # If we want the UTF-8 invariants, get those.
+            my $low;
+            if ($opt{type} !~ /non_low|high/
+                && ($low= $self->make_trie( 'low', 1)))
+            {
+                $low= $self->_optree( $low, 'depth', $opt{ret_type}, 0, 0 );
+
+                # Expand out the UTF-8 invariants as a string so that we
+                # can use them as the conditional
+                $low = $self->_cond_as_str( $low, 0, \%opt);
+
+                # If there are Latin1 variants, add a test for them.
+                if ($latin1) {
+                    $else = __cond_join("(! is_utf8 )", $latin1, $else);
+                }
+                elsif ($method eq 'generic_optree') {
+
+                    # Otherwise for 'generic' only we know that what
+                    # follows must be valid for just UTF-8 strings,
+                    $else->{test} = "( is_utf8 && $else->{test} )";
+                }
+
+                # If the invariants match, we are done; otherwise we have
+                # to go to the 'else' clause.
+                $else = __cond_join($low, 1, $else);
+            }
+            elsif ($latin1) {   # Here, didn't want or didn't have invariants,
+                                # but we do have latin variants
+                $else = __cond_join("(! is_utf8)", $latin1, $else);
+            }
+
+            # We need at least one byte available to start off the tests
+            $else = __cond_join("LIKELY((e) > (s))", $else, 0);
+        }
+        else {  # Here, we don't want or there aren't any variants.  A single
+                # byte available is enough.
+            my $cond= "((e) > (s))";
+            my $optree = $self->$method(%opt);
+            $else= __cond_join( $cond, $optree, $else );
+        }
     }
+
     return $else;
 }
 
@@ -629,44 +805,32 @@ sub calculate_mask(@) {
 
     # Consider a set of byte values, A, B, C ....  If we want to determine if
     # <c> is one of them, we can write c==A || c==B || c==C ....  If the
-    # values are consecutive, we can shorten that to A<=c && c<=Z, which uses
-    # far fewer branches.  If only some of them are consecutive we can still
-    # save some branches by creating range tests for just those that are
-    # consecutive. _cond_as_str() does this work for looking for ranges.
+    # values are consecutive, we can shorten that to inRANGE(c, 'A', 'Z'),
+    # which uses far fewer branches.  If only some of them are consecutive we
+    # can still save some branches by creating range tests for just those that
+    # are consecutive. _cond_as_str() does this work for looking for ranges.
     #
     # Another approach is to look at the bit patterns for A, B, C .... and see
     # if they have some commonalities.  That's what this function does.  For
     # example, consider a set consisting of the bytes
-    # 0xF0, 0xF1, 0xF2, and 0xF3.  We could write:
-    #   0xF0 <= c && c <= 0xF4
-    # But the following mask/compare also works, and has just one test:
-    #   (c & 0xFC) == 0xF0
-    # The reason it works is that the set consists of exactly those bytes
-    # whose first 4 bits are 1, and the next two are 0.  (The value of the
-    # other 2 bits is immaterial in determining if a byte is in the set or
-    # not.)  The mask masks out those 2 irrelevant bits, and the comparison
-    # makes sure that the result matches all bytes which match those 6
-    # material bits exactly.  In other words, the set of bytes contains
-    # exactly those whose bottom two bit positions are either 0 or 1.  The
-    # same principle applies to bit positions that are not necessarily
-    # adjacent.  And it can be applied to bytes that differ in 1 through all 8
-    # bit positions.  In order to be a candidate for this optimization, the
-    # number of bytes in the set must be a power of 2.
+    # 0x42, 0x43, 0x62, and 0x63.  We could write:
+    #   inRANGE(c, 0x42, 0x43) || inRANGE(c, 0x62, 0x63)
+    # which through the magic of casting has not 4, but 2 tests.  But the
+    # following mask/compare also works, and has just one test:
+    #   (c & 0xDE) == 0x42
+    # The reason it works is that the set consists of exactly the 4 bit
+    # patterns which have either 0 or 1 in the two bit positions that are 0 in
+    # the mask.  They have the same value in each bit position where the mask
+    # is 1.  The comparison makes sure that the result matches all bytes which
+    # match those six 1 bits exactly.  This can be applied to bytes that
+    # differ in 1 through all 8 bit positions.  In order to be a candidate for
+    # this optimization, the number of bytes in the set must be a power of 2.
     #
-    # Consider a different example, the set 0x53, 0x54, 0x73, and 0x74.  That
-    # requires 4 tests using either ranges or individual values, and even
-    # though the number in the set is a power of 2, it doesn't qualify for the
-    # mask optimization described above because the number of bits that are
-    # different is too large for that.  However, the set can be expressed as
-    # two branches with masks thusly:
-    #   (c & 0xDF) == 0x53 || (c & 0xDF) == 0x54
-    # a branch savings of 50%.  This is done by splitting the set into two
-    # subsets each of which has 2 elements, and within each set the values
-    # differ by 1 byte.
-    #
-    # This function attempts to find some way to save some branches using the
-    # mask technique.  If not, it returns an empty list; if so, it
-    # returns a list consisting of
+    # It may be that the bytes needing to be matched can't be done with a
+    # single mask.  But it may be possible to have two (or more) sets, each
+    # with a separate mask.  This function attempts to find some way to save
+    # some branches using the mask technique.  If not, it returns an empty
+    # list; if so, it returns a list consisting of
     #   [ [compare1, mask1], [compare2, mask2], ...
     #     [compare_n, undef], [compare_m, undef], ...
     #   ]
@@ -754,25 +918,29 @@ sub calculate_mask(@) {
             my @bits_that_differ = pop_count($list[$i] ^ $list[$j]);
             my $differ_count = @bits_that_differ;
             my $key = join ",", @bits_that_differ;
-            push @{$hash{$differ_count}{$key}}, $list[$i] unless grep { $_ == $list[$i] } @{$hash{$differ_count}{$key}};
+            push @{$hash{$differ_count}{$key}}, $list[$i]
+                unless grep { $_ == $list[$i] } @{$hash{$differ_count}{$key}};
             push @{$hash{$differ_count}{$key}}, $list[$j];
         }
     }
 
-    print STDERR __LINE__, ": calculate_mask() called:  List of values grouped by differing bits: ", Dumper \%hash if DEBUG;
+    print STDERR __LINE__, ": calculate_mask() called:  List of values grouped",
+                                " by differing bits: ", Dumper \%hash if DEBUG;
 
     my @final_results;
     foreach my $count (reverse sort { $a <=> $b } keys %hash) {
         my $need = 2 ** $count;     # Need 8 values for 3 differing bits, etc
-        foreach my $bits (sort keys $hash{$count}) {
+        foreach my $bits (sort keys $hash{$count}->%*) {
 
-            print STDERR __LINE__, ": For $count bit(s) difference ($bits), need $need; have ", scalar @{$hash{$count}{$bits}}, "\n" if DEBUG;
+            print STDERR __LINE__, ": For $count bit(s) difference ($bits),",
+            " need $need; have ", scalar @{$hash{$count}{$bits}}, "\n" if DEBUG;
 
             # Look only as long as there are at least as many elements in the
             # subset as are needed
             while ((my $cur_count = @{$hash{$count}{$bits}}) >= $need) {
 
-                print STDERR __LINE__, ": Looking at bit positions ($bits): ", Dumper $hash{$count}{$bits} if DEBUG;
+                print STDERR __LINE__, ": Looking at bit positions ($bits): ",
+                                          Dumper $hash{$count}{$bits} if DEBUG;
 
                 # Start with the first element in it
                 my $try_base = $hash{$count}{$bits}[0];
@@ -796,14 +964,20 @@ sub calculate_mask(@) {
                     my $try_this = $hash{$count}{$bits}[$i];
                     my @positions = pop_count($try_base ^ $try_this);
 
-                    print STDERR __LINE__, ": $try_base vs $try_this: is (", join(',', @positions), ") a subset of ($bits)?" if DEBUG;;
+                    print STDERR __LINE__, ": $try_base vs $try_this: is (",
+                      join(',', @positions), ") a subset of ($bits)?" if DEBUG;
 
                     foreach my $pos (@positions) {
                         unless (grep { $pos == $_ } @bits) {
                             print STDERR "  No\n" if DEBUG;
                             my $remaining = $cur_count - $i - 1;
                             if ($remaining && @subset + $remaining < $need) {
-                                print STDERR __LINE__, ": Can stop trying $try_base, because even if all the remaining $remaining values work, they wouldn't add up to the needed $need when combined with the existing ", scalar @subset, " ones\n" if DEBUG;
+                                print STDERR __LINE__, ": Can stop trying",
+                                    " $try_base, because even if all the",
+                                    " remaining $remaining values work, they",
+                                    " wouldn't add up to the needed $need when",
+                                    " combined with the existing ",
+                                            scalar @subset, " ones\n" if DEBUG;
                                 last TRY;
                             }
                             next TRY;
@@ -818,7 +992,8 @@ sub calculate_mask(@) {
                     $compare &= $try_this;
                 }
 
-                print STDERR __LINE__, ": subset (", join(", ", @subset), ") has ", scalar @subset, " elements; needs $need\n" if DEBUG;
+                print STDERR __LINE__, ": subset (", join(", ", @subset),
+                 ") has ", scalar @subset, " elements; needs $need\n" if DEBUG;
 
                 if (@subset < $need) {
                     shift @{$hash{$count}{$bits}};
@@ -833,14 +1008,17 @@ sub calculate_mask(@) {
                 $mask = ~$mask & 0xFF;
                 push @final_results, [$compare, $mask];
 
-                printf STDERR "%d: Got it: compare=%d=0x%X; mask=%X\n", __LINE__, $compare, $compare, $mask if DEBUG;
+                printf STDERR "%d: Got it: compare=%d=0x%X; mask=%X\n",
+                                __LINE__, $compare, $compare, $mask if DEBUG;
 
                 # These values are now spoken for.  Remove them from future
                 # consideration
                 foreach my $remove_count (sort keys %hash) {
                     foreach my $bits (sort keys %{$hash{$remove_count}}) {
                         foreach my $to_remove (@subset) {
-                            @{$hash{$remove_count}{$bits}} = grep { $_ != $to_remove } @{$hash{$remove_count}{$bits}};
+                            @{$hash{$remove_count}{$bits}}
+                                    = grep { $_ != $to_remove }
+                                                @{$hash{$remove_count}{$bits}};
                         }
                     }
                 }
@@ -852,7 +1030,7 @@ sub calculate_mask(@) {
     # individually.
     my @individuals;
     foreach my $count (reverse sort { $a <=> $b } keys %hash) {
-        foreach my $bits (sort keys $hash{$count}) {
+        foreach my $bits (sort keys $hash{$count}->%*) {
             foreach my $remaining (@{$hash{$count}{$bits}}) {
 
                 # If we already know about this value, just ignore it.
@@ -884,25 +1062,26 @@ sub _cond_as_str {
     my $is_cp_ret = $opts_ref->{ret_type} eq "cp";
     return "( $test )" if !defined $cond;
 
-    # rangify the list.
+    # rangify the list.  As we encounter a new value, it is placed in a new
+    # subarray by itself.  If the next value is adjacent to it, the end point
+    # of the subarray is merely incremented; and so on.  When the next value
+    # that isn't adjacent to the previous one is encountered, Update() is
+    # called to hoist any single-element subarray to be a scalar.
     my @ranges;
     my $Update= sub {
         # We skip this if there are optimizations that
         # we can apply (below) to the individual ranges
         if ( ($is_cp_ret || $combine) && @ranges && ref $ranges[-1]) {
-            if ( $ranges[-1][0] == $ranges[-1][1] ) {
-                $ranges[-1]= $ranges[-1][0];
-            } elsif ( $ranges[-1][0] + 1 == $ranges[-1][1] ) {
-                $ranges[-1]= $ranges[-1][0];
-                push @ranges, $ranges[-1] + 1;
-            }
+            $ranges[-1] = $ranges[-1][0] if $ranges[-1][0] == $ranges[-1][1];
         }
     };
     for my $condition ( @$cond ) {
         if ( !@ranges || $condition != $ranges[-1][1] + 1 ) {
+            # Not adjacent to the existing range.  Remove that from being a
+            # range if only a single value;
             $Update->();
             push @ranges, [ $condition, $condition ];
-        } else {
+        } else {    # Adjacent to the existing range; add to the range
             $ranges[-1][1]++;
         }
     }
@@ -911,32 +1090,18 @@ sub _cond_as_str {
     return $self->_combine( $test, @ranges )
       if $combine;
 
-    if ($is_cp_ret) {
-        @ranges= map {
-            ref $_
-            ? sprintf(
-                "( $self->{val_fmt} <= $test && $test <= $self->{val_fmt} )",
-                @$_ )
-            : sprintf( "$self->{val_fmt} == $test", $_ );
-        } @ranges;
-
-        return "( " . join( " || ", @ranges ) . " )";
-    }
-
     # If the input set has certain characteristics, we can optimize tests
-    # for it.  This doesn't apply if returning the code point, as we want
-    # each element of the set individually.  The code above is for this
-    # simpler case.
+    # for it.
 
     return 1 if @$cond == 256;  # If all bytes match, is trivially true
 
     my @masks;
     if (@ranges > 1) {
 
-        # See if the entire set shares optimizable characterstics, and if so,
-        # return the optimization.  We delay checking for this on sets with
-        # just a single range, as there may be better optimizations available
-        # in that case.
+        # See if the entire set shares optimizable characteristics, and if so,
+        # return the optimization.  There is no need to do this on sets with
+        # just a single range, as that can be expressed with a single
+        # conditional.
         @masks = calculate_mask(@$cond);
 
         # Stringify the output of calculate_mask()
@@ -944,10 +1109,12 @@ sub _cond_as_str {
             my @return;
             foreach my $mask_ref (@masks) {
                 if (defined $mask_ref->[1]) {
-                    push @return, sprintf "( ( $test & $self->{val_fmt} ) == $self->{val_fmt} )", $mask_ref->[1], $mask_ref->[0];
+                    push @return, "( ( $test & "
+                                . $self->val_fmt($mask_ref->[1]) . " ) == "
+                                . $self->val_fmt($mask_ref->[0]) . " )";
                 }
                 else {  # An undefined mask means to use the value as-is
-                    push @return, sprintf "$test == $self->{val_fmt}", $mask_ref->[0];
+                    push @return, "$test == " . $self->val_fmt($mask_ref->[0]);
                 }
             }
 
@@ -968,11 +1135,25 @@ sub _cond_as_str {
     my $range_count_extra = 0;
     for (my $i = 0; $i < @ranges; $i++) {
         if (! ref $ranges[$i]) {    # Trivial case: no range
-            $ranges[$i] = sprintf "$self->{val_fmt} == $test", $ranges[$i];
+            $ranges[$i] = $self->val_fmt($ranges[$i]) . " == $test";
         }
         elsif ($ranges[$i]->[0] == $ranges[$i]->[1]) {
             $ranges[$i] =           # Trivial case: single element range
-                    sprintf "$self->{val_fmt} == $test", $ranges[$i]->[0];
+                    $self->val_fmt($ranges[$i]->[0]) . " == $test";
+        }
+        elsif ($ranges[$i]->[0] == 0) {
+            # If the range matches all 256 possible bytes, it is trivially
+            # true.
+            return 1 if $ranges[0]->[1] == 0xFF;    # @ranges must be 1 in
+                                                    # this case
+            $ranges[$i] = "( $test <= "
+                        . $self->val_fmt($ranges[$i]->[1]) . " )";
+        }
+        elsif ($ranges[$i]->[1] == 255) {
+
+            # Similarly the max possible is 255, so can omit an upper bound
+            # test if the calculated max is the max possible one.
+            $ranges[$i] = "( $test >= " . $self->val_fmt($ranges[0]->[0]) . " )";
         }
         else {
             my $output = "";
@@ -984,72 +1165,26 @@ sub _cond_as_str {
             # bounds.  (No legal UTF-8 character can begin with anything in
             # this range, so we don't have to worry about this being a
             # continuation byte or not.)
-            if (ASCII_PLATFORM
-                && ! $opts_ref->{safe}
+            if ($opts_ref->{charset} =~ /ascii/i
+                && (! $opts_ref->{safe} && ! $opts_ref->{no_length_checks})
                 && $opts_ref->{type} =~ / ^ (?: utf8 | high ) $ /xi)
             {
-                my $lower_limit_is_80 = ($ranges[$i]->[0] == 0x80);
-                my $upper_limit_is_BF = ($ranges[$i]->[1] == 0xBF);
-
                 # If the range is the entire legal range, it matches any legal
                 # byte, so we can omit both tests.  (This should happen only
                 # if the number of ranges is 1.)
-                if ($lower_limit_is_80 && $upper_limit_is_BF) {
+                if ($ranges[$i]->[0] == 0x80 && $ranges[$i]->[1] == 0xBF) {
                     return 1;
                 }
-                elsif ($lower_limit_is_80) { # Just use the upper limit test
-                    $output = sprintf("( $test <= $self->{val_fmt} )",
-                                        $ranges[$i]->[1]);
-                }
-                elsif ($upper_limit_is_BF) { # Just use the lower limit test
-                    $output = sprintf("( $test >= $self->{val_fmt} )",
-                                    $ranges[$i]->[0]);
-                }
             }
 
-            # If we didn't change to omit a test above, see if the number of
-            # elements is a power of 2 (only a single bit in the
-            # representation of its count will be set) and if so, it may be
-            # that a mask/compare optimization is possible.
-            if ($output eq ""
-                && pop_count($ranges[$i]->[1] - $ranges[$i]->[0] + 1) == 1)
-            {
-                my @list;
-                push @list, $_  for ($ranges[$i]->[0] .. $ranges[$i]->[1]);
-                my @this_masks = calculate_mask(@list);
-
-                # Use the mask if there is just one for the whole range.
-                # Otherwise there is no savings over the two branches that can
-                # define the range.
-                if (@this_masks == 1 && defined $this_masks[0][1]) {
-                    $output = sprintf "( $test & $self->{val_fmt} ) == $self->{val_fmt}", $this_masks[0][1], $this_masks[0][0];
-                }
-            }
-
-            if ($output ne "") {  # Prefer any optimization
-                $ranges[$i] = $output;
-            }
-            else {
-                # No optimization happened.  We need a test that the code
-                # point is within both bounds.  But, if the bounds are
-                # adjacent code points, it is cleaner to say
-                # 'first == test || second == test'
-                # than it is to say
-                # 'first <= test && test <= second'
-
-                $range_count_extra++;   # This range requires 2 branches to
-                                        # represent
-                if ($ranges[$i]->[0] + 1 == $ranges[$i]->[1]) {
-                    $ranges[$i] = "( "
-                                .  join( " || ", ( map
-                                    { sprintf "$self->{val_fmt} == $test", $_ }
-                                    @{$ranges[$i]} ) )
-                                . " )";
-                }
-                else {  # Full bounds checking
-                    $ranges[$i] = sprintf("( $self->{val_fmt} <= $test && $test <= $self->{val_fmt} )", $ranges[$i]->[0], $ranges[$i]->[1]);
-                }
-            }
+            # Here, it isn't the full range of legal continuation bytes.  We
+            # could just assume that there's nothing outside of the legal
+            # bounds.  But inRANGE() allows us to have a single conditional,
+            # so the only cost of making sure it's a legal UTF-8 continuation
+            # byte is an extra subtraction instruction, a trivial expense.
+            $ranges[$i] = "inRANGE_helper_(U8, $test, "
+                        . $self->val_fmt($ranges[$i]->[0]) .", "
+                        . $self->val_fmt($ranges[$i]->[1]) . ")";
         }
     }
 
@@ -1072,14 +1207,22 @@ sub _combine {
     return if !@cond;
     my $item= shift @cond;
     my ( $cstr, $gtv );
-    if ( ref $item ) {
-        $cstr=
-          sprintf( "( $self->{val_fmt} <= $test && $test <= $self->{val_fmt} )",
-            @$item );
-        $gtv= sprintf "$self->{val_fmt}", $item->[1];
+    if ( ref $item ) {  # @item should be a 2-element array giving range start
+                        # and end
+        if ($item->[0] == 0) {  # UV's are never negative, so skip "0 <= "
+                                # test which could generate a compiler warning
+                                # that test is always true
+            $cstr= "$test <= " . $self->val_fmt($item->[1]);
+        }
+        else {
+            $cstr = "inRANGE_helper_(UV, $test, "
+                  . $self->val_fmt($item->[0]) . ", "
+                  . $self->val_fmt($item->[1]) . ")";
+        }
+        $gtv= $self->val_fmt($item->[1]);
     } else {
-        $cstr= sprintf( "$self->{val_fmt} == $test", $item );
-        $gtv= sprintf "$self->{val_fmt}", $item;
+        $cstr= $self->val_fmt($item) . " == $test";
+        $gtv= $self->val_fmt($item)
     }
     if ( @cond ) {
         my $combine= $self->_combine( $test, @cond );
@@ -1105,10 +1248,12 @@ sub _render {
     my $cond= $self->_cond_as_str( $op, $combine, $opts_ref );
     #no warnings 'recursion';   # This would allow really really inefficient
                                 # code to be generated.  See pod
-    my $yes= $self->_render( $op->{yes}, $combine, 1, $opts_ref, $def, $submacros );
+    my $yes= $self->_render( $op->{yes}, $combine, 1, $opts_ref, $def,
+                                                                    $submacros);
     return $yes if $cond eq '1';
 
-    my $no= $self->_render( $op->{no},   $combine, 0, $opts_ref, $def, $submacros );
+    my $no= $self->_render( $op->{no},   $combine, 0, $opts_ref, $def,
+                                                                    $submacros);
     return "( $cond )" if $yes eq '1' and $no eq '0';
     my ( $lb, $rb )= $brace ? ( "( ", " )" ) : ( "", "" );
     return "$lb$cond ? $yes : $no$rb"
@@ -1124,9 +1269,12 @@ sub _render {
 
     my $str= "$lb$cond ?$yes$ind: $no$rb";
     if (length $str > 6000) {
-        push @$submacros, sprintf "#define $def\n( %s )", "_part" . (my $yes_idx= 0+@$submacros), $yes;
-        push @$submacros, sprintf "#define $def\n( %s )", "_part" . (my $no_idx= 0+@$submacros), $no;
-        return sprintf "%s%s ? $def : $def%s", $lb, $cond, "_part$yes_idx", "_part$no_idx", $rb;
+        push @$submacros, sprintf "#define $def\n( %s )", "_part"
+                                  . (my $yes_idx= 0+@$submacros) . "_", $yes;
+        push @$submacros, sprintf "#define $def\n( %s )", "_part"
+                                  . (my $no_idx= 0+@$submacros) . "_", $no;
+        return sprintf "%s%s ? $def : $def%s", $lb, $cond,
+                                    "_part${yes_idx}_", "_part${no_idx}_", $rb;
     }
     return $str;
 }
@@ -1140,28 +1288,39 @@ sub _render {
 # Currently only used for type 'cp' macros.
 sub render {
     my ( $self, $op, $combine, $opts_ref, $def_fmt )= @_;
-    
+
     my @submacros;
-    my $macro= sprintf "#define $def_fmt\n( %s )", "", $self->_render( $op, $combine, 0, $opts_ref, $def_fmt, \@submacros );
+    my $macro= sprintf "#define $def_fmt\n( %s )", "",
+                       $self->_render( $op, $combine, 0, $opts_ref, $def_fmt,
+                                                                 \@submacros);
 
-    return join "\n\n", map { "/*** GENERATED CODE ***/\n" . __macro( __clean( $_ ) ) } @submacros, $macro;
+    return join "\n\n",
+            map { "/*** GENERATED CODE ***/\n" . __macro( __clean( $_ ) ) }
+                                                            @submacros, $macro;
 }
 
 # make_macro
 # make a macro of a given type.
 # calls into make_trie and (generic_|length_)optree as needed
 # Opts are:
-# type     : 'cp','cp_high', 'generic','high','low','latin1','utf8','LATIN1','UTF8'
-# ret_type : 'cp' or 'len'
-# safe     : add length guards to macro
+# type             : 'cp','cp_high', 'generic','high','low','latin1','utf8','LATIN1','UTF8'
+# ret_type         : 'cp' or 'len'
+# safe             : don't assume is well-formed UTF-8, so don't skip any range
+#                    checks, and add length guards to macro
+# no_length_checks : like safe, but don't add length guards.
 #
 # type defaults to 'generic', and ret_type to 'len' unless type is 'cp'
 # in which case it defaults to 'cp' as well.
 #
-# it is illegal to do a type 'cp' macro on a pattern with multi-codepoint
+# It is illegal to do a type 'cp' macro on a pattern with multi-codepoint
 # sequences in it, as the generated macro will accept only a single codepoint
 # as an argument.
 #
+# It is also illegal to do a non-safe macro on a pattern with multi-codepoint
+# sequences in it, as even if it is known to be well-formed, we need to not
+# run off the end of the buffer when, say, the buffer ends with the first two
+# characters, but three are looked at by the macro.
+#
 # returns the macro.
 
 
@@ -1169,33 +1328,42 @@ sub make_macro {
     my $self= shift;
     my %opts= @_;
     my $type= $opts{type} || 'generic';
-    die "Can't do a 'cp' on multi-codepoint character class '$self->{op}'"
-      if $type =~ /^cp/
-      and $self->{has_multi};
+    if ($self->{has_multi}) {
+        if ($type =~ /^cp/) {
+            die "Can't do a 'cp' on multi-codepoint character class"
+              . " '$self->{op}'"
+        }
+        elsif (! $opts{safe}) {
+            die "'safe' is required on multi-codepoint character class"
+               ." '$self->{op}'"
+        }
+    }
     my $ret_type= $opts{ret_type} || ( $opts{type} =~ /^cp/ ? 'cp' : 'len' );
     my $method;
     if ( $opts{safe} ) {
         $method= 'length_optree';
-    } elsif ( $type eq 'generic' ) {
+    } elsif ( $type =~ /generic/ ) {
         $method= 'generic_optree';
     } else {
         $method= 'optree';
     }
     my @args= $type =~ /^cp/ ? 'cp' : 's';
     push @args, "e" if $opts{safe};
-    push @args, "is_utf8" if $type eq 'generic';
+    push @args, "is_utf8" if $type =~ /generic/;
     push @args, "len" if $ret_type eq 'both';
-    my $pfx= $ret_type eq 'both'    ? 'what_len_' : 
+    my $pfx= $ret_type eq 'both'    ? 'what_len_' :
              $ret_type eq 'cp'      ? 'what_'     : 'is_';
-    my $ext= $type     eq 'generic' ? ''          : '_' . lc( $type );
+    my $ext= $type     =~ /generic/ ? ''          : '_' . lc( $type );
+    $ext .= '_non_low' if $type eq 'generic_non_low';
     $ext .= "_safe" if $opts{safe};
+    $ext .= "_no_length_checks" if $opts{no_length_checks};
     my $argstr= join ",", @args;
     my $def_fmt="$pfx$self->{op}$ext%s($argstr)";
     my $optree= $self->$method( %opts, type => $type, ret_type => $ret_type );
     return $self->render( $optree, ($type =~ /^cp/) ? 1 : 0, \%opts, $def_fmt );
 }
 
-# if we arent being used as a module (highly likely) then process
+# if we aren't being used as a module (highly likely) then process
 # the __DATA__ below and produce macros in regcharclass.h
 # if an argument is provided to the script then it is assumed to
 # be the path of the file to output to, if the arg is '-' outputs
@@ -1211,29 +1379,38 @@ if ( !caller ) {
     }
     print $out_fh read_only_top( lang => 'C', by => $0,
                                 file => 'regcharclass.h', style => '*',
-                                copyright => [2007, 2011] );
-    print $out_fh "\n#ifndef H_REGCHARCLASS   /* Guard against nested #includes */\n#define H_REGCHARCLASS 1\n\n";
+                                copyright => [2007, 2011],
+                                 final => <<EOF,
+WARNING: These macros are for internal Perl core use only, and may be
+changed or removed without notice.
+EOF
+    );
+    print $out_fh "\n#ifndef PERL_REGCHARCLASS_H_ /* Guard against nested",
+                  " #includes */\n#define PERL_REGCHARCLASS_H_\n";
 
     my ( $op, $title, @txt, @types, %mods );
-    my $doit= sub {
+    my $doit= sub ($) {
         return unless $op;
 
+        my $charset = shift;
+
         # Skip if to compile on a different platform.
-        return if delete $mods{only_ascii_platform} && ! ASCII_PLATFORM;
-        return if delete $mods{only_ebcdic_platform} && ord 'A' != 193;
+        return if delete $mods{only_ascii_platform} && $charset !~ /ascii/i;
+        return if delete $mods{only_ebcdic_platform} && $charset !~ /ebcdic/i;
 
         print $out_fh "/*\n\t$op: $title\n\n";
         print $out_fh join "\n", ( map { "\t$_" } @txt ), "*/", "";
-        my $obj= __PACKAGE__->new( op => $op, title => $title, txt => \@txt );
+        my $obj= __PACKAGE__->new( op => $op, title => $title, txt => \@txt,
+                                                        charset => $charset);
 
         #die Dumper(\@types,\%mods);
 
         my @mods;
         push @mods, 'safe' if delete $mods{safe};
-        unshift @mods, 'fast' if delete $mods{fast} || ! @mods; # Default to 'fast'
-                                                                # do this one
-                                                                # first, as
-                                                                # traditional
+        push @mods, 'no_length_checks' if delete $mods{no_length_checks};
+
+        # Default to 'fast' do this one first, as traditional
+        unshift @mods, 'fast' if delete $mods{fast} || ! @mods;
         if (%mods) {
             die "Unknown modifiers: ", join ", ", map { "'$_'" } sort keys %mods;
         }
@@ -1242,43 +1419,91 @@ if ( !caller ) {
             my ( $type, $ret )= split /-/, $type_spec;
             $ret ||= 'len';
             foreach my $mod ( @mods ) {
-                next if $mod eq 'safe' and $type =~ /^cp/;
+
+                # 'safe' is irrelevant with code point macros, so skip if
+                # there is also a 'fast', but don't skip if this is the only
+                # way a cp macro will get generated.  Below we convert 'safe'
+                # to 'fast' in this instance
+                next if $type =~ /^cp/
+                        && ($mod eq 'safe' || $mod eq 'no_length_checks')
+                        && grep { 'fast' =~ $_ } @mods;
                 delete $mods{$mod};
                 my $macro= $obj->make_macro(
                     type     => $type,
                     ret_type => $ret,
-                    safe     => $mod eq 'safe'
+                    safe     => $mod eq 'safe' && $type !~ /^cp/,
+                    charset  => $charset,
+                    no_length_checks => $mod eq 'no_length_checks'
+                                     && $type !~ /^cp/,
                 );
                 print $out_fh $macro, "\n";
             }
         }
     };
 
-    while ( <DATA> ) {
-        s/^ \s* (?: \# .* ) ? $ //x;    # squeeze out comment and blanks
-        next unless /\S/;
-        chomp;
-        if ( /^[A-Z]/ ) {
-            $doit->();  # This starts a new definition; do the previous one
-            ( $op, $title )= split /\s*:\s*/, $_, 2;
-            @txt= ();
-        } elsif ( s/^=>// ) {
-            my ( $type, $modifier )= split /:/, $_;
-            @types= split ' ', $type;
-            undef %mods;
-            map { $mods{$_} = 1 } split ' ',  $modifier;
-        } else {
-            push @txt, "$_";
+    my @data = <DATA>;
+    foreach my $charset (get_supported_code_pages()) {
+        my $first_time = 1;
+        undef $op;
+        undef $title;
+        undef @txt;
+        undef @types;
+        undef %mods;
+        print $out_fh "\n", get_conditional_compile_line_start($charset);
+        my @data_copy = @data;
+        for (@data_copy) {
+            s/^ \s* (?: \# .* ) ? $ //x;    # squeeze out comment and blanks
+            next unless /\S/;
+            chomp;
+            if ( /^[A-Z]/ ) {
+                $doit->($charset) unless $first_time;  # This starts a new
+                                                       # definition; do the
+                                                       # previous one
+                $first_time = 0;
+                ( $op, $title )= split /\s*:\s*/, $_, 2;
+                @txt= ();
+            } elsif ( s/^=>// ) {
+                my ( $type, $modifier )= split /:/, $_;
+                @types= split ' ', $type;
+                undef %mods;
+                map { $mods{$_} = 1 } split ' ',  $modifier;
+            } else {
+                push @txt, "$_";
+            }
         }
+        $doit->($charset);
+        print $out_fh get_conditional_compile_line_end();
     }
-    $doit->();
 
-    print $out_fh "\n#endif /* H_REGCHARCLASS */\n";
+    print $out_fh "\n#endif /* PERL_REGCHARCLASS_H_ */\n";
 
     if($path eq '-') {
        print $out_fh "/* ex: set ro: */\n";
     } else {
-       read_only_bottom_close_and_rename($out_fh)
+        # Some of the sources for these macros come from Unicode tables
+        my $sources_list = "lib/unicore/mktables.lst";
+        my @sources = ($0, qw(lib/unicore/mktables
+                              lib/Unicode/UCD.pm
+                              regen/regcharclass_multi_char_folds.pl
+                              regen/charset_translations.pl
+                             ));
+        {
+            # Depend on mktables’ own sources.  It’s a shorter list of files than
+            # those that Unicode::UCD uses.
+            if (! open my $mktables_list, '<', $sources_list) {
+
+                # This should force a rebuild once $sources_list exists
+                push @sources, $sources_list;
+            }
+            else {
+                while(<$mktables_list>) {
+                    last if /===/;
+                    chomp;
+                    push @sources, "lib/unicore/$_" if /^[^#]/;
+                }
+            }
+        }
+        read_only_bottom_close_and_rename($out_fh, \@sources)
     }
 }
 
@@ -1340,6 +1565,10 @@ if ( !caller ) {
 #   generic     generate a macro whose name is 'is_BASE".  It has a 2nd,
 #               boolean, parameter which indicates if the first one points to
 #               a UTF-8 string or not.  Thus it works in all circumstances.
+#   generic_non_low generate a macro whose name is 'is_BASE_non_low".  It has
+#               a 2nd, boolean, parameter which indicates if the first one
+#               points to a UTF-8 string or not.  It excludes any ASCII-range
+#               matches, but otherwise it works in all circumstances.
 #   cp          generate a macro whose name is 'is_BASE_cp' and defines a
 #               class that returns true if the UV parameter is a member of the
 #               class; false if not.
@@ -1365,12 +1594,15 @@ if ( !caller ) {
 #               string.  In the case of non-UTF8, it makes sure that the
 #               string has at least one byte in it.  The macro name has
 #               '_safe' appended to it.
+#   no_length_checks  The input string is not necessarily valid UTF-8, but it
+#               is to be assumed that the length has already been checked and
+#               found to be valid
 #   fast        The input string is valid UTF-8.  No bounds checking is done,
 #               and the macro can make assumptions that lead to faster
 #               execution.
-#   only_ascii_platform   Skip this definition if this program is being run on
+#   only_ascii_platform   Skip this definition if the character set is for
 #               a non-ASCII platform.
-#   only_ebcdic_platform  Skip this definition if this program is being run on
+#   only_ebcdic_platform  Skip this definition if the character set is for
 #               a non-EBCDIC platform.
 # No modifier need be specified; fast is assumed for this case.  If both
 # 'fast', and 'safe' are specified, two macros will be created for each
@@ -1396,99 +1628,86 @@ __DATA__
 # 0x1FE3  # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA; maps same as 03B0
 
 LNBREAK: Line Break: \R
-=> generic UTF8 LATIN1 :fast safe
+=> generic UTF8 LATIN1 : safe
 "\x0D\x0A"      # CRLF - Network (Windows) line ending
 \p{VertSpace}
 
 HORIZWS: Horizontal Whitespace: \h \H
-=> generic UTF8 LATIN1 high cp cp_high :fast safe
+=> high cp_high : fast
 \p{HorizSpace}
 
 VERTWS: Vertical Whitespace: \v \V
-=> generic UTF8 high LATIN1 cp cp_high :fast safe
+=> high cp_high : fast
 \p{VertSpace}
 
 XDIGIT: Hexadecimal digits
-=> UTF8 high cp_high :fast
+=> high cp_high : fast
 \p{XDigit}
 
 XPERLSPACE: \p{XPerlSpace}
-=> generic UTF8 high cp_high :fast
+=> high cp_high : fast
 \p{XPerlSpace}
 
-REPLACEMENT: Unicode REPLACEMENT CHARACTER
-=> UTF8 :safe
-0xFFFD
-
 NONCHAR: Non character code points
-=> UTF8 :fast
-\p{Nchar}
-
-SURROGATE: Surrogate characters
-=> UTF8 :fast
-\p{Gc=Cs}
-
-GCB_L: Grapheme_Cluster_Break=L
-=> UTF8 :fast
-\p{_X_GCB_L}
-
-GCB_LV_LVT_V: Grapheme_Cluster_Break=(LV or LVT or V)
-=> UTF8 :fast
-\p{_X_LV_LVT_V}
-
-GCB_Prepend: Grapheme_Cluster_Break=Prepend
-=> UTF8 :fast
-\p{_X_GCB_Prepend}
-
-GCB_RI: Grapheme_Cluster_Break=RI
-=> UTF8 :fast
-\p{_X_RI}
-
-GCB_SPECIAL_BEGIN_START: Grapheme_Cluster_Break=special_begin_starts
-=> UTF8 :fast
-\p{_X_Special_Begin_Start}
-
-GCB_T: Grapheme_Cluster_Break=T
-=> UTF8 :fast
-\p{_X_GCB_T}
+=> UTF8 :safe
+\p{_Perl_Nchar}
 
-GCB_V: Grapheme_Cluster_Break=V
-=> UTF8 :fast
-\p{_X_GCB_V}
-
-# This program was run with this enabled, and the results copied to utf8.h;
-# then this was commented out because it takes so long to figure out these 2
-# million code points.  The results would not change unless utf8.h decides it
-# wants a maximum other than 4 bytes, or this program creates better
-# optimizations
-#UTF8_CHAR: Matches utf8 from 1 to 4 bytes
-#=> UTF8 :safe only_ascii_platform
-#0x0 - 0x1FFFFF
-
-# This hasn't been commented out, because we haven't an EBCDIC platform to run
-# it on, and the 3 types of EBCDIC allegedly supported by Perl would have
-# different results
-UTF8_CHAR: Matches utf8 from 1 to 5 bytes
-=> UTF8 :safe only_ebcdic_platform
-0x0 - 0x3FFFFF:
+SURROGATE: Surrogate code points
+=> UTF8 :safe
+\p{_Perl_Surrogate}
 
 QUOTEMETA: Meta-characters that \Q should quote
 => high :fast
 \p{_Perl_Quotemeta}
 
 MULTI_CHAR_FOLD: multi-char strings that are folded to by a single character
+=> UTF8 UTF8-cp :safe
+%regcharclass_multi_char_folds::multi_char_folds('u', 'a')
+
+MULTI_CHAR_FOLD: multi-char strings that are folded to by a single character
+=> LATIN1 LATIN1-cp : safe
+%regcharclass_multi_char_folds::multi_char_folds('l', 'a')
+
+THREE_CHAR_FOLD: A three-character multi-char fold
 => UTF8 :safe
-do regen/regcharclass_multi_char_folds.pl
+%regcharclass_multi_char_folds::multi_char_folds('u', '3')
 
-# 1 => All folds
-&regcharclass_multi_char_folds::multi_char_folds(1)
+THREE_CHAR_FOLD: A three-character multi-char fold
+=> LATIN1 :safe
+%regcharclass_multi_char_folds::multi_char_folds('l', '3')
 
-MULTI_CHAR_FOLD: multi-char strings that are folded to by a single character
+THREE_CHAR_FOLD_HEAD: The first two of three-character multi-char folds
+=> UTF8 :safe
+%regcharclass_multi_char_folds::multi_char_folds('u', 'h')
+
+THREE_CHAR_FOLD_HEAD: The first two of three-character multi-char folds
 => LATIN1 :safe
+%regcharclass_multi_char_folds::multi_char_folds('l', 'h')
+#
+#THREE_CHAR_FOLD_NON_FINAL: The first or middle character of multi-char folds
+#=> UTF8 :safe
+#%regcharclass_multi_char_folds::multi_char_folds('u', 'fm')
+#
+#THREE_CHAR_FOLD_NON_FINAL: The first or middle character of multi-char folds
+#=> LATIN1 :safe
+#%regcharclass_multi_char_folds::multi_char_folds('l', 'fm')
 
-&regcharclass_multi_char_folds::multi_char_folds(0)
-# 0 => Latin1-only
+FOLDS_TO_MULTI: characters that fold to multi-char strings
+=> UTF8 :fast
+\p{_Perl_Folds_To_Multi_Char}
+
+PROBLEMATIC_LOCALE_FOLD : characters whose fold is problematic under locale
+=> UTF8 cp :fast
+\p{_Perl_Problematic_Locale_Folds}
+
+PROBLEMATIC_LOCALE_FOLDEDS_START : The first folded character of folds which are problematic under locale
+=> UTF8 cp :fast
+\p{_Perl_Problematic_Locale_Foldeds_Start}
 
 PATWS: pattern white space
-=> generic cp : fast safe
-\p{PatWS}
+=> generic : safe
+\p{_Perl_PatWS}
+
+HANGUL_ED: Hangul syllables whose first character is \xED
+=> UTF8 :only_ascii_platform safe
+0xD000 - 0xD7FF