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 )