Retract #14327 for now, going to the limit seems
[p5sagit/p5-mst-13.2.git] / lib / charnames.pm
index 934fafd..1297a76 100644 (file)
@@ -1,23 +1,37 @@
 package charnames;
-
-our $VERSION = '1.00';
+use strict;
+use warnings;
+use Carp;
+our $VERSION = '1.01';
 
 use bytes ();          # for $bytes::hint_bits
-use warnings();
 $charnames::hint_bits = 0x20000;
 
 my $txt;
 
 # This is not optimized in any way yet
-sub charnames {
-  $name = shift;
-  $txt = do "unicode/Name.pl" unless $txt;
+sub charnames
+{
+  my $name = shift;
+
+  ## Suck in the code/name list as a big string.
+  ## Lines look like:
+  ##     "0052\t\tLATIN CAPITAL LETTER R\n"
+  $txt = do "unicore/Name.pl" unless $txt;
+
+  ## @off will hold the index into the code/name string of the start and
+  ## end of the name as we find it.
   my @off;
+
+  ## If :full, look for the the name exactly
   if ($^H{charnames_full} and $txt =~ /\t\t$name$/m) {
     @off = ($-[0], $+[0]);
   }
+
+  ## If we didn't get above, and :short allowed, look for the short name.
+  ## The short name is like "greek:Sigma"
   unless (@off) {
-    if ($^H{charnames_short} and $name =~ /^(.*?):(.*)/s) {
+    if ($^H{charnames_short} and $name =~ /^(.+?):(.+)/s) {
       my ($script, $cname) = ($1,$2);
       my $case = ( $cname =~ /[[:upper:]]/ ? "CAPITAL" : "SMALL");
       if ($txt =~ m/\t\t\U$script\E (?:$case )?LETTER \U$cname$/m) {
@@ -25,52 +39,162 @@ sub charnames {
       }
     }
   }
+
+  ## If we still don't have it, check for the name among the loaded
+  ## scripts.
+  if (not @off)
+  {
+      my $case = ( $name =~ /[[:upper:]]/ ? "CAPITAL" : "SMALL");
+      for my $script ( @{$^H{charnames_scripts}} )
+      {
+          if ($txt =~ m/\t\t$script (?:$case )?LETTER \U$name$/m) {
+              @off = ($-[0], $+[0]);
+              last;
+          }
+      }
+  }
+
+  ## If we don't have it by now, give up.
   unless (@off) {
-    my $case = ( $name =~ /[[:upper:]]/ ? "CAPITAL" : "SMALL");
-    for ( @{$^H{charnames_scripts}} ) {
-      (@off = ($-[0], $+[0])), last 
-       if $txt =~ m/\t\t$_ (?:$case )?LETTER \U$name$/m;
-    }
+      carp "Unknown charname '$name'";
+      return "\x{FFFD}";
   }
-  die "Unknown charname '$name'" unless @off;
-  
-  my $ord = hex substr $txt, $off[0] - 4, 4;
+
+  ##
+  ## Now know where in the string the name starts.
+  ## The code, in hex, is befor that.
+  ##
+  ## The code can be 4-6 characters long, so we've got to sort of
+  ## go look for it, just after the newline that comes before $off[0].
+  ##
+  ## This would be much easier if unicore/Name.pl had info in
+  ## a name/code order, instead of code/name order.
+  ##
+  ## The +1 after the rindex() is to skip past the newline we're finding,
+  ## or, if the rindex() fails, to put us to an offset of zero.
+  ##
+  my $hexstart = rindex($txt, "\n", $off[0]) + 1;
+
+  ## we know where it starts, so turn into number - the ordinal for the char.
+  my $ord = hex substr($txt, $hexstart, $off[0] - $hexstart);
+
   if ($^H & $bytes::hint_bits) {       # "use bytes" in effect?
     use bytes;
     return chr $ord if $ord <= 255;
-    my $hex = sprintf '%X=0%o', $ord, $ord;
+    my $hex = sprintf "%04x", $ord;
     my $fname = substr $txt, $off[0] + 2, $off[1] - $off[0] - 2;
-    die "Character 0x$hex with name '$fname' is above 0xFF";
+    croak "Character 0x$hex with name '$fname' is above 0xFF";
   }
-  return chr $ord;
+
+  return pack "U", $ord;
 }
 
-sub import {
-  shift;
-  die "`use charnames' needs explicit imports list" unless @_;
+sub import
+{
+  shift; ## ignore class name
+
+  if (not @_)
+  {
+      carp("`use charnames' needs explicit imports list");
+  }
   $^H |= $charnames::hint_bits;
   $^H{charnames} = \&charnames ;
+
+  ##
+  ## fill %h keys with our @_ args.
+  ##
   my %h;
   @h{@_} = (1) x @_;
+
   $^H{charnames_full} = delete $h{':full'};
   $^H{charnames_short} = delete $h{':short'};
   $^H{charnames_scripts} = [map uc, keys %h];
-  if (warnings::enabled('utf8') && @{$^H{charnames_scripts}}) {
-       $txt = do "unicode/Name.pl" unless $txt;
-    for (@{$^H{charnames_scripts}}) {
-        warnings::warn('utf8',  "No such script: '$_'") unless
-           $txt =~ m/\t\t$_ (?:CAPITAL |SMALL )?LETTER /;
-       }
+
+  ##
+  ## If utf8? warnings are enabled, and some scripts were given,
+  ## see if at least we can find one letter of each script.
+  ##
+  if (warnings::enabled('utf8') && @{$^H{charnames_scripts}})
+  {
+      $txt = do "unicore/Name.pl" unless $txt;
+
+      for my $script (@{$^H{charnames_scripts}})
+      {
+          if (not $txt =~ m/\t\t$script (?:CAPITAL |SMALL )?LETTER /) {
+              warnings::warn('utf8',  "No such script: '$script'");
+          }
+      }
   }
 }
 
+require Unicode::UCD; # for Unicode::UCD::_getcode()
+
+my %viacode;
+
+sub viacode
+{
+    if (@_ != 1) {
+        carp "charnames::viacode() expects one numeric argument";
+        return ()
+    }
+
+    my $arg = shift;
+    my $code = Unicode::UCD::_getcode($arg);
+
+    my $hex;
+
+    if (defined $code) {
+        $hex = sprintf "%04X", $arg;
+    } else {
+        carp("unexpected arg \"$arg\" to charnames::viacode()");
+        return;
+    }
+
+    if ($code > 0x10FFFF) {
+       carp "Unicode characters only allocated up to 0x10FFFF (you asked for $hex)";
+       return "\x{FFFD}";
+    }
+
+    return $viacode{$hex} if exists $viacode{$hex};
+
+    $txt = do "unicore/Name.pl" unless $txt;
+
+    if ($txt =~ m/^$hex\t\t(.+)/m) {
+        return $viacode{$hex} = $1;
+    } else {
+        return;
+    }
+}
+
+my %vianame;
+
+sub vianame
+{
+    if (@_ != 1) {
+        carp "charnames::vianame() expects one name argument";
+        return ()
+    }
+
+    my $arg = shift;
+
+    return $vianame{$arg} if exists $vianame{$arg};
+
+    $txt = do "unicore/Name.pl" unless $txt;
+
+    if ($txt =~ m/^([0-9A-F]+)\t\t($arg)/m) {
+        return $vianame{$arg} = hex $1;
+    } else {
+        return;
+    }
+}
+
 
 1;
 __END__
 
 =head1 NAME
 
-charnames - define character names for C<\N{named}> string literal escape.
+charnames - define character names for C<\N{named}> string literal escapes.
 
 =head1 SYNOPSIS
 
@@ -83,6 +207,9 @@ charnames - define character names for C<\N{named}> string literal escape.
   use charnames qw(cyrillic greek);
   print "\N{sigma} is Greek sigma, and \N{be} is Cyrillic b.\n";
 
+  print charname::viacode(0x1234); # prints "ETHIOPIC SYLLABLE SEE"
+  printf "%04X", charname::vianame("GOTHIC LETTER AHSA"); # prints "10330"
+
 =head1 DESCRIPTION
 
 Pragma C<use charnames> supports arguments C<:full>, C<:short> and
@@ -103,8 +230,13 @@ this pragma looks for the names
   SCRIPTNAME LETTER CHARNAME
 
 in the table of standard Unicode names.  If C<CHARNAME> is lowercase,
-then the C<CAPITAL> variant is ignored, otherwise the C<SMALL> variant is
-ignored.
+then the C<CAPITAL> variant is ignored, otherwise the C<SMALL> variant
+is ignored.
+
+Note that C<\N{...}> is compile-time, it's a special form of string
+constant used inside double-quoted strings: in other words, you cannot
+use variables inside the C<\N{...}>.  If you want similar run-time
+functionality, use charnames::vianame().
 
 =head1 CUSTOM TRANSLATORS
 
@@ -136,6 +268,39 @@ state of C<bytes>-flag as in:
        }
     }
 
+=head1 charnames::viacode(code)
+
+Returns the full name of the character indicated by the numeric code.
+The example
+
+    print charnames::viacode(0x2722);
+
+prints "FOUR TEARDROP-SPOKED ASTERISK".
+
+Returns undef if no name is known for the code.
+
+This works only for the standard names, and does not yet aply 
+to custom translators.
+
+=head1 charnames::vianame(code)
+
+Returns the code point indicated by the name.
+The example
+
+    printf "%04X", charnames::vianame("FOUR TEARDROP-SPOKED ASTERISK");
+
+prints "2722".
+
+Returns undef if no name is known for the name.
+
+This works only for the standard names, and does not yet aply 
+to custom translators.
+
+=head1 ILLEGAL CHARACTERS
+
+If you ask for a character that does not exist, a warning is given
+and the special Unicode I<replacement character> "\x{FFFD}" is returned.
+
 =head1 BUGS
 
 Since evaluation of the translation function happens in a middle of