На днях прочитал 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-сущности опознанных символов (да, строка "&#$_;" может вызвать улыбку):
4
2
ў
¢
㉿
€
𤭢Именно такой результат и ожидался: 4 2 ў ¢ ㉿ € 𤭢.
В следующий раз обратим внимание на бинарные операции внутри when.
Поправка (была пропущена "4"):