Еще один пример использования state

| 1 комментарий

На днях прочитал 42 статьи о том, как пользоваться юникодом в современном C++ и заодно придумал еще один практический пример, где может быть полезна state-переменная, появившаяся в Perl 5.10, — при разборе последовательности байтов UTF-8.

Вот простая функция parse_utf8, которая принимает очередной байт и формирует в переданном по ссылке массиве @$buf последовательность кодов символов. Важно, что функция при каждом вызове принимает байт, но выходной буфер изменяется только после того, как принята вся последовательность, соответствующая юникодному символу, то есть на каждый второй, третий или четвертый раз, если очередной символ требует для записи в UTF-8 несколько байтов.

В этом примере суть довольно точно описывается словом state: переменные хранят состояние разбора между вызовами функции.

use v5.12;

(5.12 удобно использовать потому, что инструкция use v5.12 автоматически подключает и use strict. Но все должно работать и с 5.10.)

sub parse_utf8 {
    my ($byte, $buf) = @_;
   
    state $bytes = 0;
    state $value = 0;
    my $mask = 0;
   
    given($byte) {
        when(!($_ & 0x80)) {
            ($bytes, $value, $mask) = (0, $byte, 0);
        }
        when(0b1111_0000 == ($_ & 0b1111_1000)) {
            ($bytes, $value, $mask) = (3, 0, 0b0000_0111);
        }
        when(0b1110_0000 == ($_ & 0b1111_0000)) {
            ($bytes, $value, $mask) = (2, 0, 0b0000_1111);
        }
        when(0b1100_0000 == ($_ & 0b1110_0000)) {
            ($bytes, $value, $mask) = (1, 0, 0b0001_1111);
        }
        when(0b1000_0000 == ($_ & 0b1100_0000)) {            
            $bytes--;
            $mask = 0b0011_1111;           
        }
        default {
            $mask = 0;
            $value = ord('?');         
        }
    }
   
    $value += ($byte & $mask) << ($bytes * 6) if $mask;
   
    push @$buf, $value unless $bytes;
}

Когда обнаруживается начало многобайтовой последовательности, в state-переменной сохраняется число оставшихся байтов, а в $value начинает накапливаться результат. Каждый последующий байт (старшие биты которого — единица и нуль) на единицу уменьшает значение $bytes.

Обновление буфера происходит в последней строке только в том случае, если прочитана вся последовательность байтов очередного символа.

Здесь есть даже примитивная обработка ошибок (хотя она находит ошибку лишь в первом байте многобайтовой последовательности).

Теперь проверяем:

my @buf;
parse_utf8($_, \@buf) for (
    0x34, # 4
    0x32, # 2
    0xd1, 0x9e, # ў
    0xc2, 0xa2, # ¢
    0xe3, 0x89, 0xbf, # ㉿
    0xe2, 0x82, 0xac, # €
    0xf0, 0xa4, 0xad, 0xa2, # 𤭢
);
say "&#$_;" for @buf;

Вызов say печатает HTML-сущности опознанных символов (да, строка "&#$_;" может вызвать улыбку):

&#52;
&#50;
&#1118;
&#162;
&#12927;
&#8364;
&#150370;
Именно такой результат и ожидался: 4 2 ў ¢ ㉿ € 𤭢.

В следующий раз обратим внимание на бинарные операции внутри when.

1 комментарий

Поправка (была пропущена "4"):

Именно такой результат и ожидался: 4 2 ў ¢ ㉿ € 𤭢.

Комментировать

Страницы

  • img

Об этой записи

Сообщение опубликовано 24.05.2010 00:07. Автор — ash.

Предыдущая запись — Lingua::Identify

Следующая запись — Об одной особенности работы when

Смотрите новые записи на главной странице или загляните в архив, где есть ссылки на все сообщения.