BATsh

 view release on metacpan or  search on metacpan

lib/BATsh/MB.pm  view on Meta::CPAN

# Character-oriented helpers, operating on GUARDED strings.
# While the guard is inactive they fall back to byte semantics, so
# existing ASCII behaviour is preserved bit-for-bit.
#
#   mb_length($guarded)             -> number of characters
#   mb_substr($guarded, $off, $len) -> guarded substring by characters
#       $off may be negative (counts back from the end, like substr);
#       $len may be omitted (rest of string).
# ----------------------------------------------------------------
sub mb_length {
    my ($s) = @_;
    return 0 unless defined $s;
    return length($s) unless $ACTIVE;
    my $chars = _mb_chars(dec($s));
    return scalar @{$chars};
}

sub mb_substr {
    my ($s, $off, $len) = @_;
    return '' unless defined $s;
    unless ($ACTIVE) {
        return defined $len ? substr($s, $off, $len) : substr($s, $off);
    }
    my $chars = _mb_chars(dec($s));
    my $n = scalar @{$chars};
    $off = $n + $off if $off < 0;
    $off = 0 if $off < 0;
    return '' if $off >= $n;
    my $end = defined $len ? $off + $len : $n;
    $end = $n if $end > $n;
    return '' if $end <= $off;
    return enc(join('', @{$chars}[$off .. $end - 1]));
}

# Split RAW (decoded) DBCS bytes into characters, forward-scanning:
# a lead byte followed by a valid trail byte forms one two-byte
# character; anything else is a single byte.
sub _mb_chars {
    my ($s) = @_;
    my @c = ();
    my $i = 0;
    my $n = length($s);
    while ($i < $n) {
        my $b = substr($s, $i, 1);
        if ($b =~ /[$LEAD]/ && $i + 1 < $n
            && substr($s, $i + 1, 1) =~ /[$TRAIL]/) {
            push @c, substr($s, $i, 2);
            $i += 2;
        }
        else {
            push @c, $b;
            $i++;
        }
    }
    return [ @c ];
}

# ----------------------------------------------------------------
# strip_bom: remove a UTF-8 byte-order mark from the head of the
# first script line (harmless everywhere, required for editors that
# save UTF-8-with-BOM).
# ----------------------------------------------------------------
sub strip_bom {
    my $s = defined $_[0] && $_[0] !~ /\ABATsh::MB\z/ ? $_[0] : $_[1];
    return $s unless defined $s;
    $s =~ s/\A\xef\xbb\xbf//;
    return $s;
}

1;

__END__

=head1 NAME

BATsh::MB - Multibyte (CP932/DBCS) script guard for BATsh

=head1 VERSION

Version 0.11

=head1 SYNOPSIS

  use BATsh;

  # Explicit encoding
  BATsh->run('nihongo.batsh', encoding => 'cp932');

  # Or rely on auto-detection (the default):
  # a non-UTF-8 script containing bytes >= 0x80 is treated as CP932
  BATsh->run('nihongo.batsh');

  # Environment-variable override
  #   set BATSH_ENCODING=cp932

=head1 DESCRIPTION

BATsh::MB makes BATsh safe for scripts written in CP932 (Shift_JIS as
used on Japanese Windows) and other double-byte character sets whose
trail bytes overlap the ASCII range 0x40-0x7E.

Without protection, byte-oriented shell parsing tears such characters
apart: the trail byte 0x5C is mistaken for a backslash escape or path
separator, 0x7C for a pipeline separator, 0x60 for a backtick, 0x5E
for the cmd.exe caret escape, and uc()/lc() corrupt trail bytes in the
a-z range.  This is the well-known "dame-moji" problem affecting very
common characters such as SO (0x835C), HYOU (0x955C), NOH (0x945C),
PO (0x837C), CHI (0x8360), and DA (0x835E).

Rather than teaching every scanner about lead/trail bytes, BATsh::MB
applies a reversible guard transform on input: each two-byte character
whose trail byte falls in the dangerous ASCII range is rewritten to a
three-byte form C<\x01 LEAD (TRAIL+0x80)> containing no ASCII bytes.
The inverse transform is applied at output boundaries (print, external
commands, filesystem calls, %ENV export).  Between the two, all of
BATsh's byte-oriented parsing is automatically DBCS-safe.

=head1 FUNCTIONS

=over 4



( run in 3.130 seconds using v1.01-cache-2.11-cpan-364913b4093 )