perl

 view release on metacpan or  search on metacpan

lib/_charnames.pm  view on Meta::CPAN

  if (my @alias = do $file) {
    @alias == 1 && !defined $alias[0] and
      croak "$file cannot be used as alias file for charnames";
    @alias % 2 and
      croak "$file did not return a (valid) list of alias pairs";
    alias (@alias);
    return (1);
  }
  0;
} # alias_file

# For use when don't import anything.  This structure must be kept in
# sync with the one that import() fills up.
my %dummy_H = (
                charnames_stringified_names => "",
                charnames_stringified_ords => "",
                charnames_scripts => "",
                charnames_full => 1,
                charnames_loose => 0,
                charnames_short => 0,
              );


sub lookup_name ($name, $wants_ord, $runtime, $regex_loose //= 0) {

  # Lookup the name or sequence $name in the tables.  If $wants_ord is false,
  # returns the string equivalent of $name; if true, returns the ordinal value
  # instead, but in this case $name must not be a sequence; otherwise undef is
  # returned and a warning raised.  $runtime is 0 if compiletime, otherwise
  # gives the number of stack frames to go back to get the application caller
  # info.
  # If $name is not found, returns undef in runtime with no warning; and in
  # compiletime, the Unicode replacement character, with a warning.

  # It looks first in the aliases, then in the large table of official Unicode
  # names.

  my $result;       # The string result
  my $save_input;

  if ($runtime && ! $regex_loose) {

    my $hints_ref = (caller($runtime))[10];

    # If we didn't import anything (which happens with 'use charnames ()',
    # substitute a dummy structure.
    $hints_ref = \%dummy_H if ! defined $hints_ref
                              || (! defined $hints_ref->{charnames_full}
                                  && ! defined $hints_ref->{charnames_loose});

    # At runtime, but currently not at compile time, %^H gets
    # stringified, so un-stringify back to the original data structures.
    # These get thrown away by perl before the next invocation
    # Also fill in the hash with the non-stringified data.
    # N.B.  New fields must be also added to %dummy_H

    %{$^H{charnames_name_aliases}} = split ',',
                                      $hints_ref->{charnames_stringified_names};
    %{$^H{charnames_ord_aliases}} = split ',',
                                      $hints_ref->{charnames_stringified_ords};
    $^H{charnames_scripts} = $hints_ref->{charnames_scripts};
    $^H{charnames_full} = $hints_ref->{charnames_full};
    $^H{charnames_loose} = $hints_ref->{charnames_loose};
    $^H{charnames_short} = $hints_ref->{charnames_short};
  }

  my $loose = $regex_loose || $^H{charnames_loose};
  my $lookup_name;  # Input name suitably modified for grepping for in the
                    # table

  # User alias should be checked first or else can't override ours, and if we
  # were to add any, could conflict with theirs.
  if (! $regex_loose && exists $^H{charnames_ord_aliases}{$name}) {
    $result = $^H{charnames_ord_aliases}{$name};
  }
  elsif (! $regex_loose && exists $^H{charnames_name_aliases}{$name}) {
    $name = $^H{charnames_name_aliases}{$name};
    $save_input = $lookup_name = $name;  # Cache the result for any error
                                         # message
    # The aliases are documented to not match loosely, so change loose match
    # into full.
    if ($loose) {
      $loose = 0;
      $^H{charnames_full} = 1;
    }
  }
  else {

    # Here, not a user alias.  That means that loose matching may be in
    # effect; will have to modify the input name.
    $lookup_name = $name;
    if ($loose) {
      $lookup_name = uc $lookup_name;

      # Squeeze out all underscores
      $lookup_name =~ s/_//g;

      # Remove all medial hyphens
      $lookup_name =~ s/ (?<= \S  ) - (?= \S  )//gx;

      # Squeeze out all spaces
      $lookup_name =~ s/\s//g;
    }

    # Here, $lookup_name has been modified as necessary for looking in the
    # hashes.  Check the system alias files next.  Most of these aliases are
    # the same for both strict and loose matching.  To save space, the ones
    # which differ are in their own separate hash, which is checked if loose
    # matching is selected and the regular match fails.  To save time, the
    # loose hashes could be expanded to include all aliases, and there would
    # only have to be one check.  But if someone specifies :loose, they are
    # interested in convenience over speed, and the time for this second check
    # is miniscule compared to the rest of the routine.
    if (exists $system_aliases{$lookup_name}) {
      $result = $system_aliases{$lookup_name};
    }
    # There are currently no entries in this hash, so don't waste time looking
    # for them.  But the code is retained for the unlikely possibility that
    # some will be added in the future.
#    elsif ($loose && exists $loose_system_aliases{$lookup_name}) {
#      $result = $loose_system_aliases{$lookup_name};
#    }
#    if (exists $deprecated_aliases{$lookup_name}) {
#      require warnings;

lib/_charnames.pm  view on Meta::CPAN

      my $cache_ref;

      ## Suck in the code/name list as a big string.
      ## Entries look like:
      ##     "00052\nLATIN CAPITAL LETTER R\n\n"
      # or
      #      "0052 0303\nLATIN CAPITAL LETTER R WITH TILDE\n\n"
      populate_txt() unless $txt;

      ## @off will hold the index into the code/name string of the start and
      ## end of the name as we find it.

      ## If :loose, look for a loose match; if :full, look for the name
      ## exactly
      # First, see if the name is one which is algorithmically determinable.
      # The subroutine is included in Name.pl.  The table contained in
      # $txt doesn't contain these.  Experiments show that checking
      # for these before checking for the regular names has no
      # noticeable impact on performance for the regular names, but
      # the other way around slows down finding these immensely.
      # Algorithmically determinables are not placed in the cache because
      # that uses up memory, and finding these again is fast.
      if (   ($loose || $^H{charnames_full})
          && (defined (my $ord = charnames::name_to_code_point_special($lookup_name, $loose))))
      {
        $result = chr $ord;
      }
      else {

        # Not algorithmically determinable; look up in the table.  The name
        # will be turned into a regex, so quote any meta characters.
        $lookup_name = quotemeta $lookup_name;

        if ($loose) {

          # For loose matches, $lookup_name has already squeezed out the
          # non-essential characters.  We have to add in code to make the
          # squeezed version match the non-squeezed equivalent in the table.
          # The only remaining hyphens are ones that start or end a word in
          # the original.  They have been quoted in $lookup_name so they look
          # like "\-".  Change all other characters except the backslash
          # quotes for any metacharacters, and the final character, so that
          # e.g., COLON gets transformed into: /C[- ]?O[- ]?L[- ]?O[- ]?N/
          $lookup_name =~ s/ (?! \\ -)    # Don't do this to the \- sequence
                             ( [^-\\] )   # Nor the "-" within that sequence,
                                          # nor the "\" that quotes metachars,
                                          # but otherwise put the char into $1
                             (?=.)        # And don't do it for the final char
                           /$1\[- \]?/gx; # And add an optional blank or
                                          # '-' after each $1 char

          # Those remaining hyphens were originally at the beginning or end of
          # a word, so they can match either a blank before or after, but not
          # both.  (Keep in mind that they have been quoted, so are a '\-'
          # sequence)
          $lookup_name =~ s/\\ -/(?:- | -)/xg;
        }

        # Do the lookup in the full table if asked for, and if succeeds
        # save the offsets and set where to cache the result.
        if (($loose || $^H{charnames_full}) && $txt =~ /^$lookup_name$/m) {
          @off = ($-[0], $+[0]);
          $cache_ref = ($loose) ? \%loose_names_cache : \%full_names_cache;
        }
        elsif ($regex_loose) {
          # Currently don't allow :short when this is set
          return;
        }
        else {

          # Here, didn't look for, or didn't find the name.
          # If :short is allowed, see if input is like "greek:Sigma".
          # Keep in mind that $lookup_name has had the metas quoted.
          my $scripts_trie = "";
          my $name_has_uppercase;
          my @scripts;
          if (($^H{charnames_short})
              && $lookup_name =~ /^ (?: \\ \s)*   # Quoted space
                                    (.+?)         # $1 = the script
                                    (?: \\ \s)*
                                    \\ :          # Quoted colon
                                    (?: \\ \s)*
                                    (.+?)         # $2 = the name
                                    (?: \\ \s)* $
                                  /xs)
          {
              # Even in non-loose matching, the script traditionally has been
              # case insensitive
              $scripts_trie = "\U$1";
              $lookup_name = $2;

              # Use original name to find its input casing, but ignore the
              # script part of that to make the determination.
              $save_input //= $name;
              $name =~ s/.*?://;
              $name_has_uppercase = $name =~ /[[:upper:]]/;
          }
          else { # Otherwise look in allowed scripts
              # We want to search first by script name then by letter name, so that
              # if the user imported `use charnames qw(arabic hebrew)` and asked for
              # \N{alef} they get ARABIC LETTER ALEF, and if they imported
              # `... (hebrew arabic)` and ask for \N{alef} they get HEBREW LETTER ALEF.
              # We can't rely on the regex engine to preserve ordering like that, so
              # pick the pipe-seperated string apart so we can iterate over it.
              @scripts = split(/\|/, $^H{charnames_scripts});

              # Use original name to find its input casing
              $name_has_uppercase = $name =~ /[[:upper:]]/;
          }
          my $case = $name_has_uppercase ? "CAPITAL" : "SMALL";

          if(@scripts) {
              SCRIPTS: foreach my $script (@scripts) {
                  if($txt =~ /^ (?: $script ) \ (?:$case\ )? LETTER \ \U$lookup_name $/xm) {
                      @off = ($-[0], $+[0]);
                      last SCRIPTS;
                  }
              }
              return unless(@off);
          }
          else {

lib/_charnames.pm  view on Meta::CPAN

  # utf8.  Prefer any official name over the input one for the error message.
  if (@off) {
    $name = substr($txt, $off[0], $off[1] - $off[0]) if @off;
  }
  elsif (defined $save_input) {
    $name = $save_input;
  }

  if ($wants_ord) {
    # Only way to get here in this case is if result too long.  Message
    # assumes that our only caller that requires single char result is
    # vianame.
    carp "charnames::vianame() doesn't handle named sequences ($name).  Use charnames::string_vianame() instead";
    return;
  }

  # Only other possible failure here is from use bytes.
  if ($runtime) {
    carp not_legal_use_bytes_msg($name, $result);
    return;
  } else {
    croak not_legal_use_bytes_msg($name, $result);
  }

} # lookup_name

sub charnames ($arg) {

  # For \N{...}.  Looks up the character name and returns the string
  # representation of it.

  # The first 0 arg means wants a string returned; the second that we are in
  # compile time
  return lookup_name($arg, 0, 0);
}

sub _loose_regcomp_lookup ($arg) {
  # For use only by regcomp.c to compile \p{name=...}
  # khw thinks it best to not do :short matching, and only official names.
  # But that is only a guess, and if demand warrants, could be changed
  return lookup_name($arg, 0, 1,
                     1  # Always use :loose matching
                    );
}

sub _get_names_info {
  # For use only by regcomp.c to compile \p{name=/.../}
  populate_txt() unless $txt;


  return ( \$txt, \@charnames::code_points_ending_in_code_point );
}

sub import ($, @import) {
  populate_txt() unless $txt;

  if (not @import) {
    carp("'use charnames' needs explicit imports list");
  }
  $^H{charnames} = \&charnames ;
  $^H{charnames_ord_aliases} = {};
  $^H{charnames_name_aliases} = {};
  $^H{charnames_inverse_ords} = {};
  # New fields must be added to %dummy_H, and the code in lookup_name()
  # that copies fields from the runtime structure

  ##
  ## fill %h keys with our @import args.
  ##
  my ($promote, %h, @args) = (0);
  while (my $arg = shift @import) {
    if ($arg eq ":alias") {
      @import or
        croak ":alias needs an argument in charnames";
      my $alias = shift @import;
      if (ref $alias) {
        ref $alias eq "HASH" or
          croak "Only HASH reference supported as argument to :alias";
        alias (%$alias);
        $promote = 1;
        next;
      }
      if ($alias =~ m{:(\w+)$}) {
        $1 eq "full" || $1 eq "loose" || $1 eq "short" and
          croak ":alias cannot use existing pragma :$1 (reversed order?)";
        alias_file ($1) and $promote = 1;
        next;
      }
      alias_file ($alias) and $promote = 1;
      next;
    }
    if (substr($arg, 0, 1) eq ':'
      and ! ($arg eq ":full" || $arg eq ":short" || $arg eq ":loose"))
    {
      warn "unsupported special '$arg' in charnames";
      next;
    }
    push @args, $arg;
  }

  @args == 0 && $promote and @args = (":full");
  @h{@args} = (1) x @args;

  # Don't leave these undefined as are tested for in lookup_names
  $^H{charnames_full} = delete $h{':full'} || 0;
  $^H{charnames_loose} = delete $h{':loose'} || 0;
  $^H{charnames_short} = delete $h{':short'} || 0;
  my @scripts = map { uc quotemeta } grep { /^[^:]/ } @args;

  ##
  ## If utf8? warnings are enabled, and some scripts were given,
  ## see if at least we can find one letter from each script.
  ##
  if (warnings::enabled('utf8') && @scripts) {
    for my $script (@scripts) {
      if (not $txt =~ m/^$script (?:CAPITAL |SMALL )?LETTER /m) {
        warnings::warn('utf8',  "No such script: '$script'");
        $script = quotemeta $script;  # Escape it, for use in the re.
      }
    }
  }

  # %^H gets stringified, so serialize it ourselves so can extract the
  # real data back later.
  $^H{charnames_stringified_ords} = join ",", %{$^H{charnames_ord_aliases}};
  $^H{charnames_stringified_names} = join ",", %{$^H{charnames_name_aliases}};
  $^H{charnames_stringified_inverse_ords} = join ",", %{$^H{charnames_inverse_ords}};

  # Modify the input script names for loose name matching if that is also
  # specified, similar to the way the base character name is prepared.  They
  # don't (currently, and hopefully never will) have dashes.  These go into a
  # regex, and have already been uppercased and quotemeta'd.  Squeeze out all
  # input underscores, blanks, and dashes.  Then convert so will match a blank
  # between any characters.
  if ($^H{charnames_loose}) {
    for (@scripts) {
      s/[_ -]//g;
      s/ ( [^\\] ) (?= . ) /$1\\ ?/gx;
    }
  }

  my %letters_by_script = map {
      $_ => [
          ($txt =~ m/$_(?: (?:small|capital))? letter (.*)/ig)
      ]
  } @scripts;
  SCRIPTS: foreach my $this_script (@scripts) {
    my @other_scripts = grep { $_ ne $this_script } @scripts;
    my @this_script_letters  = @{$letters_by_script{$this_script}};
    my @other_script_letters = map { @{$letters_by_script{$_}} } @other_scripts;
    foreach my $this_letter (@this_script_letters) {
      if(grep { $_ eq $this_letter } @other_script_letters) {
        warn "charnames: some short character names may clash in [".join(', ', sort @scripts)."], for example $this_letter\n";
        last SCRIPTS;
      }
    }
  }

  $^H{charnames_scripts} = join "|", @scripts;  # Stringifiy them as a trie
} # import

# Cache of already looked-up values.  This is set to only contain
# official values, and user aliases can't override them, so scoping is
# not an issue.
my %viacode;

my $no_name_code_points_re = join "|", map { sprintf("%05X",
                                             utf8::unicode_to_native($_)) }
                                            0x80, 0x81, 0x84, 0x99;
$no_name_code_points_re = qr/$no_name_code_points_re/;

sub viacode ($arg) {

  # Returns the name of the code point argument

  # This is derived from Unicode::UCD, where it is nearly the same as the
  # function _getcode(), but here it makes sure that even a hex argument
  # has the proper number of leading zeros, which is critical in
  # matching against $txt below
  # Must check if decimal first; see comments at that definition
  my $hex;
  if ($arg =~ $decimal_qr) {
    $hex = sprintf "%05X", $arg;
  } elsif ($arg =~ $hex_qr) {
    $hex = CORE::hex $1;
    $hex = utf8::unicode_to_native($hex) if $arg =~ /^[Uu]\+/;
    # Below is the line that differs from the _getcode() source



( run in 0.808 second using v1.01-cache-2.11-cpan-7f9471e7e0a )