/*************************************************************** MPU-UTF8.C This file contains UTF-8 <-> UCS-2 conversion and character/string stream operations. PART OF : MPUIO - library . USAGE : Internal and external . NOTE : UCS-2 is used strictly. Unicode values above U+FFFF and surrogate values are not accepted. Copyright (C) 2000 - 2026 by Andrey V.Kosteltsev. All Rights Reserved. ***************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include static int __mpu_UTF8_continuation( mpu_FILE *stream ) { int c; c = __mpu_IO_get_byte_unlocked( stream ); if( c == mpu_EOF ) { if( stream->_flags & __MPU_IO_EOF_SEEN ) { stream->_flags &= ~__MPU_IO_EOF_SEEN; stream->_flags |= __MPU_IO_ERR_SEEN; errno = EILSEQ; } return( mpu_EOF ); } if( (c & 0xc0) != 0x80 ) { stream->_flags |= __MPU_IO_ERR_SEEN; errno = EILSEQ; return( mpu_EOF ); } return( c ); } /* End of __mpu_UTF8_continuation() */ int __mpu_UTF8_decode_unlocked( mpu_FILE *stream, __mpu_char16_t *wc ) { unsigned int u; int c0; int c1; int c2; int c3; c0 = __mpu_IO_get_byte_unlocked( stream ); if( c0 == mpu_EOF ) return( mpu_EOF ); if( c0 < 0x80 ) { *wc = (__mpu_char16_t)c0; return( 0 ); } if( c0 >= 0xc2 && c0 <= 0xdf ) { c1 = __mpu_UTF8_continuation( stream ); if( c1 == mpu_EOF ) return( mpu_EOF ); u = ((unsigned int)(c0 & 0x1f) << 6) | (unsigned int)(c1 & 0x3f); } else if( c0 >= 0xe0 && c0 <= 0xef ) { c1 = __mpu_UTF8_continuation( stream ); if( c1 == mpu_EOF ) return( mpu_EOF ); c2 = __mpu_UTF8_continuation( stream ); if( c2 == mpu_EOF ) return( mpu_EOF ); if( (c0 == 0xe0 && c1 < 0xa0) || (c0 == 0xed && c1 >= 0xa0) ) { stream->_flags |= __MPU_IO_ERR_SEEN; errno = EILSEQ; return( mpu_EOF ); } u = ((unsigned int)(c0 & 0x0f) << 12) | ((unsigned int)(c1 & 0x3f) << 6) | (unsigned int)(c2 & 0x3f); } else if( c0 >= 0xf0 && c0 <= 0xf4 ) { c1 = __mpu_UTF8_continuation( stream ); if( c1 == mpu_EOF ) return( mpu_EOF ); c2 = __mpu_UTF8_continuation( stream ); if( c2 == mpu_EOF ) return( mpu_EOF ); c3 = __mpu_UTF8_continuation( stream ); if( c3 == mpu_EOF ) return( mpu_EOF ); if( (c0 == 0xf0 && c1 < 0x90) || (c0 == 0xf4 && c1 > 0x8f) ) { stream->_flags |= __MPU_IO_ERR_SEEN; errno = EILSEQ; return( mpu_EOF ); } /* This is a valid Unicode scalar value above U+FFFF, but strict UCS-2 has no representation for it. */ stream->_flags |= __MPU_IO_ERR_SEEN; errno = EILSEQ; return( mpu_EOF ); } else { stream->_flags |= __MPU_IO_ERR_SEEN; errno = EILSEQ; return( mpu_EOF ); } if( u >= 0xd800U && u <= 0xdfffU ) { stream->_flags |= __MPU_IO_ERR_SEEN; errno = EILSEQ; return( mpu_EOF ); } *wc = (__mpu_char16_t)u; return( 0 ); } /* End of __mpu_UTF8_decode_unlocked() */ int __mpu_UTF8_encode_unlocked( __mpu_char16_t wc, mpu_FILE *stream ) { unsigned int u = (unsigned int)wc; unsigned char out[3]; int n; int i; if( u >= 0xd800U && u <= 0xdfffU ) { errno = EILSEQ; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } if( u < 0x80U ) { out[0] = (unsigned char)u; n = 1; } else if( u < 0x800U ) { out[0] = (unsigned char)(0xc0U | (u >> 6)); out[1] = (unsigned char)(0x80U | (u & 0x3fU)); n = 2; } else { out[0] = (unsigned char)(0xe0U | (u >> 12)); out[1] = (unsigned char)(0x80U | ((u >> 6) & 0x3fU)); out[2] = (unsigned char)(0x80U | (u & 0x3fU)); n = 3; } for( i = 0; i < n; ++i ) if( __mpu_IO_put_byte_unlocked( out[i], stream ) == mpu_EOF ) return( mpu_EOF ); return( 0 ); } /* End of __mpu_UTF8_encode_unlocked() */ /*************************************************************** Read one UCS-2 character from STREAM without acquiring its lock. ***************************************************************/ int mpu_fgetc_unlocked( mpu_FILE *stream ) { __mpu_char16_t wc; int ret; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); ret = __mpu_IO_get_char_unlocked( stream, &wc ); return( ret == 0 ? (int)wc : mpu_EOF ); } /* End of mpu_fgetc_unlocked() */ int mpu_fgetc( mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); pthread_mutex_lock( &stream->_lock ); ret = mpu_fgetc_unlocked( stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_fgetc() */ /*************************************************************** Alias of mpu_fgetc_unlocked(). ***************************************************************/ int mpu_getc_unlocked( mpu_FILE *stream ) { return( mpu_fgetc_unlocked( stream ) ); } /* End of mpu_getc_unlocked() */ int mpu_getc( mpu_FILE *stream ) { return( mpu_fgetc( stream ) ); } /* End of mpu_getc() */ /*************************************************************** Push one UCS-2 character back to STREAM. At least one character of pushback is guaranteed. ***************************************************************/ int mpu_ungetc( __mpu_char16_t c, mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); pthread_mutex_lock( &stream->_lock ); ret = __mpu_IO_unget_char_unlocked( c, stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_ungetc() */ /*************************************************************** Read one UCS-2 character from mpu_stdin without locking it. ***************************************************************/ int mpu_getchar_unlocked( void ) { return( mpu_fgetc_unlocked( mpu_stdin ) ); } /* End of mpu_getchar_unlocked() */ int mpu_getchar( void ) { return( mpu_fgetc( mpu_stdin ) ); } /* End of mpu_getchar() */ /*************************************************************** Write one UCS-2 character to STREAM without acquiring its lock. ***************************************************************/ int mpu_fputc_unlocked( __mpu_char16_t c, mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); ret = __mpu_IO_put_char_unlocked( c, stream ); return( ret == 0 ? (int)c : mpu_EOF ); } /* End of mpu_fputc_unlocked() */ int mpu_fputc( __mpu_char16_t c, mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); pthread_mutex_lock( &stream->_lock ); ret = mpu_fputc_unlocked( c, stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_fputc() */ /*************************************************************** Alias of mpu_fputc_unlocked(). ***************************************************************/ int mpu_putc_unlocked( __mpu_char16_t c, mpu_FILE *stream ) { return( mpu_fputc_unlocked( c, stream ) ); } /* End of mpu_putc_unlocked() */ int mpu_putc( __mpu_char16_t c, mpu_FILE *stream ) { return( mpu_fputc( c, stream ) ); } /* End of mpu_putc() */ /*************************************************************** Write one UCS-2 character to mpu_stdout without locking it. ***************************************************************/ int mpu_putchar_unlocked( __mpu_char16_t c ) { return( mpu_fputc_unlocked( c, mpu_stdout ) ); } /* End of mpu_putchar_unlocked() */ int mpu_putchar( __mpu_char16_t c ) { return( mpu_fputc( c, mpu_stdout ) ); } /* End of mpu_putchar() */ /*************************************************************** Read one UCS-2 line/string from STREAM without acquiring its lock. ***************************************************************/ __mpu_char16_t *mpu_fgets_unlocked( __mpu_char16_t *s, int n, mpu_FILE *stream ) { int i; if( s == (__mpu_char16_t *)0 || n <= 0 ) { errno = EINVAL; return( (__mpu_char16_t *)0 ); } if( !__mpu_IO_valid( stream ) ) return( (__mpu_char16_t *)0 ); for( i = 0; i < n - 1; ++i ) { __mpu_char16_t wc; if( __mpu_IO_get_char_unlocked( stream, &wc ) == mpu_EOF ) break; s[i] = wc; if( wc == (__mpu_char16_t)'\n' ) { ++i; break; } } s[i] = (__mpu_char16_t)0; if( i == 0 && (stream->_flags & (__MPU_IO_EOF_SEEN | __MPU_IO_ERR_SEEN)) ) return( (__mpu_char16_t *)0 ); return( s ); } /* End of mpu_fgets_unlocked() */ __mpu_char16_t *mpu_fgets( __mpu_char16_t *s, int n, mpu_FILE *stream ) { __mpu_char16_t *ret; if( !__mpu_IO_valid( stream ) ) return( (__mpu_char16_t *)0 ); pthread_mutex_lock( &stream->_lock ); ret = mpu_fgets_unlocked( s, n, stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_fgets() */ /*************************************************************** Write a NUL-terminated UCS-2 string without acquiring STREAM lock. ***************************************************************/ int mpu_fputs_unlocked( const __mpu_char16_t *s, mpu_FILE *stream ) { if( s == (const __mpu_char16_t *)0 ) { errno = EINVAL; return( mpu_EOF ); } if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); while( *s ) { if( __mpu_IO_put_char_unlocked( *s++, stream ) == mpu_EOF ) return( mpu_EOF ); } return( 0 ); } /* End of mpu_fputs_unlocked() */ int mpu_fputs( const __mpu_char16_t *s, mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); pthread_mutex_lock( &stream->_lock ); ret = mpu_fputs_unlocked( s, stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_fputs() */ int mpu_puts( const __mpu_char16_t *s ) { int ret = 0; if( !__mpu_IO_valid( mpu_stdout ) ) return( mpu_EOF ); pthread_mutex_lock( &mpu_stdout->_lock ); if( mpu_fputs_unlocked( s, mpu_stdout ) == mpu_EOF || mpu_fputc_unlocked( (__mpu_char16_t)'\n', mpu_stdout ) == mpu_EOF ) ret = mpu_EOF; pthread_mutex_unlock( &mpu_stdout->_lock ); return( ret ); } /* End of mpu_puts() */ /*************************************************************** Read UCS-2 characters through DELIMITER without acquiring the stream lock, growing *LINEPTR as required. The delimiter is included in the returned string and a trailing NUL is added. ***************************************************************/ ssize_t mpu_getdelim_unlocked( __mpu_char16_t **lineptr, size_t *n, __mpu_char16_t delimiter, mpu_FILE *stream ) { __mpu_char16_t *line; size_t capacity; size_t length = 0; if( lineptr == (__mpu_char16_t **)0 || n == (size_t *)0 ) { errno = EINVAL; return( (ssize_t)-1 ); } if( delimiter >= (__mpu_char16_t)0xd800 && delimiter <= (__mpu_char16_t)0xdfff ) { errno = EILSEQ; return( (ssize_t)-1 ); } if( !__mpu_IO_valid( stream ) ) return( (ssize_t)-1 ); line = *lineptr; capacity = *n; if( line == (__mpu_char16_t *)0 || capacity == 0 ) { capacity = 128; line = (__mpu_char16_t *)malloc( capacity * sizeof( __mpu_char16_t ) ); if( line == (__mpu_char16_t *)0 ) return( (ssize_t)-1 ); *lineptr = line; *n = capacity; } for( ;; ) { __mpu_char16_t wc; if( __mpu_IO_get_char_unlocked( stream, &wc ) == mpu_EOF ) { if( length == 0 ) return( (ssize_t)-1 ); break; } if( length == (size_t)SSIZE_MAX ) { errno = EOVERFLOW; stream->_flags |= __MPU_IO_ERR_SEEN; return( (ssize_t)-1 ); } if( length + 1 >= capacity ) { size_t new_capacity; __mpu_char16_t *new_line; if( capacity > SIZE_MAX / 2 ) { errno = EOVERFLOW; stream->_flags |= __MPU_IO_ERR_SEEN; return( (ssize_t)-1 ); } new_capacity = capacity * 2; if( new_capacity > SIZE_MAX / sizeof( __mpu_char16_t ) ) { errno = EOVERFLOW; stream->_flags |= __MPU_IO_ERR_SEEN; return( (ssize_t)-1 ); } new_line = (__mpu_char16_t *)realloc( line, new_capacity * sizeof( __mpu_char16_t ) ); if( new_line == (__mpu_char16_t *)0 ) { stream->_flags |= __MPU_IO_ERR_SEEN; return( (ssize_t)-1 ); } line = new_line; capacity = new_capacity; *lineptr = line; *n = capacity; } line[length++] = wc; if( wc == delimiter ) break; } line[length] = (__mpu_char16_t)0; return( (ssize_t)length ); } /* End of mpu_getdelim_unlocked() */ /*************************************************************** Read UCS-2 characters through DELIMITER, growing *LINEPTR as required. This is the locked form of mpu_getdelim_unlocked(). ***************************************************************/ ssize_t mpu_getdelim( __mpu_char16_t **lineptr, size_t *n, __mpu_char16_t delimiter, mpu_FILE *stream ) { ssize_t ret; if( !__mpu_IO_valid( stream ) ) return( (ssize_t)-1 ); pthread_mutex_lock( &stream->_lock ); ret = mpu_getdelim_unlocked( lineptr, n, delimiter, stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_getdelim() */ /*************************************************************** Read one UCS-2 line without acquiring STREAM lock. The newline, when present, is included in the returned string. ***************************************************************/ ssize_t mpu_getline_unlocked( __mpu_char16_t **lineptr, size_t *n, mpu_FILE *stream ) { return( mpu_getdelim_unlocked( lineptr, n, (__mpu_char16_t)'\n', stream ) ); } /* End of mpu_getline_unlocked() */ /*************************************************************** Read one UCS-2 line, growing *LINEPTR as required. ***************************************************************/ ssize_t mpu_getline( __mpu_char16_t **lineptr, size_t *n, mpu_FILE *stream ) { return( mpu_getdelim( lineptr, n, (__mpu_char16_t)'\n', stream ) ); } /* End of mpu_getline() */ /*************************************************************** Hide internal symbols: ***************************************************************/ __mpu_hidden_decl(__mpu_UTF8_decode_unlocked); __mpu_hidden_decl(__mpu_UTF8_encode_unlocked); /* End of hide internal symbols. ***************************************************************/ /* End of mpu-utf8.c */