В UTF-8 функцией считывания символов


Вы можете увидеть полный код здесь (обратите внимание, что ссылка указывает на конкретный коммит).

Язык-это "чистый С" (то есть, подмножество С89, С99 и C++98 — предназначен для компиляции под все эти стандарты). Код должен быть переносим между x86 и x86_64.

UTF-8 для обработки, реализованных на основе информации, вот и протестирован с данные в этот файл.

В первую очередь я заинтересован в правильности. Но я немного беспокоюсь о длине и читаемости кода и его скорость (я не профиль этой вещи, так что особенно беспокоиться не мотивирован). Я приму любые комментарии, в том числе и конструктивные, что доставит некоторое неудобство.

Сама функция:

/*
* *Increments* len_bytes by the number of bytes read.
* Fails on invalid UTF-8 characters.
*/
static int ltsLS_eatutf8char(lts_LoadState * ls, size_t * len_bytes)
{
  unsigned char b = 0;
  signed char expected_length = 0;
  int i = 0;
  const unsigned char * origin = ls->pos;

  /* Check if we have any data in the buffer */
  if (!ltsLS_good(ls) || ltsLS_unread(ls) < 1)
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_ECLIPPED;
  }

  /* We have at least one byte in the buffer, let's check it out. */
  b = *ls->pos;

  /* We did just eat a byte, no matter what happens next. */
  ++ls->pos;
  --ls->unread;

  /* Get an expected length of a character. */
  expected_length = utf8_char_len[b];

  /* Check if it was a valid first byte. */
  if (expected_length < 1)
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_EBADUTF8;
  }

  /* If it was a single-byte ASCII character, return right away. */
  if (expected_length == 1)
  {
    *len_bytes += expected_length;

    return LUATEXTS_ESUCCESS;
  }

  /*
  * It was a multi-byte character. Check if we have enough bytes unread.
  * Note that we've eaten one byte already.
  */
  if (ltsLS_unread(ls) + 1 < expected_length)
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_ECLIPPED; /* Assuming it is buffer's fault. */
  }

  /* Let's eat the rest of characters */
  for (i = 1; i < expected_length; ++i)
  {
    b = *ls->pos;

    /* We did just eat a byte, no matter what happens next. */
    ++ls->pos;
    --ls->unread;

    /* Check if it is a continuation byte */
    if (utf8_char_len[b] != -1)
    {
      ls->unread = 0;
      ls->pos = NULL;

      return LUATEXTS_EBADUTF8;
    }
  }

  /* All bytes are correct, let's check out for overlong forms */
  if (
      expected_length == 2 && (
          (origin[0] & 0xFE) == 0xC0
        )
    )
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_EBADUTF8;
  }
  else if (
      expected_length == 3 && (
          origin[0] == 0xE0
            && (origin[1] & 0xE0) == 0x80
        )
    )
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_EBADUTF8;
  }
  else if (
      expected_length == 4 && (
          origin[0] == 0xF0
            && (origin[1] & 0xF0) == 0x80
        )
    )
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_EBADUTF8;
  }
  else if (
      expected_length == 5 && (
          origin[0] == 0xF8
            && (origin[1] & 0xF8) == 0x80
        )
    )
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_EBADUTF8;
  }
  else if (
      expected_length == 6 && (
          origin[0] == 0xFC
            && (origin[1] & 0xFC) == 0x80
        )
    )
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_EBADUTF8;
  }

  /* No overlongs, check for surrogates. */

  if (
      expected_length == 3 && (
          origin[0] == 0xED
            && (origin[1] & 0xE0) == 0xA0
        )
    )
  {
    ls->unread = 0;
    ls->pos = NULL;

    return LUATEXTS_EBADUTF8;
  }

  /*
  * Note: Not checking for U+FFFE or U+FFFF.
  *
  * Chapter 3 of version 3.2 of the Unicode standard, Paragraph C5 says
  * "A process shall not interpret either U+FFFE or U+FFFF as an abstract
  * character", but table 3.1B includes them among
  * the "Legal UTF-8 Byte Sequences".
  *
  * We opt to pass them through.
  */

  /* Phew. All done, the UTF-8 character is valid. */

  *len_bytes += expected_length;

  return LUATEXTS_ESUCCESS;
}

Функция опирается на эту таблицу подстановок:

static const signed char utf8_char_len[256] =
{
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
   3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
   4,  4,  4,  4,  4,  4,  4,  4,  5,  5,  5,  5,  6,  6,  0,  0
};

