Changeset 65bf084 in mainline for common/str.c


Ignore:
Timestamp:
2025-04-15T18:53:31Z (19 hours ago)
Author:
Jiří Zárevúcky <zarevucky.jiri@…>
Branches:
master
Children:
bfee444, f444633
Parents:
f94a11f
git-author:
Jiří Zárevúcky <zarevucky.jiri@…> (2025-04-15 18:42:32)
git-committer:
Jiří Zárevúcky <zarevucky.jiri@…> (2025-04-15 18:53:31)
Message:

Implement both str_decode() and mbrtoc32() using one function

File:
1 edited

Legend:

Unmodified
Added
Removed
  • common/str.c

    rf94a11f r65bf084  
    55 * Copyright (c) 2011 Martin Sucha
    66 * Copyright (c) 2011 Oleg Romanenko
     7 * Copyright (c) 2025 Jiří Zárevúcky
    78 * All rights reserved.
    89 *
     
    124125#include <ctype.h>
    125126#include <errno.h>
     127#include <limits.h>
    126128#include <macros.h>
    127129#include <mem.h>
     
    132134#include <uchar.h>
    133135
     136#if __STDC_HOSTED__
     137#include <fibril.h>
     138#endif
     139
     140static void _set_ilseq()
     141{
     142#ifdef errno
     143        errno = EILSEQ;
     144#endif
     145}
     146
    134147/** Byte mask consisting of lowest @n bits (out of 8) */
    135148#define LO_MASK_8(n)  ((uint8_t) ((1 << (n)) - 1))
     
    144157#define CONT_BITS  6
    145158
     159#define UTF8_MASK_INITIAL2  0b00011111
     160#define UTF8_MASK_INITIAL3  0b00001111
     161#define UTF8_MASK_INITIAL4  0b00000111
     162#define UTF8_MASK_CONT      0b00111111
     163
     164#define CHAR_INVALID ((char32_t) UINT_MAX)
     165
    146166static inline bool _is_ascii(uint8_t b)
    147167{
     
    149169}
    150170
    151 static inline bool _is_continuation_byte(uint8_t b)
    152 {
    153         return (b & 0xc0) == 0x80;
     171static inline bool _is_continuation(uint8_t b)
     172{
     173        return (b & 0xC0) == 0x80;
     174}
     175
     176static inline bool _is_2_byte(uint8_t c)
     177{
     178        return (c & 0xE0) == 0xC0;
     179}
     180
     181static inline bool _is_3_byte(uint8_t c)
     182{
     183        return (c & 0xF0) == 0xE0;
     184}
     185
     186static inline bool _is_4_byte(uint8_t c)
     187{
     188        return (c & 0xF8) == 0xF0;
    154189}
    155190
     
    179214
    180215        /* 110xxxxx 10xxxxxx */
    181         if ((b & 0xe0) == 0xc0)
     216        if (_is_2_byte(b))
    182217                return 1;
    183218
    184219        /* 1110xxxx 10xxxxxx 10xxxxxx */
    185         if ((b & 0xf0) == 0xe0)
     220        if (_is_3_byte(b))
    186221                return 2;
    187222
    188223        /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
    189         if ((b & 0xf8) == 0xf0)
     224        if (_is_4_byte(b))
    190225                return 3;
    191226
    192227        return -1;
     228}
     229
     230static bool _is_non_shortest(const mbstate_t *mb, uint8_t b)
     231{
     232        return (mb->state == 0b1111110000000000 && !(b & 0b00100000)) ||
     233            (mb->state == 0b1111111111110000 && !(b & 0b00110000));
     234}
     235
     236#define _likely(expr) __builtin_expect((expr), true)
     237#define _unlikely(expr) __builtin_expect((expr), false)
     238
     239#define FAST_PATHS 1
     240
     241static char32_t _str_decode(const char *s, size_t *offset, size_t size, mbstate_t *mb)
     242{
     243        assert(s);
     244        assert(offset);
     245        assert(*offset <= size);
     246        assert(size == STR_NO_LIMIT || s + size >= s);
     247        assert(mb);
     248
     249        if (*offset == size)
     250                return 0;
     251
     252        if (_likely(!mb->state)) {
     253                /* Clean slate, read initial byte. */
     254                uint8_t b = s[(*offset)++];
     255
     256                /* Fast exit for the most common case. */
     257                if (_likely(_is_ascii(b)))
     258                        return b;
     259
     260                /* unexpected continuation byte */
     261                if (_unlikely(_is_continuation(b)))
     262                        return CHAR_INVALID;
     263
     264                /*
     265                 * The value stored into `continuation` is designed to have
     266                 * just enough leading ones that after shifting in one less than
     267                 * the expected number of continuation bytes, the most significant
     268                 * bit becomes zero. (The field is 16b wide.)
     269                 */
     270
     271                if (_is_2_byte(b)) {
     272                        /* Reject non-shortest form. */
     273                        if (_unlikely(!(b & 0b00011110)))
     274                                return CHAR_INVALID;
     275
     276#if FAST_PATHS
     277                        /* We can usually take this exit. */
     278                        if (_likely(*offset < size && _is_continuation(s[*offset])))
     279                                return (b & UTF8_MASK_INITIAL2) << 6 |
     280                                    (s[(*offset)++] & UTF8_MASK_CONT);
     281#endif
     282
     283                        /* 2 byte continuation    110xxxxx */
     284                        mb->state = b ^ 0b0000000011000000;
     285
     286                } else if (_is_3_byte(b)) {
     287#if FAST_PATHS
     288                        /* We can usually take this exit. */
     289                        if (_likely(*offset + 1 < size && _is_continuation(s[*offset]) && _is_continuation(s[*offset + 1]))) {
     290
     291                                char32_t ch = (b & UTF8_MASK_INITIAL3) << 12 |
     292                                    (s[(*offset)] & UTF8_MASK_CONT) << 6 |
     293                                    (s[(*offset) + 1] & UTF8_MASK_CONT);
     294
     295                                *offset += 2;
     296
     297                                /* Reject non-shortest form. */
     298                                if (_unlikely(!(ch & 0xFFFFF800)))
     299                                        return CHAR_INVALID;
     300
     301                                return ch;
     302                        }
     303#endif
     304
     305                        /* 3 byte continuation    1110xxxx */
     306                        mb->state = b ^ 0b1111110011100000;
     307
     308                } else if (_is_4_byte(b)) {
     309#if FAST_PATHS
     310                        /* We can usually take this exit. */
     311                        if (_likely(*offset + 2 < size && _is_continuation(s[*offset]) &&
     312                            _is_continuation(s[*offset + 1]) && _is_continuation(s[*offset + 2]))) {
     313
     314                                char32_t ch = (b & UTF8_MASK_INITIAL4) << 18 |
     315                                    (s[(*offset)] & UTF8_MASK_CONT) << 12 |
     316                                    (s[(*offset) + 1] & UTF8_MASK_CONT) << 6 |
     317                                    (s[(*offset) + 2] & UTF8_MASK_CONT);
     318
     319                                *offset += 3;
     320
     321                                /* Reject non-shortest form. */
     322                                if (_unlikely(!(ch & 0xFFFF0000)))
     323                                        return CHAR_INVALID;
     324
     325                                return ch;
     326                        }
     327#endif
     328
     329                        /* 4 byte continuation    11110xxx */
     330                        mb->state = b ^ 0b1111111100000000;
     331                } else {
     332                        return CHAR_INVALID;
     333                }
     334        }
     335
     336        /* Deal with the remaining edge and invalid cases. */
     337        for (; *offset < size; (*offset)++) {
     338                /* Read continuation bytes. */
     339                uint8_t b = s[*offset];
     340
     341                if (!_is_continuation(b) || _is_non_shortest(mb, b)) {
     342                        mb->state = 0;
     343                        return CHAR_INVALID;
     344                }
     345
     346                /* Top bit becomes zero when shifting in the second to last byte. */
     347                if (!(mb->state & 0x8000)) {
     348                        char32_t c = ((char32_t) mb->state) << 6 | (b & UTF8_MASK_CONT);
     349                        mb->state = 0;
     350                        (*offset)++;
     351                        return c;
     352                }
     353
     354                mb->state = mb->state << 6 | (b & UTF8_MASK_CONT);
     355        }
     356
     357        /* Incomplete character. */
     358        assert(mb->state);
     359        return 0;
     360}
     361
     362/** Standard <uchar.h> function since C11. */
     363size_t mbrtoc32(char32_t *c, const char *s, size_t n, mbstate_t *mb)
     364{
     365#if __STDC_HOSTED__
     366        static fibril_local mbstate_t global_state = { };
     367
     368        if (!mb)
     369                mb = &global_state;
     370#endif
     371
     372        if (!s) {
     373                /* Equivalent to mbrtoc32(NULL, "", 1, mb); */
     374                c = NULL;
     375                s = "";
     376                n = 1;
     377        }
     378
     379        size_t offset = 0;
     380        char32_t ret = _str_decode(s, &offset, n, mb);
     381        if (ret == CHAR_INVALID) {
     382                assert(!mb->state);
     383                _set_ilseq();
     384                return UCHAR_ILSEQ;
     385        }
     386        if (mb->state) {
     387                assert(ret == 0);
     388                return UCHAR_INCOMPLETE;
     389        }
     390
     391        if (c)
     392                *c = ret;
     393        return ret ? offset : 0;
    193394}
    194395
     
    210411char32_t str_decode(const char *str, size_t *offset, size_t size)
    211412{
    212         if (*offset >= size)
    213                 return 0;
    214 
    215         /* First byte read from string */
    216         uint8_t b0 = (uint8_t) str[(*offset)++];
    217 
    218         /* Fast exit for the most common case. */
    219         if (_is_ascii(b0))
    220                 return b0;
    221 
    222         /* 10xxxxxx -- unexpected continuation byte */
    223         if (_is_continuation_byte(b0))
     413        mbstate_t mb = { };
     414        char32_t ch = _str_decode(str, offset, size, &mb);
     415
     416        if (ch == CHAR_INVALID)
    224417                return U_SPECIAL;
    225418
    226         /* Determine code length */
    227 
    228         int cbytes = _continuation_bytes(b0);
    229         int b0_bits = 6 - cbytes;  /* Data bits in first byte */
    230 
    231         if (cbytes < 0 || *offset + cbytes > size)
    232                 return U_SPECIAL;
    233 
    234         char32_t ch = b0 & LO_MASK_8(b0_bits);
    235 
    236         /* Decode continuation bytes */
    237         for (int i = 0; i < cbytes; i++) {
    238                 uint8_t b = (uint8_t) str[*offset];
    239 
    240                 if (!_is_continuation_byte(b))
    241                         return U_SPECIAL;
    242 
    243                 (*offset)++;
    244 
    245                 /* Shift data bits to ch */
    246                 ch = (ch << CONT_BITS) | (char32_t) (b & LO_MASK_8(CONT_BITS));
    247         }
    248 
    249         /*
    250          * Reject non-shortest form encodings.
    251          * See https://www.unicode.org/versions/corrigendum1.html
    252          */
    253         if (cbytes != _char_continuation_bytes(ch))
     419        if (mb.state)
    254420                return U_SPECIAL;
    255421
     
    282448                uint8_t b = (uint8_t) str[--(*offset)];
    283449
    284                 if (_is_continuation_byte(b)) {
     450                if (_is_continuation(b)) {
    285451                        cbytes++;
    286452                        continue;
    287453                }
    288454
    289                 /* Invalid byte. */
     455                /* Reject non-shortest form encoding. */
    290456                if (cbytes != _continuation_bytes(b))
    291457                        return U_SPECIAL;
     
    317483errno_t chr_encode(char32_t ch, char *str, size_t *offset, size_t size)
    318484{
     485        // TODO: merge with c32rtomb()
     486
    319487        if (*offset >= size)
    320488                return EOVERFLOW;
     
    372540                /* Check continuation bytes. */
    373541                for (int i = 1; i <= cont; i++) {
    374                         if (!_is_continuation_byte(b[i])) {
     542                        if (!_is_continuation(b[i])) {
    375543                                b[0] = U_SPECIAL;
    376544                                continue;
Note: See TracChangeset for help on using the changeset viewer.