Функция работает на lts_LoadState буфера "потоковый итератор", который должен быть подготовлен абонента. Соответствующие куски кода:

typedef struct lts_LoadState
{
  const unsigned char * pos;
  size_t unread;
} lts_LoadState;

static void ltsLS_init(
    lts_LoadState * ls,
    const unsigned char * data,
    size_t len
  )
{
  ls->pos = (len > 0) ? data : NULL;
  ls->unread = len;
}

#define ltsLS_good(ls) \
  ((ls)->pos != NULL)

#define ltsLS_unread(ls) \
  ((ls)->unread)


664
6
задан 2 апреля 2011 в 02:04 Источник Поделиться
Комментарии
1 ответ

Одна очевидная проблема - самая длинная допустимый символ Unicode представлен 4 байта в UTF-8. Хотя можно расширить логику до 5 или 6 байт, нет необходимости, так как Unicode-это 21-битной кодировки.

Будьте осторожны при использовании версии 3.2 стандарта Unicode; текущая версия 6.0. Согласен, они держат их как обратная совместимость, как это возможно, но так же использовать последнюю версию. Пункт С5 в 6.0.0 совсем не похож на пункте вы цитируете. Байты от 0xC0, 0xC1, и 0xF5..как 0xFF не может появиться в допустимом формате UTF-8.

Учитывая это, вы можете изменить таблицу подстановки:

static const signed char utf8_char_len[256] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

Нули на запретный байт вызовет преждевременный выход.

Структура программы loadstate странно; вы должны иметь что-то еще, что ведет запись, где начало буфера. Однако, поскольку код пронизывает loadstate на обнаружении ошибки, там должно быть другое место, где хранится информация, так что, возможно, это не слишком критично.

Хороший компилятор может оптимизировать:

  if (expected_length == 1)
{
*len_bytes += expected_length;
return LUATEXTS_ESUCCESS;
}

так что он такой же как:

  if (expected_length == 1)
{
*len_bytes++;
return LUATEXTS_ESUCCESS;
}

Эти длинные и суррогатное тестирование-это интересно. Таблица 3.7 Главы 3 из 6.0.0 стандартные списки Юникод:

Table 3-7. Well-Formed UTF-8 Byte Sequences
Code Points First Byte Second Byte Third Byte Fourth Byte
U+0000..U+007F 00..7F
U+0080..U+07FF C2..DF 80..BF
U+0800..U+0FFF E0 A0..BF 80..BF
U+1000..U+CFFF E1..EC 80..BF 80..BF
U+D000..U+D7FF ED 80..9F 80..BF
U+E000..U+FFFF EE..EF 80..BF 80..BF
U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
U+100000..U+10FFFF F4 80..8F 80..BF 80..BF

Вам нужно сделать некоторые дополнительные проверки для 0xF4 в качестве первого байта; код позволяет через довольно большое недопустимые символы. В противном случае, анализы у вас действительно работают, позволяя через суррогатов, которыми вы проверить отдельно.

Тест на суррогаты-это неправильно. Бит маскирования операцию неправильно, позволяя через много суррогатов. Я думаю, вы могли бы написать:

if (expected_length == 3 && (origin[0] == 0xED && (origin[1] & 0xE0) != 0x80))

Условия для сверхдолгим форм и суррогатов может быть уменьшена до:

/* All bytes are correct; check out for overlong forms and surrogates */
if ((expected_length == 2 && ((origin[0] & 0xFE) == 0xC0)) ||
(expected_length == 3 && (origin[0] == 0xE0 && (origin[1] & 0xE0) == 0x80)) ||
(expected_length == 4 && (origin[0] == 0xF0 && (origin[1] & 0xF0) == 0x80)) ||
(expected_length == 4 && (origin[0] == 0xF4 && (origin[1] > 0x8F))) ||
(expected_length == 3 && (origin[0] == 0xED && (origin[1] & 0xE0) != 0x80)))
{
ls->unread = 0;
ls->pos = NULL;
return LUATEXTS_EBADUTF8;
}

Это значительно короче и, следовательно, более читаемым, чем оригинал. Ближайшее симметрии условий, становится очевидной макет. Он шире, чем 80 символов; если это проблема, я бы сократить название expected_length (возможно exp_len).

7
ответ дан 3 апреля 2011 в 04:04 Источник Поделиться